├── README.md ├── autoload ├── i3vimfocus.py └── i3vimfocus.vim ├── i3-vim-focus ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs └── plugin └── i3vimfocus.vim /README.md: -------------------------------------------------------------------------------- 1 | Vim plugin for seamless i3/vim navigation 2 | ========================================= 3 | 4 | Allows i3 direction keys to control vim splits and i3 windows seamlessly 5 | 6 | The `i3-vim-focus` folder has a Rust program that should be installed on the 7 | user's path. It can be build with `cargo build --release`. 8 | 9 | No changes are necessary to the .vimrc. 10 | 11 | Every instance of vim must be launched with a `--servername` argument that 12 | starts with `VIM:` and must be unique. One way to do achieve this is by using an 13 | alias for vim, like so: 14 | 15 | ```bash 16 | alias vim='/usr/local/bin/vim --servername "VIM:$RANDOM"' 17 | ``` 18 | 19 | The i3 config needs to be updated with the following bindings. 20 | 21 | ``` 22 | bindsym --release $mod+h exec --no-startup-id "i3-vim-focus left" 23 | bindsym --release $mod+j exec --no-startup-id "i3-vim-focus down" 24 | bindsym --release $mod+k exec --no-startup-id "i3-vim-focus up" 25 | bindsym --release $mod+l exec --no-startup-id "i3-vim-focus right" 26 | ``` 27 | 28 | Finally, this project needs to be installed as a vim plugin using 29 | something like pathogen. 30 | -------------------------------------------------------------------------------- /autoload/i3vimfocus.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import vim 3 | 4 | def PythonExecSubprocess(): 5 | name = vim.eval('a:name') 6 | args = vim.eval('a:args') 7 | name_args = [name] + args 8 | subprocess.call(name_args) 9 | -------------------------------------------------------------------------------- /autoload/i3vimfocus.vim: -------------------------------------------------------------------------------- 1 | let s:plugin_path = escape(expand(':p:h'), '\') 2 | 3 | if has('python') 4 | exe 'pyfile ' . escape(s:plugin_path, ' ') . '/i3vimfocus.py' 5 | elseif has('python3') 6 | exe 'py3file ' . escape(s:plugin_path, ' ') . '/i3vimfocus.py' 7 | else 8 | echom "Your vim installation does not support +python or +python3." 9 | endif 10 | 11 | function! i3vimfocus#PythonExecProcess(name, args) 12 | if has('python') 13 | python PythonExecSubprocess() 14 | elseif has('python3') 15 | python3 PythonExecSubprocess() 16 | else 17 | echom "Your vim installation does not support +python or +python3." 18 | endif 19 | endfunction 20 | -------------------------------------------------------------------------------- /i3-vim-focus/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /i3-vim-focus/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.12.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "bitflags" 27 | version = "1.3.2" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 30 | 31 | [[package]] 32 | name = "byteorder" 33 | version = "1.4.3" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 36 | 37 | [[package]] 38 | name = "cfg-if" 39 | version = "1.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 42 | 43 | [[package]] 44 | name = "clap" 45 | version = "2.34.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 48 | dependencies = [ 49 | "ansi_term", 50 | "atty", 51 | "bitflags", 52 | "strsim", 53 | "textwrap", 54 | "unicode-width", 55 | "vec_map", 56 | ] 57 | 58 | [[package]] 59 | name = "error-chain" 60 | version = "0.7.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "318cb3c71ee4cdea69fdc9e15c173b245ed6063e1709029e8fd32525a881120f" 63 | 64 | [[package]] 65 | name = "foreign-types" 66 | version = "0.1.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "6d303bf9c8e20c02242ffe4217add748a5b2424cf5955bc399545540fc751c21" 69 | 70 | [[package]] 71 | name = "heck" 72 | version = "0.3.3" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 75 | dependencies = [ 76 | "unicode-segmentation", 77 | ] 78 | 79 | [[package]] 80 | name = "hermit-abi" 81 | version = "0.1.19" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 84 | dependencies = [ 85 | "libc", 86 | ] 87 | 88 | [[package]] 89 | name = "i3-vim-focus" 90 | version = "0.1.0" 91 | dependencies = [ 92 | "i3ipc", 93 | "jwilm-xdo", 94 | "structopt", 95 | ] 96 | 97 | [[package]] 98 | name = "i3ipc" 99 | version = "0.10.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "63f3dac00c473fae88cb3114f35312204469a32ffb20874264a5214d6c8c927e" 102 | dependencies = [ 103 | "byteorder", 104 | "log", 105 | "serde", 106 | "serde_json", 107 | ] 108 | 109 | [[package]] 110 | name = "itoa" 111 | version = "0.4.8" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 114 | 115 | [[package]] 116 | name = "jwilm-xdo" 117 | version = "0.1.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "ad161810191da91d9d9976973f497095ca3fa489bd066f1f12c499ba544f8756" 120 | dependencies = [ 121 | "foreign-types", 122 | "libc", 123 | "libxdo-sys", 124 | "x11", 125 | ] 126 | 127 | [[package]] 128 | name = "lazy_static" 129 | version = "1.4.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 132 | 133 | [[package]] 134 | name = "libc" 135 | version = "0.2.112" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 138 | 139 | [[package]] 140 | name = "libxdo-sys" 141 | version = "0.11.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" 144 | dependencies = [ 145 | "libc", 146 | "x11", 147 | ] 148 | 149 | [[package]] 150 | name = "log" 151 | version = "0.4.14" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 154 | dependencies = [ 155 | "cfg-if", 156 | ] 157 | 158 | [[package]] 159 | name = "metadeps" 160 | version = "1.1.1" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "829fffe7ea1d747e23f64be972991bc516b2f1ac2ae4a3b33d8bea150c410151" 163 | dependencies = [ 164 | "error-chain", 165 | "pkg-config", 166 | "toml", 167 | ] 168 | 169 | [[package]] 170 | name = "pkg-config" 171 | version = "0.3.9" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 174 | 175 | [[package]] 176 | name = "proc-macro-error" 177 | version = "1.0.4" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 180 | dependencies = [ 181 | "proc-macro-error-attr", 182 | "proc-macro2", 183 | "quote", 184 | "syn", 185 | "version_check", 186 | ] 187 | 188 | [[package]] 189 | name = "proc-macro-error-attr" 190 | version = "1.0.4" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 193 | dependencies = [ 194 | "proc-macro2", 195 | "quote", 196 | "version_check", 197 | ] 198 | 199 | [[package]] 200 | name = "proc-macro2" 201 | version = "1.0.34" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" 204 | dependencies = [ 205 | "unicode-xid", 206 | ] 207 | 208 | [[package]] 209 | name = "quote" 210 | version = "1.0.10" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 213 | dependencies = [ 214 | "proc-macro2", 215 | ] 216 | 217 | [[package]] 218 | name = "ryu" 219 | version = "1.0.5" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 222 | 223 | [[package]] 224 | name = "serde" 225 | version = "1.0.130" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 228 | 229 | [[package]] 230 | name = "serde_json" 231 | version = "1.0.67" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" 234 | dependencies = [ 235 | "itoa", 236 | "ryu", 237 | "serde", 238 | ] 239 | 240 | [[package]] 241 | name = "strsim" 242 | version = "0.8.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 245 | 246 | [[package]] 247 | name = "structopt" 248 | version = "0.3.25" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" 251 | dependencies = [ 252 | "clap", 253 | "lazy_static", 254 | "structopt-derive", 255 | ] 256 | 257 | [[package]] 258 | name = "structopt-derive" 259 | version = "0.4.18" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 262 | dependencies = [ 263 | "heck", 264 | "proc-macro-error", 265 | "proc-macro2", 266 | "quote", 267 | "syn", 268 | ] 269 | 270 | [[package]] 271 | name = "syn" 272 | version = "1.0.82" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 275 | dependencies = [ 276 | "proc-macro2", 277 | "quote", 278 | "unicode-xid", 279 | ] 280 | 281 | [[package]] 282 | name = "textwrap" 283 | version = "0.11.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 286 | dependencies = [ 287 | "unicode-width", 288 | ] 289 | 290 | [[package]] 291 | name = "toml" 292 | version = "0.2.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" 295 | 296 | [[package]] 297 | name = "unicode-segmentation" 298 | version = "1.8.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 301 | 302 | [[package]] 303 | name = "unicode-width" 304 | version = "0.1.9" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 307 | 308 | [[package]] 309 | name = "unicode-xid" 310 | version = "0.2.2" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 313 | 314 | [[package]] 315 | name = "vec_map" 316 | version = "0.8.2" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 319 | 320 | [[package]] 321 | name = "version_check" 322 | version = "0.9.3" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 325 | 326 | [[package]] 327 | name = "winapi" 328 | version = "0.3.9" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 331 | dependencies = [ 332 | "winapi-i686-pc-windows-gnu", 333 | "winapi-x86_64-pc-windows-gnu", 334 | ] 335 | 336 | [[package]] 337 | name = "winapi-i686-pc-windows-gnu" 338 | version = "0.4.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 341 | 342 | [[package]] 343 | name = "winapi-x86_64-pc-windows-gnu" 344 | version = "0.4.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 347 | 348 | [[package]] 349 | name = "x11" 350 | version = "2.12.1" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "124eb405bf0262a54e1a982d4ffe4cd1c24261bdb306e49996e2ce7d492284a8" 353 | dependencies = [ 354 | "libc", 355 | "metadeps", 356 | ] 357 | -------------------------------------------------------------------------------- /i3-vim-focus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "i3-vim-focus" 3 | version = "0.2.0" 4 | edition = "2018" 5 | authors = ["Joe Wilm "] 6 | 7 | [dependencies] 8 | jwilm-xdo = { version = "0.1.1" } 9 | i3ipc = "0.10" 10 | structopt = "0.3" 11 | -------------------------------------------------------------------------------- /i3-vim-focus/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A rust implementation of i3_vim_focus 2 | //! 3 | //! Original version from 4 | //! https://faq.i3wm.org/question/3042/change-the-focus-of-windows-within-vim-and-i3-with-the-same-keystroke/ 5 | //! 6 | //! Usage: 7 | //! i3-vim-focus [left|right|up|down] 8 | //! 9 | //! Requires that libxdo is installed 10 | 11 | extern crate i3ipc; 12 | extern crate jwilm_xdo as xdo; 13 | 14 | use std::env; 15 | use std::path::PathBuf; 16 | use std::process::Command; 17 | use structopt::StructOpt; 18 | 19 | fn resolve_vim() -> Option { 20 | let path = env::var("PATH").expect("PATH environment variable not set"); 21 | path.split(":") 22 | .map(|path| PathBuf::from(path).join("vim")) 23 | .filter(|p| p.exists()) 24 | .next() 25 | } 26 | 27 | #[derive(Debug, StructOpt)] 28 | struct Options { 29 | #[structopt(long, help = "specify a vim binary instead of searching path")] 30 | vim: Option, 31 | 32 | #[structopt(subcommand)] 33 | direction: Direction, 34 | } 35 | 36 | #[derive(Debug, StructOpt, Copy, Clone)] 37 | enum Direction { 38 | Up, 39 | Left, 40 | Down, 41 | Right, 42 | } 43 | 44 | impl Direction { 45 | pub fn to_vim_direction(&self) -> &'static str { 46 | match *self { 47 | Direction::Up => "k", 48 | Direction::Down => "j", 49 | Direction::Left => "h", 50 | Direction::Right => "l", 51 | } 52 | } 53 | 54 | pub fn to_i3_direction(&self) -> &'static str { 55 | match *self { 56 | Direction::Up => "up", 57 | Direction::Down => "down", 58 | Direction::Left => "left", 59 | Direction::Right => "right", 60 | } 61 | } 62 | } 63 | 64 | fn main() { 65 | let options = Options::from_args(); 66 | 67 | let vim = options 68 | .vim 69 | .or_else(resolve_vim) 70 | .expect("must set --vim or have vim on path"); 71 | let direction = options.direction; 72 | 73 | let xdo = xdo::Xdo::new().expect("create xdo"); 74 | let window = xdo.get_active_window(); 75 | 76 | if let Ok(window) = window { 77 | let window_name = window.get_name(); 78 | 79 | if let Ok(window_name) = window_name { 80 | println!("window_name={}", window_name); 81 | 82 | if let Some(idx) = window_name.find("VIM:") { 83 | // by default, the server name appears at the end of the window title. By 84 | // convention, the server name starts with VIM:. 85 | let servername = &window_name[idx..]; 86 | let remoteexpr = format!( 87 | "execute(\"call Focus('{}', '{}')\")", 88 | direction.to_i3_direction(), 89 | direction.to_vim_direction() 90 | ); 91 | Command::new(&vim) // XXX hard-coded path 92 | .args(&["--servername", servername, "--remote-expr", &remoteexpr[..]]) 93 | .output() 94 | .expect("ran vim command"); 95 | 96 | return; 97 | } 98 | } 99 | } 100 | 101 | let mut conn = i3ipc::I3Connection::connect().expect("connect i3"); 102 | let command = format!("focus {}", direction.to_i3_direction()); 103 | println!("sending command: {}", command); 104 | conn.run_command(&command).expect("send i3 message"); 105 | } 106 | -------------------------------------------------------------------------------- /plugin/i3vimfocus.vim: -------------------------------------------------------------------------------- 1 | func! Focus(comando,vim_comando) 2 | let oldw = winnr() 3 | silent exe 'wincmd ' . a:vim_comando 4 | let neww = winnr() 5 | if oldw == neww 6 | " Use python to invoke the i3-msg command so that vim doesn't need to be 7 | " redrawn. 8 | call i3vimfocus#PythonExecProcess("i3-msg", ["-q", "focus", a:comando]) 9 | endif 10 | endfunction 11 | --------------------------------------------------------------------------------