[{"content":"Introduction I used TMux in the past, however, it never stuck with me. Since I&rsquo;m a Rust fan boy, I tried Zellij and I fell in love with it. It had everything that I needed and for me the best was that I didn&rsquo;t have to learn the shortcuts since Zellij UI has all of them. For a beginner, it was a blessing.\nI used Zellij in my day-to-day work, however, I saw a youtube video that someone entering something like a VIM selection mode in the terminal copying what they need and then continue executing CLI commands. I was intrigued, I searched and I found that terminal emulators have shortcuts to try to do something similar. Also I&rsquo;ve added a shortcut in Zellij to open the current pane in my editor. However, it doesn&rsquo;t feel as native, so I gave tmux a try. I saw some videos and I tried to do a config as close to zellij keybinds since I already had the muscle memory. And then, I found tmux copy selection mode and I found my home.\nSince I like tmux so much, I&rsquo;m writing this tutorial on how I configured my tmux and I will highlight some of my use cases, I hope it helps someone. Okay, you got me, I&rsquo;m writing this to try to convince a friend how good it is \ud83d\ude05\nTL;DR You can find my complete dotfiles at github.com\/jmarcelomb\/.dotfiles, and my tmux config specifically at .config\/tmux\/tmux.conf.\nClick to expand configuration # ============================================================================ # GENERAL OPTIONS # ============================================================================ # Mouse support and terminal settings set -g mouse on set -g default-terminal &#34;tmux-256color&#34; set-option -sa terminal-overrides &#34;,xterm*:Tc&#34; set-option -g status-position top set -g set-clipboard on set -g detach-on-destroy off # Don&#39;t exit from tmux when closing a session # Window and pane numbering set -g base-index 1 # Start windows at 1, not 0 setw -g pane-base-index 1 # Start panes at 1, not 0 set-window-option -g pane-base-index 1 set-option -g renumber-windows on # Renumber windows when one is closed # Window behavior set-option -g allow-rename off # Don&#39;t rename windows automatically # Enable Yazi Image Previewer set -g allow-passthrough on set -ga update-environment TERM set -ga update-environment TERM_PROGRAM # ============================================================================ # Window popups # ============================================================================ # Popup session switcher \/ creator with fzf # - shows list of sessions # - Ctrl-K kills a session (and reloads list) # - Enter on existing session \u2192 switch # - Enter on query \u2192 create + switch # - Preview pane shows windows inside the session bind O display-popup -w 80% -h 70% -E &#34;$HOME\/.config\/tmux\/scripts\/session-fzf.sh&#34; bind-key &#34;T&#34; run-shell &#34;sesh connect \\&#34;$( sesh list --icons | fzf-tmux -p 80%,70% \\ --no-sort --ansi --border-label &#39; sesh &#39; --prompt &#39;\u26a1 &#39; \\ --header &#39; ^a all ^t tmux ^g configs ^x zoxide ^d tmux kill ^f find&#39; \\ --bind &#39;tab:down,btab:up&#39; \\ --bind &#39;ctrl-a:change-prompt(\u26a1 )+reload(sesh list --icons)&#39; \\ --bind &#39;ctrl-t:change-prompt(\ud83e\ude9f )+reload(sesh list -t --icons)&#39; \\ --bind &#39;ctrl-g:change-prompt(\u2699\ufe0f )+reload(sesh list -c --icons)&#39; \\ --bind &#39;ctrl-x:change-prompt(\ud83d\udcc1 )+reload(sesh list -z --icons)&#39; \\ --bind &#39;ctrl-f:change-prompt(\ud83d\udd0e )+reload(fd -H -d 2 -t d -E .Trash . ~)&#39; \\ --bind &#39;ctrl-d:execute(tmux kill-session -t {2..})+change-prompt(\u26a1 )+reload(sesh list --icons)&#39; \\ --preview-window &#39;right:55%&#39; \\ --preview &#39;sesh preview {}&#39; )\\&#34;&#34; bind -N &#34;last-session (via sesh) &#34; o run-shell &#34;sesh last&#34; # Key bindings help bind k display-popup \\ -w 90% \\ -h 100% \\ -E &#34;tmux list-keys | fzf&#34; # AI Assistant popup bind a display-popup -w 40% -h 80% -E &#34;fish -c &#39;ask -i&#39;&#34; # ============================================================================ # PREFIX KEY # ============================================================================ unbind C-b set -g prefix ` bind ` send-prefix # ============================================================================ # KEY BINDINGS # ============================================================================ # Configuration reload unbind r bind r source-file ~\/.config\/tmux\/tmux.conf \\; display-message &#34;tmux.conf reloaded \u263a\ufe0f&#34; # Unbind Ctrl+T to let it pass through to shell unbind C-t # Pane splitting (open in current directory) bind &#39;&#34;&#39; split-window -v -c &#34;#{pane_current_path}&#34; bind &#39;-&#39; split-window -v -c &#34;#{pane_current_path}&#34; bind % split-window -h -c &#34;#{pane_current_path}&#34; bind &#39;|&#39; split-window -h -c &#34;#{pane_current_path}&#34; # New window in same path bind c new-window -c &#34;#{pane_current_path}&#34; bind-key S command-prompt -p &#34;Session name:&#34; &#34;new-session -s &#39;%%&#39;&#34; # Window navigation bind -n C-H previous-window bind -n C-L next-window bind -n M-S-Left previous-window bind -n M-S-Right next-window # Smart pane\/window navigation with Alt + Arrow keys # Alt + Left\/Right switches panes unless at screen edge, then switches windows bind -n M-Left if-shell &#34;tmux display-message -p &#39;#{pane_at_left}&#39; | grep -q 1&#34; &#34;previous-window&#34; &#34;select-pane -L&#34; bind -n M-Right if-shell &#34;tmux display-message -p &#39;#{pane_at_right}&#39; | grep -q 1&#34; &#34;next-window&#34; &#34;select-pane -R&#34; bind -n M-Up select-pane -U bind -n M-Down select-pane -D # Pane resizing with leader + HJKL bind -r H resize-pane -L 5 bind -r J resize-pane -D 5 bind -r K resize-pane -U 5 bind -r L resize-pane -R 5 bind -r m resize-pane -Z # Use m key to maximize pane # Pane resizing with leader + Arrow keys bind -r Left resize-pane -L 5 bind -r Down resize-pane -D 5 bind -r Up resize-pane -U 5 bind -r Right resize-pane -R 5 # Window reordering bind-key -n M-i swap-window -t -1\\; select-window -t -1 bind-key -n M-o swap-window -t +1\\; select-window -t +1 # ============================================================================ # VI MODE &amp; COPY MODE # ============================================================================ # Highlight pane when entering copy mode set-hook -g pane-mode-changed { if -F &#34;#{pane_in_mode}&#34; &#34;selectp -P bg=#181818&#34; &#34;selectp -P default&#34; } set-window-option -g mode-keys vi # Vi-style copy mode keybindings bind-key -T copy-mode-vi v send-keys -X begin-selection bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel &#34;pbcopy&#34; # AI Ask with selection - copy selection and open AI chat bind-key -T copy-mode-vi a send-keys -X copy-selection \\; run-shell &#34;$HOME\/.config\/tmux\/scripts\/ai-ask-selection.sh&#34; # Don&#39;t exit copy mode when dragging with mouse unbind -T copy-mode-vi MouseDragEnd1Pane # ============================================================================ # PLUGINS # ============================================================================ # Plugin manager set -g @plugin &#39;tmux-plugins\/tpm&#39; set -g @plugin &#39;tmux-plugins\/tmux-sensible&#39; # Theme set -g @plugin &#39;catppuccin\/tmux&#39; # Functionality plugins set -g @plugin &#39;christoomey\/vim-tmux-navigator&#39; set -g @plugin &#39;tmux-plugins\/tmux-yank&#39; set -g @plugin &#39;omerxx\/tmux-floax&#39; set -g @plugin &#39;tmux-plugins\/tmux-resurrect&#39; set -g @plugin &#39;tmux-plugins\/tmux-continuum&#39; set -g @plugin &#39;tmux-plugins\/tmux-open&#39; set -g @plugin &#39;tmux-plugins\/tmux-copycat&#39; set -g @plugin &#39;schasse\/tmux-jump&#39; # Commented out plugins (uncomment if needed) # set -g @plugin &#39;tmux-plugins\/tmux-cpu&#39; # set -g @plugin &#39;tmux-plugins\/tmux-battery&#39; # ============================================================================ # PLUGIN CONFIGURATION # ============================================================================ # Enable tmux-resurrect to capture pane contents and tmux-continuum to automatically restore sessions set -g @resurrect-capture-pane-contents &#39;on&#39; # set -g @continuum-restore &#39;on&#39; # Floax configuration # M- means &#34;hold Meta\/Alt&#34; set -g @floax-bind &#39;-n M-f&#39; # Catppuccin theme configuration set -g @catppuccin_flavor &#34;frappe&#34; set -g @catppuccin_window_status_style &#34;basic&#34; set -g @catppuccin_window_current_text &#34; #{window_name}&#34; set -g @catppuccin_window_text &#34; #{window_name}&#34; set -g @catppuccin_window_current_number_color &#34;#{?window_zoomed_flag,#{@thm_yellow},#{@thm_mauve}}&#34; # typos: ignore set -g @catppuccin_window_number_color &#34;#{?window_zoomed_flag,#{@thm_yellow},#{@thm_overlay_2}}&#34; # typos: ignore # ============================================================================ # STATUS LINE CONFIGURATION # ============================================================================ # Status line length settings set -g status-right-length 100 set -g status-left-length 100 set -g status-left &#34;&#34; # Status line modules set -g status-right &#34;#{E:@catppuccin_status_application}&#34; set -ag status-right &#34;#{E:@catppuccin_status_session}&#34; # Commented out status modules (uncomment if needed) # set -agF status-right &#34;#{E:@catppuccin_status_cpu}&#34; # set -ag status-right &#34;#{E:@catppuccin_status_uptime}&#34; # set -agF status-right &#34;#{E:@catppuccin_status_battery}&#34; # ============================================================================ # INITIALIZE PLUGIN MANAGER # ============================================================================ # Initialize TPM (keep this line at the very bottom of tmux.conf) run &#39;~\/.config\/tmux\/plugins\/tpm\/tpm&#39; Basic TMux Configuration I&rsquo;m going to describe my config assuming you&rsquo;re new to tmux, explaining both what I configured and why I made these choices.\nFirst of all, we have to create the ~\/.tmux.conf or $XDG_CONFIG_HOME\/.config\/tmux\/tmux.conf, I chose the latter to have everything in there.\nLeader Key Setup In tmux we have a leader key and then we can execute commands. The default is Ctrl b, I tried Ctrl a, Ctrl g as Zellij, but I ended up with ` by adding the following to the config:\nunbind C-b set -g prefix ` bind ` send-prefix It is very close to the numbers and it is very reachable and if I need to write `, you just have to press it twice.\nSince we are going to change the configuration, lets also add a keybind to hot reload the configuration. I use the following:\n# Configuration reload unbind r bind r source-file ~\/.config\/tmux\/tmux.conf \\; display-message &#34;tmux.conf reloaded \u263a\ufe0f&#34; Exit tmux and re-enter and now if you do changes to your config and do leader r it reloads, therefore if we do ` r it will show tmux.conf reloaded \u263a\ufe0f.\nGeneral Settings Now I&rsquo;m going to share several configuration sections with inline comments to explain each part.\n# Mouse support and terminal settings set -g mouse on set -g default-terminal &#34;tmux-256color&#34; # Useful when having problems if your terminal doesn&#39;t support kitty or ghostty set-option -sa terminal-overrides &#34;,xterm*:Tc&#34; # Setting the tmux status bar on top set-option -g status-position top set -g set-clipboard on set -g detach-on-destroy off # Don&#39;t exit from tmux when closing a session # Window and pane numbering set -g base-index 1 # Start windows at 1, not 0 setw -g pane-base-index 1 # Start panes at 1, not 0 set-window-option -g pane-base-index 1 set-option -g renumber-windows on # Renumber windows when one is closed # Window behavior set-option -g allow-rename off # Don&#39;t rename windows automatically # Enable Yazi Image Previewer set -g allow-passthrough on set -ga update-environment TERM set -ga update-environment TERM_PROGRAM Pane Splitting I got very used to the tmux keybinds to split windows that are leader % for vertical and leader &quot; for horizontal. However, I also added leader - for horizontal, and leader | for vertical. One nice thing to have here is to add this to execute the split with pane_current_path, so it opens a new pane in the current path that we are in.\n# Pane splitting (open in current directory) bind &#39;&#34;&#39; split-window -v -c &#34;#{pane_current_path}&#34; bind &#39;-&#39; split-window -v -c &#34;#{pane_current_path}&#34; bind % split-window -h -c &#34;#{pane_current_path}&#34; bind &#39;|&#39; split-window -h -c &#34;#{pane_current_path}&#34; As panes, the following is setting the new window current path to the current one.\n# New window in same path bind c new-window -c &#34;#{pane_current_path}&#34; Session Management Adding leader S to create a new session:\nbind-key S command-prompt -p &#34;Session name:&#34; &#34;new-session -s &#39;%%&#39;&#34; I used this a lot on the beginning, but now I just open sessions with sesh and they are path relative, but more on that later.\nWindow navigation The following commands is to make possible to move between windows\/panes with Ctrl H or Ctrl L or the one that I use most is Alt Arrow. If I want to move from one window to another without going through all the panes I do Alt Shift Left\/Right. When we are in the left most pane or right most pane, it goes to the next window.\nFor direct window access, you can jump to a specific window by pressing leader followed by the window number. For example, ` 1 jumps to window 1, ` 2 to window 2, and so on. This is a built-in tmux feature and very handy when you have many windows open.\n# Window navigation bind -n C-H previous-window bind -n C-L next-window bind -n M-S-Left previous-window bind -n M-S-Right next-window # Smart pane\/window navigation with Alt + Arrow keys # Alt + Left\/Right switches panes unless at screen edge, then switches windows bind -n M-Left if-shell &#34;tmux display-message -p &#39;#{pane_at_left}&#39; | grep -q 1&#34; &#34;previous-window&#34; &#34;select-pane -L&#34; bind -n M-Right if-shell &#34;tmux display-message -p &#39;#{pane_at_right}&#39; | grep -q 1&#34; &#34;next-window&#34; &#34;select-pane -R&#34; bind -n M-Up select-pane -U bind -n M-Down select-pane -D Pane Resizing For pane resizing I normally do leader arrows, while you are hitting the arrows it will move, but you can also do with leader HJKL, they need to be higher case since we have that in the bind.\n# Pane resizing with leader + HJKL bind -r H resize-pane -L 5 bind -r J resize-pane -D 5 bind -r K resize-pane -U 5 bind -r L resize-pane -R 5 bind -r m resize-pane -Z # Use m key to maximize pane # Pane resizing with leader + Arrow keys bind -r Left resize-pane -L 5 bind -r Down resize-pane -D 5 bind -r Up resize-pane -U 5 bind -r Right resize-pane -R 5 Window Reordering I like that in Zellij we can move windows around with Alt i to move the window to left in the tabs, or Alt o to the right, so I also added that with the following binds.\nbind-key -n M-i swap-window -t -1\\; select-window -t -1 bind-key -n M-o swap-window -t +1\\; select-window -t +1 Session Management with Sesh Sessions allow you to organize different projects or contexts, and you can detach\/attach to them at any time. I use sesh which is a smart session manager that integrates with tmux, zoxide, and fzf.\nSesh makes it incredibly easy to switch between projects. It can list your tmux sessions, zoxide directories, and even search for projects. Here are the keybinds I use:\n# Session switcher with sesh - shows all sessions, zoxide dirs, and more bind-key &#34;T&#34; run-shell &#34;sesh connect \\&#34;$( sesh list --icons | fzf-tmux -p 80%,70% \\ --no-sort --ansi --border-label &#39; sesh &#39; --prompt &#39;\u26a1 &#39; \\ --header &#39; ^a all ^t tmux ^g configs ^x zoxide ^d tmux kill ^f find&#39; \\ --bind &#39;tab:down,btab:up&#39; \\ --bind &#39;ctrl-a:change-prompt(\u26a1 )+reload(sesh list --icons)&#39; \\ --bind &#39;ctrl-t:change-prompt(\ud83e\ude9f )+reload(sesh list -t --icons)&#39; \\ --bind &#39;ctrl-g:change-prompt(\u2699\ufe0f )+reload(sesh list -c --icons)&#39; \\ --bind &#39;ctrl-x:change-prompt(\ud83d\udcc1 )+reload(sesh list -z --icons)&#39; \\ --bind &#39;ctrl-f:change-prompt(\ud83d\udd0e )+reload(fd -H -d 2 -t d -E .Trash . ~)&#39; \\ --bind &#39;ctrl-d:execute(tmux kill-session -t {2..})+change-prompt(\u26a1 )+reload(sesh list --icons)&#39; \\ --preview-window &#39;right:55%&#39; \\ --preview &#39;sesh preview {}&#39; )\\&#34;&#34; # Jump to last session quickly bind -N &#34;last-session (via sesh)&#34; o run-shell &#34;sesh last&#34; When I press leader T (capital T), it opens a fuzzy finder popup where I can:\nCtrl a - Show all sessions and directories Ctrl t - Show only tmux sessions Ctrl g - Show config directories Ctrl x - Show zoxide frecent directories Ctrl f - Find directories in home Ctrl d - Kill a session The preview pane shows what&rsquo;s inside each session This is incredibly powerful for jumping between projects. I also use leader o to quickly jump to my last session - I chose o because it reminds me of Vim&rsquo;s Ctrl o which jumps to the previous location.\nEssential Built-in Commands While I&rsquo;ve customized many keybindings, there are several built-in tmux commands that I use constantly and are worth highlighting:\nSession Commands ` d - Detach from session - Detach from your current session and return to your shell. You can reattach with tmux attach or tmux attach -t session-name. To be honest, I rarely use this on my local machine since I keep tmux running all the time - I only need it if I accidentally close my terminal. The main exception is when connecting to remote servers via SSH, where detaching is essential to keep processes running after disconnecting. For local work, I just switch between sessions using sesh instead of detaching.\n` : - Command prompt - As Vim, you can open the tmux command line where you can execute any tmux command directly. I use it a lot when I forget the keybind to rename a session or window.\n` $ - Rename session - Opens a prompt to rename the current session.\nWindow Commands ` , - Rename window - Since I have allow-rename off in my config, windows don&rsquo;t rename automatically. I use it to give names that I know what they are for. Pane Commands ` x - Kill pane - Closes the current pane (with confirmation). If it&rsquo;s the last pane in a window, the window closes too.\n` { \/ ` } - Swap pane positions - Move the current pane backwards ({) or forwards (}) in the pane arrangement. Great for reorganizing your layout.\n` ! - Break pane into new window - Takes the current pane and converts it into a new window. Super useful when you realize a pane deserves to be its own window, or when you want to temporarily focus on just that pane without losing your current layout.\nHelp ` ? - List all keybindings - Shows all available keybindings in a scrollable list. Press q to exit. I also added ` k which opens the keybindings in a fuzzy searchable fzf popup, which seemed like a great idea at first. To be honest though, I rarely use either of them anymore - once you learn your workflow, you just remember the keybindings. Vi Mode &amp; Copy Mode For me, where tmux shines is in the copy mode. You can enter it using leader [. And then you can move around, use vim motions, for example v for start selecting, V for full line selection. w for moving between words, b for back, f for find and so on. You can do y for yank (copy), and ? for search upwards, and then use the normal n for next, and N for previous. We can do more, but, I will talk in the plugin section.\nHere is the config:\nset-window-option -g mode-keys vi # Vi-style copy mode keybindings bind-key -T copy-mode-vi v send-keys -X begin-selection bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle # You should change `pbcopy` to your clipboard command! bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel &#34;pbcopy&#34; # Don&#39;t exit copy mode when dragging with mouse unbind -T copy-mode-vi MouseDragEnd1Pane Since sometimes I was entering copy mode without noticing, for me the following config was a game changer, when in copy mode, it sets the background to the color #181818.\n# Highlight pane when entering copy mode set-hook -g pane-mode-changed { if -F &#34;#{pane_in_mode}&#34; &#34;selectp -P bg=#181818&#34; &#34;selectp -P default&#34; } You can add keybinds so that you can select text and then call a script that you made, this is my selecting and piping into a script that I did that let me do questions about the selection into an LLM.\n# AI Ask with selection - copy selection and open AI chat bind-key -T copy-mode-vi a send-keys -X copy-selection \\; run-shell &#34;$HOME\/.config\/tmux\/scripts\/ai-ask-selection.sh&#34; Plugins Tmux has a great plugin ecosystem. To use plugins, you need TPM (Tmux Plugin Manager). Here are the plugins I use and why:\n# Plugin manager set -g @plugin &#39;tmux-plugins\/tpm&#39; set -g @plugin &#39;tmux-plugins\/tmux-sensible&#39; TPM is the plugin manager itself, and tmux-sensible provides sensible default settings that most people want.\nFor the theme, I use Catppuccin which gives tmux a beautiful modern look:\n# Theme set -g @plugin &#39;catppuccin\/tmux&#39; Catppuccin for tmux provides beautiful color schemes for the status bar and windows.\nNow for the functionality plugins that enhance tmux capabilities:\nset -g @plugin &#39;christoomey\/vim-tmux-navigator&#39; vim-tmux-navigator allows seamless navigation between vim splits and tmux panes. If you use vim\/neovim, this is essential. It lets you use the same keybindings to move between vim splits and tmux panes without thinking about the boundary.\nTo use it with Neovim, you need to install the plugin on the Neovim side as well. Here&rsquo;s my Neovim configuration (using lazy.nvim):\nreturn { &#34;christoomey\/vim-tmux-navigator&#34;, cmd = { &#34;TmuxNavigateLeft&#34;, &#34;TmuxNavigateDown&#34;, &#34;TmuxNavigateUp&#34;, &#34;TmuxNavigateRight&#34;, &#34;TmuxNavigatePrevious&#34;, &#34;TmuxNavigatorProcessList&#34;, }, keys = { { &#34;&lt;c-h&gt;&#34;, &#34;&lt;cmd&gt;&lt;C-U&gt;TmuxNavigateLeft&lt;cr&gt;&#34; }, { &#34;&lt;c-j&gt;&#34;, &#34;&lt;cmd&gt;&lt;C-U&gt;TmuxNavigateDown&lt;cr&gt;&#34; }, { &#34;&lt;c-k&gt;&#34;, &#34;&lt;cmd&gt;&lt;C-U&gt;TmuxNavigateUp&lt;cr&gt;&#34; }, { &#34;&lt;c-l&gt;&#34;, &#34;&lt;cmd&gt;&lt;C-U&gt;TmuxNavigateRight&lt;cr&gt;&#34; }, { &#34;&lt;c-\\\\&gt;&#34;, &#34;&lt;cmd&gt;&lt;C-U&gt;TmuxNavigatePrevious&lt;cr&gt;&#34; }, }, } With this setup, Ctrl h\/j\/k\/l works seamlessly between Neovim splits and tmux panes. I still use Alt Arrow keys most of the time out of habit, but having Ctrl h\/j\/k\/l available is great when my hands are already on the home row.\nset -g @plugin &#39;tmux-plugins\/tmux-yank&#39; tmux-yank enhances copying to system clipboard. It handles the clipboard commands for different operating systems automatically.\nset -g @plugin &#39;omerxx\/tmux-floax&#39; tmux-floax adds floating windows support. I use Alt f to toggle a floating terminal like in Zellij, which is great for quick commands. However, I rarely use it anymore since it doesn&rsquo;t feel as native as Zellij&rsquo;s implementation.\nset -g @plugin &#39;tmux-plugins\/tmux-resurrect&#39; set -g @plugin &#39;tmux-plugins\/tmux-continuum&#39; tmux-resurrect lets you save and restore tmux sessions, even after a system restart. tmux-continuum automates this process by continuously saving sessions.\nset -g @plugin &#39;tmux-plugins\/tmux-open&#39; tmux-open allows you to open highlighted text with appropriate applications. Select a URL in copy mode and press o to open it in your browser, or Ctrl o to open it in your $EDITOR. This is super useful when you have unit test failures or linting errors - just select the file path and Ctrl o to jump directly to it.\nset -g @plugin &#39;tmux-plugins\/tmux-copycat&#39; tmux-copycat enhances searching in copy mode. You can search for files, URLs, git commits, and more with predefined patterns. For example, leader Ctrl f searches and selects the first file path it finds in copy mode, then you can press n for next match. leader Ctrl u searches for URLs. Here are the main search shortcuts:\nleader \/ - regex search (or simple string search) leader Ctrl f - file path search leader Ctrl g - git status files (great after git status) leader Ctrl u - URL search (http, ftp, git) leader Ctrl d - number search leader Alt i - IP address search leader Alt h - SHA-1\/SHA-256 hash search (useful after git log) Once in &ldquo;copycat mode&rdquo;, use n to jump to the next match and N for the previous match.\nI use leader Ctrl f and leader Ctrl u the most. Combined with tmux-open mentioned above, I can do leader Ctrl f to find a file path, then Ctrl o to open it directly in Neovim. Super convenient workflow!\nHere&rsquo;s a demo showing this in action - using leader Ctrl f to search for file paths and leader Ctrl u for URLs, then Ctrl o to open them in Neovim:\nYour browser does not support the video tag. set -g @plugin &#39;schasse\/tmux-jump&#39; tmux-jump implements EasyMotion-like jumping. Press the keybind, type the highlighted letters, and jump directly to that location.\nPlugin Configuration For tmux-resurrect, I enable capturing pane contents so that the actual terminal content is saved:\nset -g @resurrect-capture-pane-contents &#39;on&#39; # set -g @continuum-restore &#39;on&#39; I have @continuum-restore commented out because I prefer to manually restore sessions rather than having it happen automatically.\nFor the floating terminal plugin:\nset -g @floax-bind &#39;-n M-f&#39; This binds Alt f to toggle the floating window.\nFor the Catppuccin theme, I customize the colors and window display:\nset -g @catppuccin_flavor &#34;frappe&#34; set -g @catppuccin_window_status_style &#34;basic&#34; set -g @catppuccin_window_current_text &#34; #{window_name}&#34; set -g @catppuccin_window_text &#34; #{window_name}&#34; set -g @catppuccin_window_current_number_color &#34;#{?window_zoomed_flag,#{@thm_yellow},#{@thm_mauve}}&#34; # typos: ignore set -g @catppuccin_window_number_color &#34;#{?window_zoomed_flag,#{@thm_yellow},#{@thm_overlay_2}}&#34; # typos: ignore I use the &ldquo;frappe&rdquo; flavor which is a nice dark theme. The window number changes color to yellow when zoomed (` m to maximize a pane), which gives good visual feedback.\nStatus Line I configure the status line to show only the application name and session name:\nset -g status-right-length 100 set -g status-left-length 100 set -g status-left &#34;&#34; set -g status-right &#34;#{E:@catppuccin_status_application}&#34; set -ag status-right &#34;#{E:@catppuccin_status_session}&#34; I keep it minimal because I don&rsquo;t need to see CPU, battery, or uptime in my tmux status bar.\nInstalling Plugins To actually use these plugins, you need to install TPM first:\ngit clone https:\/\/github.com\/tmux-plugins\/tpm ~\/.config\/tmux\/plugins\/tpm Then add this to the very bottom of your tmux.conf:\n# Initialize TPM (keep this line at the very bottom of tmux.conf) run &#39;~\/.config\/tmux\/plugins\/tpm\/tpm&#39; After reloading your config or restarting tmux, press leader Shift I (capital I) to install all the plugins. You should see them being downloaded and installed.\nUseful Aliases I use the following aliases to open the current pane in my editor.\nalias tpe=&#39;tmux capture-pane -Jp | $EDITOR&#39; alias tbe=&#39;tpe&#39; alias be=&#39;tpe&#39; Conclusion With tmux there are so many possibilities since the buffers are text and we can pipe them to the programs that we want. And with all the tools that exist such as fzf, zoxide, there are so many automations and workflow improvements that can be done. It was a good journey with tmux, and it is going to be. I&rsquo;m thinking of implementing myself some of the plugins to get a feel of how they are implemented and how they interact with tmux.\nA word of advice if you&rsquo;re just starting out: Don&rsquo;t feel overwhelmed by all the keybindings in this post. Start slow. I didn&rsquo;t learn everything at once either. Begin with the basics - splitting panes, creating windows, maybe copy mode. Add more keybindings as you need them. One day you&rsquo;ll catch yourself navigating tmux without thinking, and that&rsquo;s when you know it clicked.\nWith that, I hope at least you learn one new thing or copy on keybind or config that helps you. And for my friend, I think you have no excuses now \u00af\\(\u30c4)\/\u00af, kidding ahah.\nYou can find my complete tmux configuration and all my dotfiles at github.com\/jmarcelomb\/.dotfiles.\n-Marcelo\n","permalink":"https:\/\/marceloborges.dev\/posts\/4\/","summary":"My personal journey switching from Zellij to TMux, with configuration tips and workflow improvements","title":"My Journey from Zellij to TMux"},{"content":"Introduction My daily workflow involves connecting over SSH and using Neovim, but I struggled with clipboard integration. I wanted a way to copy text inside Neovim and sync it with my local clipboard seamlessly.\nHowever, copying and pasting between remote and local machines isn\u2019t always straightforward. Thankfully, OSC52 (Operating System Command 52) came to the rescue!\nIn this post, I&rsquo;ll introduce my copy script that leverages OSC52, explain how OSC52 works, and show how to integrate it into Neovim for a smooth experience.\nWhat is OSC52? OSC52 is a terminal escape sequence that allows you to copy text to your system clipboard from a remote session. It works by encoding the copied text in Base64 and sending it through a special terminal command, which supported terminal emulators (such as Alacritty, Ghostty, Kitty, WezTerm) detect and copy to your local clipboard.\nThis means that even when working on a remote machine over SSH, you can copy text directly to your local clipboard without needing tools like xclip, wl-copy, or xsel.\nThe Copy and Paste Scripts To make clipboard management effortless, I created two simple scripts: copy and paste. You can find them in my .dotfiles repository.\nCopy Script The copy script encodes the input in Base64 and sends it using OSC52. Here\u2019s a simplified working version:\n#!\/bin\/sh printf &#39;\\033]52;c;%s\\a&#39; &#34;$(cat | base64 | tr -d &#39;\\n&#39;)&#34; Usage:\necho &#34;Hello, clipboard!&#34; | copy This sends &ldquo;Hello, clipboard!&rdquo; to your system clipboard, even over SSH! \ud83d\ude0a!\nPaste Script I also attempted to create a paste script, but due to terminal limitations, I could only prompt for input when executing the paste script.\nExample use case:\n\u276f echo &#34;$(paste)&#34; | grep &#34;42&#34; \ud83d\udccb Enter input and press Enter (Ctrl+D to finish): We can insert text, or paste, in a multi-line format, and it will be print to stdout. 42 and when we want to exit, we just do Ctrl+D \u2705 Echoing the input to stdout! 42 and when we want to exit, we just do Integrating OSC52 with Neovim To enable seamless copying inside Neovim, we need to:\nSet the clipboard to unnamedplus Use an autocommand to copy text using OSC52 whenever we yank (yy, d, x, etc.) Configuration for Neovim Add the following to your init.lua or equivalent configuration file:\nvim.o.clipboard = &#34;unnamedplus&#34; vim.api.nvim_create_autocmd(&#34;TextYankPost&#34;, { callback = function() vim.highlight.on_yank() local copy_to_unnamedplus = require(&#34;vim.ui.clipboard.osc52&#34;).copy(&#34;+&#34;) copy_to_unnamedplus(vim.v.event.regcontents) local copy_to_unnamed = require(&#34;vim.ui.clipboard.osc52&#34;).copy(&#34;*&#34;) copy_to_unnamed(vim.v.event.regcontents) end, }) Explanation vim.o.clipboard = &quot;unnamedplus&quot; ensures that Neovim&rsquo;s default clipboard operations work with the system clipboard. TextYankPost event triggers whenever you copy (yank) text in Neovim. vim.highlight.on_yank() provides a visual cue that text was copied. vim.ui.clipboard.osc52.copy(&quot;+&quot;) and vim.ui.clipboard.osc52.copy(&quot;*&quot;) copy the yanked text to both + (system clipboard) and * (primary selection). With this setup, anything you yank inside Neovim is automatically available in your system clipboard, even over SSH!\nConclusion These simple scripts and Neovim integration massively improved my workflow, allowing seamless clipboard access over SSH.\nI hope they help you as well! \ud83d\ude0a Give them a try, and let me know how they work for you. \ud83d\ude80\n","permalink":"https:\/\/marceloborges.dev\/posts\/3\/","summary":"Learn how to seamlessly sync your remote clipboard with your local system clipboard using OSC52 escape sequences.","title":"Supercharging My Clipboard with OSC52 Escape Sequence"},{"content":"Introduction As a software engineer who spends countless hours typing, both at work and home, I began experiencing soreness in my fingers\u2014especially my pinky, from constantly pressing Ctrl and Shift. You know, the daily grind of copying code that&rsquo;s already been copied by someone else (just kidding\u2026 kind of).\nBeing a bit of a hypochondriac and wanting to safeguard my hands for years of coding ahead, I started researching ergonomic keyboards. That&rsquo;s when I stumbled upon the ZSA Moonlander Mark I:\nThis keyboard is something else! It&rsquo;s an ortholinear split keyboard, meaning it&rsquo;s split into two halves that can be angled to match the natural position of your wrists and arms. This design reduces strain and promotes healthier typing posture. Say goodbye to wrist pain, carpal tunnel syndrome, and repetitive strain injuries (RSIs)!\nStaggered vs. Ortholinear Keyboards Before diving deeper, let&rsquo;s clarify some terms. &ldquo;Staggered&rdquo; refers to the traditional key arrangement we&rsquo;re all familiar with\u2014vertically aligned keys in each column, but horizontally staggered rows. This layout dates back to typewriters, where staggered rows prevented the key arms from jamming when multiple keys were pressed simultaneously.\nOn the other hand, an &ldquo;ortholinear&rdquo; keyboard has keys aligned both vertically and horizontally, creating a perfect grid pattern. This layout, sometimes called a &ldquo;matrix&rdquo; layout, offers a more ergonomic typing experience, especially for those prone to strain from traditional keyboards.\nImage Credit: @HoffmanMyster\nIf you&rsquo;re interested in learning more about keyboard layouts, I recommend this YouTube video.\nDespite my excitement about the ZSA Moonlander Mark I, its price was a major deterrent\u2014especially for something I wasn&rsquo;t sure I&rsquo;d get used to. That&rsquo;s when I discovered the DIY mechanical keyboard community, where hardware and software are open-source, offering endless possibilities.\nMy Exploration of DIY Mechanical Keyboards I began with the Lily58 Pro:\nThis 6\u00d74+4 keys column-staggered split keyboard was a promising start. However, I soon learned about the power of customizable layers in keyboard layouts. Layers allow keys to perform different functions depending on the context, similar to how we use Shift or Alt keys. Intrigued by the potential to simplify my setup, I decided to explore smaller and more customizable options.\nThat&rsquo;s when I found the Corne Keyboard:\nThe Corne Keyboard is a split keyboard with 3x6 column-staggered keys and 3 thumb keys. In its latest iteration, you can even break the outer column, making it a 3x5 layout. But as I delved deeper into the rabbit hole of DIY keyboards, I discovered even more compact designs.\nEnter the Ferris Keyboard:\nNamed after the Rustlang mascot, Ferris, this keyboard instantly won my heart. However, one drawback was that the microcontroller was soldered in, limiting customization. Fortunately, David Barr built upon Ferris to create Sweep:\nThe Sweep is a version of the Ferris that uses a daughterboard like a Promicro, Elite-C, Bit-C, or Nice!Nano instead of onboard components. There are even more alternatives, such as the Urchin and Swoop, which add features like displays, encoders, and LEDs. I ultimately chose the Sweep Bling MX for its simplicity and flexibility, allowing for both wired and wireless use.\nThe Build Process: Hardware I started by ordering the PCBs from JLCPCB using the files provided here. Next, I sourced all the components from AliExpress as listed in the repository.\nOnce everything arrived:\nI began assembling the keyboard. Soldering was the next step:\nI quickly realized that the header I used was too small, so I replaced it. I also built two keyboards\u2014one with the MCU components facing down, and the other facing up, depending on how you solder the jumpers on the board. Always read the instructions carefully!\nThe Case For the case, I used Sweep Bling MX Sleeve Hard Case and made the following modifications:\nPCB Snap-In Feature: Redesigned the interior to securely snap the PCB into place, ensuring a snug fit without the need for additional adhesives or screws. Enhanced Power Button Space: Enlarged the power button area to allow for easier access and a more comfortable user experience. These enhancements improve the overall functionality and ease of use while maintaining the sleek design of the original model. You can see the end result here.\nThe Build Process: Software Flashing the firmware was more challenging than expected, mainly due to write protection on the SuperMini boards. I documented my solution in this blog post to save you the time I spent troubleshooting.\nFor firmware, you can choose between QMK and ZMK. QMK is great for wired keyboards with deep customization, while ZMK is optimized for wireless keyboards and power efficiency. Since I wanted a wireless build, I went with ZMK.\nI chose the Miryoku layout, which is ergonomic, minimal, orthogonal, and universal. I forked the Miryoku ZMK, added a build-sweep-nice_nano_v2.yml file, and created a custom_config.h. To enable custom combos, I added custom_combos.dtsi. The end result is the following layout:\nPortuguese Layout:\nYou can find a detailed explanation of how everything works here.\nSince I was already adapting to new keyboard layouts, I explored alternatives to QWERTY, such as Dvorak and Colemak. I ultimately chose Colemak DH because it minimizes finger movement, keeping most frequently used keys on the home row.\nThe heatmap above illustrates how the Colemak layout concentrates the most common keys under your fingertips, significantly reducing strain. Compared to Dvorak and QWERTY, Colemak offers a more efficient typing experience.\nThe Final Result In the end, I built two keyboards:\nRight now, I&rsquo;m daily carrying the first one and using it for work and personal tasks. It was very hard in the beginning. I went from writing over 100 words per minute to what felt like 2. I got fatigued from writing because I needed to think about where the keys were and had to look. I started using the mouse in the middle to avoid shoulder overloading and had a normal keyboard in front of the display that I used when I wanted to write fast.\nI want to go without the labels on the keys eventually, but first, I need to train my muscle memory. Getting the hang of layers was fast. The problem is the new layout that I have chosen. However, I can see that I decreased my finger movements significantly, and having Shift, Ctrl, Alt, and Super in the home row is very nice.\nAfter three weeks, I&rsquo;m now writing 30 words per minute and only using this keyboard. Last week, I started putting the normal keyboard away so I don&rsquo;t have the instinct to use it. I don&rsquo;t feel as tired as I did in the beginning, and I&rsquo;m starting to develop muscle memory for this keyboard, though I do make some typos due to QWERTY muscle memory. When I use a QWERTY keyboard, the 20 years of using a keyboard kick in, and it still feels natural.\nThe Sweep Bling MX turned out to be a fun, affordable, and educational project. Not only did I end up with a keyboard tailored to my ergonomic needs, but I also learned a lot about mechanical keyboards, electronics, and firmware.\nIf you&rsquo;re considering a DIY keyboard build, I highly recommend diving into the world of open-source keyboards. The community is incredibly supportive, and the possibilities are endless!\nConclusion In this journey, I learned that ergonomic keyboards are not just for enthusiasts\u2014they can make a significant difference in your typing comfort and long-term health. If you&rsquo;re someone who spends a lot of time at the keyboard, it&rsquo;s worth exploring the world of ergonomic and custom keyboards. Your hands will thank you!\nFeel free to ask anything, see you in the next post! jmmb\n","permalink":"https:\/\/marceloborges.dev\/posts\/2\/","summary":"Discover how I ventured into the world of DIY mechanical keyboards to create a custom ergonomic keyboard tailored to my needs.","title":"My Journey to Building the Sweep Ergonomic Keyboard"},{"content":"My Journey into the World of DIY Mechanical Keyboards I recently fell into the rabbit hole of DIY mechanical keyboards. It all started when I began experiencing pain in my hands, prompting me to search for ergonomic keyboards. That\u2019s when I discovered the ZSA Moonlander Mark 1. However, its high price led me to explore cheaper alternatives, and I stumbled upon the fascinating world of DIY keyboards. After considering options like the Lily58 Pro, Corne, and Ferris, I ultimately chose the 34-key Sweep keyboard.\nMost build examples I found online used the nice!nano v2. In an effort to save money, I opted for a compatible clone from AliExpress, the Super Mini NRF52840. Unfortunately, it didn\u2019t come with the nice!nano bootloader and had Access Port Protection (APP) enabled, resulting in the error message: Error: Could not find MEM-AP to control the core when attempting to write into memory.\nTL;DR To solve this issue, use the following commands:\nopenocd -f interface\/raspberrypi-native.cfg -f target\/nrf52.cfg -c &#34;gdb_flash_program enable&#34; -c &#34;gdb_breakpoint_override hard&#34; -c &#34;init&#34; -c &#34;reset halt&#34; -c &#34;nrf52_recover&#34; or\nopenocd -f interface\/raspberrypi-native.cfg -f target\/nrf52.cfg -c &#34;gdb_flash_program enable&#34; And in another shell:\ntelnet localhost 4444 nrf52.dap apreg 0x04 0x01 Configuration for interface\/raspberrypi-native.cfg With the following interface\/raspberrypi-native.cfg:\n# SPDX-License-Identifier: GPL-2.0-or-later adapter driver bcm2835gpio bcm2835gpio peripheral_base 0x20000000 bcm2835gpio speed_coeffs 113714 28 transport select swd # Each of the SWD lines need a gpio number set: swclk swdio # Header pin numbers: 23 22 adapter gpio swclk -chip 0 11 adapter gpio swdio -chip 0 25 # If you define trst or srst, use appropriate reset_config # Header pin numbers: TRST - 26, SRST - 18 # adapter gpio trst -chip 0 7 # reset_config trst_only adapter gpio srst -chip 0 24 reset_config srst_only srst_push_pull # or if you have both connected, # reset_config trst_and_srst srst_push_pull Raspberry Pi OpenOCD installation I installed Raspberry Pi OS using Raspberry Pi Imager, configured it, and connected via SSH. To install OpenOCD on it, I executed the following commands:\ncd ~ sudo apt-get update sudo apt-get install git autoconf libtool make pkg-config libusb-1.0-0 libusb-1.0-0-dev git clone http:\/\/openocd.zylin.com\/openocd cd openocd .\/bootstrap .\/configure --enable-sysfsgpio --enable-bcm2835gpio make sudo make install Connections I soldered the back SWCLK and SWDIO to GPIO 11 and 25, respectively, connected a GND, and the NRF board reset pin to GPIO 24, as shown in the figure below.\nUnderstanding Access Port Protection The CTRL-AP (Control Access Port) allows control of the device even if other access ports are disabled by access port protection. If APP is enabled in the APPROTECT register (0x10001208) of the UICR, debugger access to CPU registers and memory is blocked.\nTo check the APPROTECTSTATUS register (0x00C) of the CTRL-AP, execute the following in OpenOCD:\nnrf52.dap apreg 1 0x0c If the least significant bit is \u20180\u2019, access port protection is enabled. If the least significant bit is \u20181\u2019, access port protection is not enabled. Proceed to halt the CPU. To unlock a device with APP enabled, issue an ERASEALL command through the CTRL-AP, followed by a reset. This erases the entire code flash, UICR area, and RAM. Perform this with:\nopenocd -f interface\/raspberrypi-native.cfg -f target\/nrf52.cfg -c &#34;gdb_flash_program enable&#34; -c &#34;gdb_breakpoint_override hard&#34; -c &#34;init&#34; -c &#34;reset halt&#34; -c &#34;nrf52_recover&#34; or\nopenocd -f interface\/raspberrypi-native.cfg -f target\/nrf52.cfg -c &#34;gdb_flash_program enable&#34; And in another shell:\ntelnet localhost 4444 nrf52.dap apreg 0x04 0x01 Re-reading the APPROTECTSTATUS register (0x00C) should now return 1. After unlocking, I flashed the bootloader and began training my muscle memory to use this new layout. I\u2019m already using the new keyboard as I write this post! \ud83d\ude02\nThe command that I used to flash the bootloader, was:\n$ openocd -f interface\/raspberrypi-native.cfg -f target\/nrf52.cfg -c &#34;gdb_flash_program enable&#34; -c &#34;gdb_breakpoint_override hard&#34; -c &#34;init&#34; -c &#34;reset halt&#34; -c &#34;flash write_image erase .\/nice_nano_bootloader-0.6.0_s140_6.1.1.hex&#34; References Raspberry Pi and OpenOCD \u2013 Lean2 Nordic Semiconductor Infocenter Removing flash protection from the nRF52 using JLink + OpenOCD Troubleshooting - nice!nano I hope this helps someone save time figuring it all out. Feel free to ask if you need further assistance! See you in the next post!\njmmb\n","permalink":"https:\/\/marceloborges.dev\/posts\/1\/","summary":"Learn how to unlock and flash your nRF52 microcontroller using a Raspberry Pi Zero and OpenOCD. This guide covers the installation, configuration, and necessary commands to remove flash protection.","title":"How to remove flash protection from the nRF52 using Raspberry Pi Zero + OpenOCD"},{"content":"While browsing Aliexpress, I stumbled upon some intriguing LoRa modules and decided to give them a try. I ended up purchasing three LoRa LLCC68 modules.\nI wanted to create a low-power application utilizing LoRa technology. My background includes working on an NB-IoT project for my master&rsquo;s thesis, so I was eager to explore another low-power wireless technology.\nAs with any new technology, I began with a simple Hello World application. However, when I attempted to mount the ESP32-C3 with the LoRa modules on a breadboard, I encountered a problem: the LoRa module pins had a 2 mm spacing, making them incompatible with a standard breadboard. To solve this, I designed an adapter board in KiCad that converts the pin spacing to be breadboard-compatible:\nWith the LoRa module now breadboard-compatible, I set up two nodes using ESP32-C3 boards: one as a receiver and the other as a transmitter, as shown below:\nFor this experiment, I used the basic example from the nopnop2002\/esp-idf-sx126x repository. The wiring was done according to the following table:\nSX126X\/LLCC68 ESP32 ESP32-S2\/S3 ESP32-C2\/C3\/C6 BUSY &ndash; GPIO17 GPIO39 GPIO3 RST &ndash; GPIO16 GPIO38 GPIO2 TXEN &ndash; N\/C N\/C N\/C RXEN &ndash; N\/C N\/C N\/C MISO &ndash; GPIO19 GPIO37 GPIO4 SCK &ndash; GPIO18 GPIO36 GPIO5 MOSI &ndash; GPIO23 GPIO35 GPIO6 NSS &ndash; GPIO15 GPIO34 GPIO7 GND &ndash; GND GND GND VCC &ndash; 3.3V 3.3V 3.3V Using the ESP-IDF to build the example, I flashed one ESP32 as the transmitter and the other as the receiver. The monitor output is shown below:\nThe poor reception and signal quality were due to the lack of an antenna; the PCB trace was functioning as a antenna. Despite this, it was a successful first interaction with LoRa technology. It demonstrated that working with LoRa isn\u2019t as daunting as it might seem and inspired several potential applications, such as creating open\/close end-device sensors for my house gates.\nThis initial experiment has sparked numerous ideas for future projects, and I\u2019m excited to explore more advanced LoRa applications. Thank you for joining me on this journey into LoRa technology. Stay tuned for more updates and projects!\nHappy experimenting!\njmmb\n","permalink":"https:\/\/marceloborges.dev\/posts\/0\/","summary":"Discover my initial journey into LoRa technology using the LLCC68 module. This post covers my setup process, the creation of a breadboard adapter, and the results of my first transmitter and receiver test using ESP32-C3 boards.","title":"Diving into LoRa: Initial Experiments with LLCC68 and ESP32"},{"content":"\nThis is the content of my blog.\nBlog: https:\/\/jmarcelomb.github.io\/blog\/.\nBlog repo: https:\/\/github.com\/jmarcelomb\/blog\n","permalink":"https:\/\/marceloborges.dev\/readme\/","summary":"<p><a href=\"https:\/\/github.com\/jmarcelomb\/blog-content\/actions\/workflows\/update_blog.yml\"><img alt=\"CI\" loading=\"lazy\" src=\"https:\/\/github.com\/jmarcelomb\/blog-content\/actions\/workflows\/update_blog.yml\/badge.svg\"><\/a><\/p>\n<p>This is the content of my <strong><a href=\"https:\/\/jmarcelomb.github.io\/blog\/\">blog<\/a><\/strong>.<\/p>\n<p>Blog: <strong><a href=\"https:\/\/jmarcelomb.github.io\/blog\/\">https:\/\/jmarcelomb.github.io\/blog\/<\/a><\/strong>.<\/p>\n<p>Blog repo: <strong><a href=\"https:\/\/github.com\/jmarcelomb\/blog\">https:\/\/github.com\/jmarcelomb\/blog<\/a><\/strong><\/p>","title":""}]