├── .editorconfig ├── LICENSE ├── README.md └── zellij-smart-sessionizer /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 4 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Popov Dmitriy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Zellij Smart Sessionizer

2 | 3 |
4 | Zellij sessionizer with layout selection capability 5 |
6 | 7 |
8 | Instant Zellij session startup powered by Zoxide and fuzzy finder 9 |
10 | 11 | [![zellij smart sessionizer demo](https://asciinema.org/a/617601.svg)](https://asciinema.org/a/617601) 12 | 13 | ## Table of Contents 14 | 15 | - [Key Features](#key-features) 16 | - [Installation](#installation) 17 | - [Usage](#usage) 18 | - [Usage inside Neovim](#usage-inside-neovim) 19 | - [Quick Tips](#quick-tips) 20 | - [Inspirations](#inspirations) 21 | 22 | ## Key Features 23 | 24 | - **Easy to use:** Run `zs` or `Ctrl+f` in your shell select your project from [`Zoxide`](https://github.com/ajeetdsouza/zoxide) list using [`fzf`](https://github.com/junegunn/fzf) or [`skim`](https://github.com/lotabout/skim), select preferable layout; 25 | - **Usage inside zellij session:** It will create a new tab with the selected layout; 26 | - **Attach to the session:** Will not ask for layout when the session was previously created; 27 | - **Layout config preview:** Layout config preview will be shown using [`bat`](https://github.com/sharkdp/bat) if installed or `cat` as a fallback 28 | 29 | ## Installation 30 | 31 | 1. Install all dependencies 32 | 33 | - [Zoxide](https://github.com/ajeetdsouza/zoxide) 34 | - [skim](https://github.com/lotabout/skim) or [fzf](https://github.com/junegunn/fzf) - fuzzy finders, `skim` has higher priority 35 | - [bat](https://github.com/junegunn/fzf) - optional dependency 36 | 37 | 2. Clone the repo 38 | 39 | ``` 40 | git clone https://github.com/demestoss/zellij-smart-sessionizer 41 | cd zellij-smart-sessionizer 42 | ``` 43 | 44 | 3. Place [zellij-smart-sessionizer](https://github.com/demestoss/zellij-smart-sessionizer/blob/master/zellij-smart-sessionizer) script in your `PATH`. One of the ways: 45 | 46 | ``` 47 | sudo chmod +x ./zellij-smart-sessionizer 48 | sudo ln -s $(echo "$(pwd)/zellij-smart-sessionizer") /usr/bin/zellij-smart-sessionizer 49 | ``` 50 | 51 | 4. Populate your `Zoxide` database by simply going into the directories that you want to start the session from. Check [`Zoxide`](https://github.com/ajeetdsouza/zoxide) docs for more info. 52 | 53 | 5. **(Recommended)** I like to create alias for the script to have an ability to easily execute it. Place it into your shell's `.rc` file: 54 | 55 | ```sh 56 | alias zs="zellij-smart-sessionizer" 57 | ``` 58 | 59 | 6. **(Optional)** Create an alias to call this script in your shells `.rs` config 60 | 61 | ```sh 62 | bindkey -s ^f "zellij-smart-sessionizer^M" 63 | ``` 64 | 65 | ## Usage 66 | 67 | You just need to type this command in your shell to start a new session 68 | 69 | ```sh 70 | zellij-smart-sessionizer 71 | 72 | # or just this command if you have done (5) step of installation process 73 | 74 | zs 75 | ``` 76 | 77 | Or `Ctrl+f` if you've set up an optional keybinding step 78 | 79 | Next will be provided different types of scenarios of the script execution. 80 | 81 | ### Outside of Zellij (session doesn't exist) 82 | 83 | You will be offered paths from `Zoxide`. After selection, it will give you a layout selection for the session if your layout directory is not empty. It uses the `LAYOUT DIR` path provided to `Zellij` setup. 84 | 85 | After that, it will open a session with a zoxide path's `basename` and selected layout. 86 | 87 | ### Outside of Zellij (session exists) 88 | 89 | You will be offered paths from `Zoxide`. After selecting a path, the layout step will be skipped and you will be attached to `Zellij` session. 90 | 91 | ### Inside Zellij 92 | 93 | If you run this script inside `Zellij` you will be offered the same selection steps. But after selection, it will open a new tab with the selected session `basename` and Layout 94 | 95 | _Note_: Not all layouts will be provided in this case, It's kinda smart and will not provide layouts that contain `tabs` options inside it, because you cannot create tabs inside the tab. 96 | 97 | ## Usage inside Neovim 98 | 99 | I like to have this keymap in my Neovim config to run sessionizer inside floating pane: 100 | 101 | ```lua 102 | vim.keymap.set("n", "", ":silent !zellij action new-pane -f -c -- zellij-smart-sessionizer", { silent = true }) 103 | ``` 104 | 105 | By pressing `Ctrl+f` inside Neovim you will open floting window with sessionizer script 106 | 107 | ## Passing session name argument 108 | 109 | Also, you can provide optional arguments to the script 110 | 111 | ```sh 112 | zellij-smart-sessionizer session-name 113 | 114 | # or 115 | 116 | zs session-name 117 | ``` 118 | 119 | It will pick up this argument and will init session with this name instead of the path's `basename`. It's very useful if you want to have several different sessions that were initialized from the same directory 120 | 121 | ## Quick Tips 122 | 123 | I was always strugling with removing directories from Zoxide DB, so I finally found the right solution and want to share it here :) 124 | 125 | If you want to remove some paths from your `Zoxide` DB you can use this simple command: 126 | 127 | ```sh 128 | zoxide remove $(zoxide query -l | fzf -m) 129 | ``` 130 | 131 | Select options that you want to remove by pressing `Tab` and they will be deleted from DB 132 | 133 | ## Inspirations 134 | 135 | Special thanks to this repos: 136 | 137 | - [zellij-sessionizer](https://github.com/silicakes/zellij-sessionizer/tree/main) 138 | - [t](https://github.com/joshmedeski/t-smart-tmux-session-manager) 139 | 140 | ## License 141 | 142 | [MIT](https://tldrlegal.com/license/mit-license) 143 | -------------------------------------------------------------------------------- /zellij-smart-sessionizer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$(command -v zellij)" = "" ]; then 4 | echo "Zellij is not installed" 5 | exit 1 6 | fi 7 | 8 | if [ "$(command -v zoxide)" = "" ]; then 9 | echo "Zoxide is not installed" 10 | exit 1 11 | fi 12 | 13 | get_fuzzy_cmd() { 14 | if [ -x "$(command -v sk)" ]; then 15 | echo "sk" 16 | else 17 | echo "fzf" 18 | fi 19 | } 20 | 21 | get_preview_cmd() { 22 | if [ -x "$(command -v bat)" ]; then 23 | echo "bat --color=always --style=numbers --wrap=auto" 24 | else 25 | echo "cat" 26 | fi 27 | } 28 | 29 | home_replacer() { 30 | HOME_REPLACER="" # default to a noop 31 | echo "$HOME" | grep -E "^[a-zA-Z0-9\-_/.@]+$" &>/dev/null # chars safe to use in sed 32 | HOME_SED_SAFE=$? 33 | if [ $HOME_SED_SAFE -eq 0 ]; then # $HOME should be safe to use in sed 34 | HOME_REPLACER="s|^$HOME/|~/|" 35 | fi 36 | echo "$HOME_REPLACER" 37 | } 38 | 39 | transform_home_path() { 40 | HOME_SED_SAFE=$? 41 | if [ $HOME_SED_SAFE -eq 0 ]; then 42 | echo "$1" | sed -e "s|^~/|$HOME/|" 43 | else 44 | echo "$1" 45 | fi 46 | } 47 | 48 | select_session_dir() { 49 | current_sessions=$1 50 | zoxide_list=$(zoxide query -l) 51 | 52 | if [ "$current_sessions" = "" ]; then 53 | list="$zoxide_list" 54 | else 55 | list=$(echo "$current_sessions $zoxide_list" | tr ' ' '\n') 56 | fi 57 | 58 | header="Select session:" 59 | project_dir=$(echo "$list" | sed -e "$(home_replacer)" | $(get_fuzzy_cmd) --reverse --header "$header") 60 | 61 | if [ "$project_dir" = "" ]; then 62 | echo "" 63 | exit 64 | fi 65 | 66 | transform_home_path "$project_dir" 67 | } 68 | 69 | select_tab_dir() { 70 | header="Select tab directory:" 71 | project_dir=$(zoxide query -l | sed -e "$(home_replacer)" | $(get_fuzzy_cmd) --reverse --header "$header") 72 | 73 | if [ "$project_dir" = "" ]; then 74 | echo "" 75 | exit 76 | fi 77 | 78 | transform_home_path "$project_dir" 79 | } 80 | 81 | get_session_name() { 82 | project_dir=$1 83 | provided_session_name=$2 84 | 85 | directory=$(basename "$project_dir") 86 | session_name="" 87 | if [ "$provided_session_name" = "" ]; then 88 | session_name=$(echo "$directory" | tr ' .:' '_') 89 | else 90 | session_name="$provided_session_name" 91 | fi 92 | echo "$session_name" 93 | } 94 | 95 | get_layouts_list() { 96 | layout_dir=$(zellij setup --check | grep "LAYOUT DIR" | grep -o '".*"' | tr -d '"') 97 | 98 | if [ "$layout_dir" = "" ]; then 99 | echo "" 100 | exit 101 | fi 102 | 103 | layouts=$(find "$layout_dir" | tail -n+2) 104 | echo "$layouts" 105 | } 106 | 107 | select_layout() { 108 | layouts=$1 109 | session_name=$2 110 | 111 | header="Select layout for '$session_name'" 112 | layout_path=$( 113 | { 114 | echo "$layouts" | 115 | awk '{ 116 | full = $0 117 | bn = full 118 | sub(/^.*\//, "", bn) 119 | sub(/\.[^.]+$/, "", bn) 120 | print full "\t" bn 121 | }' 122 | echo -e "default\tdefault" 123 | } | $(get_fuzzy_cmd) --with-nth=2 --reverse --header "$header" --tabstop=4 --ansi --preview="$(get_preview_cmd) {1}" | cut -f1 124 | ) 125 | 126 | if [ "$layout_path" = "" ]; then 127 | echo "" 128 | exit 129 | fi 130 | 131 | echo "$layout_path" 132 | } 133 | 134 | get_session_layout() { 135 | session_name=$1 136 | layouts=$(get_layouts_list) 137 | if [ "$layouts" = "" ]; then 138 | echo "default" 139 | exit 140 | fi 141 | select_layout "$layouts" "$session_name" 142 | } 143 | 144 | get_tab_layout() { 145 | session_name=$1 146 | layouts=$(get_layouts_list) 147 | if [ "$layouts" = "" ]; then 148 | echo "default" 149 | exit 150 | fi 151 | 152 | layouts_for_tabs="" 153 | for layout in $layouts; do 154 | # filtering layouts with "tab" keyword to avoid having tabs in tabs 155 | # Maybe there is a better way to do this 156 | has_tab=$(grep "tab " "$layout") 157 | if [ "$has_tab" = "" ]; then 158 | layouts_for_tabs="$layouts_for_tabs $layout" 159 | fi 160 | done 161 | 162 | layouts_for_tabs=$(echo "$layouts_for_tabs" | tr ' ' '\n') 163 | 164 | select_layout "$layouts_for_tabs" "$session_name" 165 | } 166 | 167 | # If we outside of Zellij initialize session and attach to it, or just attach to it 168 | outside_zellij() { 169 | sessions_list=$(zellij list-sessions -s) 170 | 171 | project_dir=$(select_session_dir "$sessions_list") 172 | if [ "$project_dir" = "" ]; then 173 | exit 0 174 | fi 175 | 176 | session_name=$(get_session_name "$project_dir" "$1") 177 | session=$(echo "$sessions_list" | grep "^$session_name$") 178 | 179 | # If no session, create with the default layout 180 | if [ "$session" = "" ]; then 181 | layout=$(get_session_layout "$session_name") 182 | 183 | if [ "$layout" = "" ]; then 184 | exit 0 185 | fi 186 | 187 | zellij --session "$session_name" --new-session-with-layout "$layout" options --default-cwd "$project_dir" 188 | exit 0 189 | fi 190 | 191 | zellij attach "$session_name" 192 | exit 0 193 | 194 | } 195 | 196 | # If we inside of Zellij create new tab with layout 197 | inside_zellij() { 198 | project_dir=$(select_tab_dir) 199 | if [ "$project_dir" = "" ]; then 200 | exit 0 201 | fi 202 | 203 | tab_name=$(get_session_name "$project_dir" "$1") 204 | 205 | layout=$(get_tab_layout "$tab_name") 206 | if [ "$layout" = "" ]; then 207 | exit 0 208 | fi 209 | 210 | zellij action new-tab --layout "$layout" --name "$tab_name" --cwd "$project_dir" 211 | zellij action go-to-tab-name "$tab_name" 212 | } 213 | 214 | main() { 215 | # Outside Zellij session 216 | if [[ -z $ZELLIJ ]]; then 217 | outside_zellij "$@" 218 | else 219 | inside_zellij "$@" 220 | fi 221 | } 222 | 223 | main "$@" 224 | --------------------------------------------------------------------------------