├── .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 | [](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 |
--------------------------------------------------------------------------------