├── .python-version ├── CONTRIBUTORS.md ├── Context.sublime-menu ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── LICENSE ├── Main.sublime-menu ├── Origami.sublime-commands ├── Origami.sublime-settings ├── README.md ├── messages.json ├── messages ├── 2.1.0.txt └── install.txt └── origami.py /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Origami was originally created by: 2 | 3 | * Nikolaus Wittenstein (adzenith) 4 | 5 | Other contributors: 6 | 7 | * David Baumgold (singingwolfboy), 8 | * Ryan Morrissey (23maverick23), 9 | -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Origami", 4 | "children": 5 | [ 6 | // Destroy current pane 7 | { 8 | "caption": "Close current", 9 | "command": "destroy_pane", "args": {"direction": "self"} 10 | }, 11 | // Pane 12 | { 13 | "caption": "Pane", 14 | "children": 15 | [ 16 | { 17 | "caption": "Create", 18 | "children": 19 | [ 20 | { "command": "create_pane", "args": {"direction": "up"}, "caption": "Above" }, 21 | { "command": "create_pane", "args": {"direction": "down"}, "caption": "Below" }, 22 | { "command": "create_pane", "args": {"direction": "right"}, "caption": "Right" }, 23 | { "command": "create_pane", "args": {"direction": "left"}, "caption": "Left" } 24 | ] 25 | }, 26 | { 27 | "caption": "Travel to", 28 | "children": 29 | [ 30 | { "command": "travel_to_pane", "args": {"direction": "up"}, "caption": "Above" }, 31 | { "command": "travel_to_pane", "args": {"direction": "down"}, "caption": "Below" }, 32 | { "command": "travel_to_pane", "args": {"direction": "right"}, "caption": "Right" }, 33 | { "command": "travel_to_pane", "args": {"direction": "left"}, "caption": "Left" } 34 | ] 35 | } 36 | ] 37 | }, 38 | // File 39 | { 40 | "caption": "File", 41 | "children": 42 | [ 43 | { 44 | "caption": "Create to", 45 | "children": 46 | [ 47 | { "command": "create_pane_with_file", "args": {"direction": "up"}, "caption": "Above" }, 48 | { "command": "create_pane_with_file", "args": {"direction": "down"}, "caption": "Below" }, 49 | { "command": "create_pane_with_file", "args": {"direction": "right"}, "caption": "Right" }, 50 | { "command": "create_pane_with_file", "args": {"direction": "left"}, "caption": "Left" } 51 | ] 52 | }, 53 | { 54 | "caption": "Carry to", 55 | "children": 56 | [ 57 | { "command": "carry_file_to_pane", "args": {"direction": "up"}, "caption": "Above" }, 58 | { "command": "carry_file_to_pane", "args": {"direction": "down"}, "caption": "Below" }, 59 | { "command": "carry_file_to_pane", "args": {"direction": "right"}, "caption": "Right" }, 60 | { "command": "carry_file_to_pane", "args": {"direction": "left"}, "caption": "Left" } 61 | ] 62 | }, 63 | { 64 | "caption": "Clone to", 65 | "children": 66 | [ 67 | { "command": "clone_file_to_pane", "args": {"direction": "up"}, "caption": "Above" }, 68 | { "command": "clone_file_to_pane", "args": {"direction": "down"}, "caption": "Below" }, 69 | { "command": "clone_file_to_pane", "args": {"direction": "right"}, "caption": "Right" }, 70 | { "command": "clone_file_to_pane", "args": {"direction": "left"}, "caption": "Left" } 71 | ] 72 | }, 73 | { 74 | "caption": "Pull from", 75 | "children": 76 | [ 77 | { "command": "pull_file_from_pane", "args": {"direction": "up"}, "caption": "Above" }, 78 | { "command": "pull_file_from_pane", "args": {"direction": "down"}, "caption": "Below" }, 79 | { "command": "pull_file_from_pane", "args": {"direction": "right"}, "caption": "Right" }, 80 | { "command": "pull_file_from_pane", "args": {"direction": "left"}, "caption": "Left" } 81 | ] 82 | } 83 | ] 84 | }, 85 | // Window 86 | { 87 | "caption": "Window", 88 | "children": 89 | [ 90 | { 91 | "caption": "New", 92 | "children": 93 | [ 94 | { "command": "new_window_from_saved_layout", "caption": "From Saved Layout" }, 95 | { "command": "new_window_with_current_layout", "caption": "With Current Layout" } 96 | ] 97 | } 98 | ] 99 | }, 100 | // Destroy 101 | { 102 | "caption": "Destroy", 103 | "children": 104 | [ 105 | { "command": "destroy_pane", "args": {"direction": "up"}, "caption": "Above" }, 106 | { "command": "destroy_pane", "args": {"direction": "down"}, "caption": "Below" }, 107 | { "command": "destroy_pane", "args": {"direction": "right"}, "caption": "Right" }, 108 | { "command": "destroy_pane", "args": {"direction": "left"}, "caption": "Left" }, 109 | { "command": "destroy_pane", "args": {"direction": "self"}, "caption": "Current Pane" } 110 | ] 111 | } 112 | ] 113 | } 114 | ] 115 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+k", "up"], "command": "travel_to_pane", "args": {"direction": "up"} }, 3 | { "keys": ["ctrl+k", "right"], "command": "travel_to_pane", "args": {"direction": "right"} }, 4 | { "keys": ["ctrl+k", "down"], "command": "travel_to_pane", "args": {"direction": "down"} }, 5 | { "keys": ["ctrl+k", "left"], "command": "travel_to_pane", "args": {"direction": "left"} }, 6 | 7 | { "keys": ["ctrl+k", "shift+up"], "command": "carry_file_to_pane", "args": {"direction": "up"} }, 8 | { "keys": ["ctrl+k", "shift+right"], "command": "carry_file_to_pane", "args": {"direction": "right"} }, 9 | { "keys": ["ctrl+k", "shift+down"], "command": "carry_file_to_pane", "args": {"direction": "down"} }, 10 | { "keys": ["ctrl+k", "shift+left"], "command": "carry_file_to_pane", "args": {"direction": "left"} }, 11 | 12 | { "keys": ["ctrl+k", "alt+up"], "command": "clone_file_to_pane", "args": {"direction": "up"} }, 13 | { "keys": ["ctrl+k", "alt+right"], "command": "clone_file_to_pane", "args": {"direction": "right"} }, 14 | { "keys": ["ctrl+k", "alt+down"], "command": "clone_file_to_pane", "args": {"direction": "down"} }, 15 | { "keys": ["ctrl+k", "alt+left"], "command": "clone_file_to_pane", "args": {"direction": "left"} }, 16 | // You can also create the pane automatically with the following command (insert empty parameters): 17 | // { "keys": [], "command": "create_pane_with_cloned_file", "args": {"direction": ""} } 18 | 19 | { "keys": ["ctrl+k", "ctrl+up"], "command": "create_pane", "args": {"direction": "up"} }, 20 | { "keys": ["ctrl+k", "ctrl+right"], "command": "create_pane", "args": {"direction": "right"} }, 21 | { "keys": ["ctrl+k", "ctrl+down"], "command": "create_pane", "args": {"direction": "down"} }, 22 | { "keys": ["ctrl+k", "ctrl+left"], "command": "create_pane", "args": {"direction": "left"} }, 23 | // You can also add "give_focus": true to automatically focus on the new pane as follows: 24 | // { "keys": [], "command": "create_pane", "args": {"direction": "", "give_focus": true} } 25 | 26 | { "keys": ["ctrl+k", "ctrl+shift+up"], "command": "destroy_pane", "args": {"direction": "up"} }, 27 | { "keys": ["ctrl+k", "ctrl+shift+right"], "command": "destroy_pane", "args": {"direction": "right"} }, 28 | { "keys": ["ctrl+k", "ctrl+shift+down"], "command": "destroy_pane", "args": {"direction": "down"} }, 29 | { "keys": ["ctrl+k", "ctrl+shift+left"], "command": "destroy_pane", "args": {"direction": "left"} }, 30 | // You can also destroy the current pane by binding the following command: 31 | // { "keys": [], "command": "destroy_pane", "args": {"direction": "self"} }, 32 | 33 | { "keys": ["ctrl+k", "ctrl+alt+up"], "command": "create_pane_with_file", "args": {"direction": "up"} }, 34 | { "keys": ["ctrl+k", "ctrl+alt+right"], "command": "create_pane_with_file", "args": {"direction": "right"} }, 35 | { "keys": ["ctrl+k", "ctrl+alt+down"], "command": "create_pane_with_file", "args": {"direction": "down"} }, 36 | { "keys": ["ctrl+k", "ctrl+alt+left"], "command": "create_pane_with_file", "args": {"direction": "left"} }, 37 | 38 | // You can pull a file from another pane by binding the following command: 39 | // { "keys": [], "command": "pull_file_from_pane", "args": { "direction": ""} } 40 | 41 | // NOTE: The following keybindings are disabled as they conflict with built-in features. 42 | // You can copy those to your user keybindings and change to your choosen combination 43 | // or just enable if you don't care about built-in features. 44 | 45 | // { "keys": ["ctrl+k", "ctrl+z"], "command": "zoom_pane", "args": {"fraction": 0.9} }, 46 | // { "keys": ["ctrl+k", "ctrl+shift+z"], "command": "unzoom_pane", "args": {} }, 47 | 48 | // { "keys": ["ctrl+k", "ctrl+c"], "command": "resize_pane", "args": {"orientation": "cols"} }, 49 | // { "keys": ["ctrl+k", "ctrl+r"], "command": "resize_pane", "args": {"orientation": "rows"} }, 50 | ] 51 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["super+k", "up"], "command": "travel_to_pane", "args": {"direction": "up"} }, 3 | { "keys": ["super+k", "right"], "command": "travel_to_pane", "args": {"direction": "right"} }, 4 | { "keys": ["super+k", "down"], "command": "travel_to_pane", "args": {"direction": "down"} }, 5 | { "keys": ["super+k", "left"], "command": "travel_to_pane", "args": {"direction": "left"} }, 6 | 7 | { "keys": ["super+k", "shift+up"], "command": "carry_file_to_pane", "args": {"direction": "up"} }, 8 | { "keys": ["super+k", "shift+right"], "command": "carry_file_to_pane", "args": {"direction": "right"} }, 9 | { "keys": ["super+k", "shift+down"], "command": "carry_file_to_pane", "args": {"direction": "down"} }, 10 | { "keys": ["super+k", "shift+left"], "command": "carry_file_to_pane", "args": {"direction": "left"} }, 11 | 12 | { "keys": ["super+k", "alt+up"], "command": "clone_file_to_pane", "args": {"direction": "up"} }, 13 | { "keys": ["super+k", "alt+right"], "command": "clone_file_to_pane", "args": {"direction": "right"} }, 14 | { "keys": ["super+k", "alt+down"], "command": "clone_file_to_pane", "args": {"direction": "down"} }, 15 | { "keys": ["super+k", "alt+left"], "command": "clone_file_to_pane", "args": {"direction": "left"} }, 16 | // You can also create the pane automatically with the following command (insert empty parameters): 17 | // { "keys": [], "command": "create_pane_with_cloned_file", "args": {"direction": ""} } 18 | 19 | { "keys": ["super+k", "super+up"], "command": "create_pane", "args": {"direction": "up"} }, 20 | { "keys": ["super+k", "super+right"], "command": "create_pane", "args": {"direction": "right"} }, 21 | { "keys": ["super+k", "super+down"], "command": "create_pane", "args": {"direction": "down"} }, 22 | { "keys": ["super+k", "super+left"], "command": "create_pane", "args": {"direction": "left"} }, 23 | // You can also add "give_focus": true to automatically focus on the new pane as follows: 24 | // { "keys": [], "command": "create_pane", "args": {"direction": "", "give_focus": true} } 25 | 26 | { "keys": ["super+k", "super+shift+up"], "command": "destroy_pane", "args": {"direction": "up"} }, 27 | { "keys": ["super+k", "super+shift+right"], "command": "destroy_pane", "args": {"direction": "right"} }, 28 | { "keys": ["super+k", "super+shift+down"], "command": "destroy_pane", "args": {"direction": "down"} }, 29 | { "keys": ["super+k", "super+shift+left"], "command": "destroy_pane", "args": {"direction": "left"} }, 30 | // You can also destroy the current pane by binding the following command: 31 | // { "keys": [], "command": "destroy_pane", "args": {"direction": "self"} }, 32 | 33 | { "keys": ["super+k", "super+alt+up"], "command": "create_pane_with_file", "args": {"direction": "up"} }, 34 | { "keys": ["super+k", "super+alt+right"], "command": "create_pane_with_file", "args": {"direction": "right"} }, 35 | { "keys": ["super+k", "super+alt+down"], "command": "create_pane_with_file", "args": {"direction": "down"} }, 36 | { "keys": ["super+k", "super+alt+left"], "command": "create_pane_with_file", "args": {"direction": "left"} }, 37 | 38 | // You can pull a file from another pane by binding the following command: 39 | // { "keys": [], "command": "pull_file_from_pane", "args": { "direction": ""} } 40 | 41 | // NOTE: The following keybindings are disabled as they conflict with built-in features. 42 | // You can copy those to your user keybindings and change to your choosen combination 43 | // or just enable if you don't care about built-in features. 44 | 45 | // { "keys": ["super+k", "super+z"], "command": "zoom_pane", "args": {"fraction": 0.9} }, 46 | // { "keys": ["super+k", "super+shift+z"], "command": "unzoom_pane", "args": {} }, 47 | 48 | // { "keys": ["super+k", "super+c"], "command": "resize_pane", "args": {"orientation": "cols"} }, 49 | // { "keys": ["super+k", "super+r"], "command": "resize_pane", "args": {"orientation": "rows"} }, 50 | ] 51 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+k", "up"], "command": "travel_to_pane", "args": {"direction": "up"} }, 3 | { "keys": ["ctrl+k", "right"], "command": "travel_to_pane", "args": {"direction": "right"} }, 4 | { "keys": ["ctrl+k", "down"], "command": "travel_to_pane", "args": {"direction": "down"} }, 5 | { "keys": ["ctrl+k", "left"], "command": "travel_to_pane", "args": {"direction": "left"} }, 6 | 7 | { "keys": ["ctrl+k", "shift+up"], "command": "carry_file_to_pane", "args": {"direction": "up"} }, 8 | { "keys": ["ctrl+k", "shift+right"], "command": "carry_file_to_pane", "args": {"direction": "right"} }, 9 | { "keys": ["ctrl+k", "shift+down"], "command": "carry_file_to_pane", "args": {"direction": "down"} }, 10 | { "keys": ["ctrl+k", "shift+left"], "command": "carry_file_to_pane", "args": {"direction": "left"} }, 11 | 12 | { "keys": ["ctrl+k", "alt+up"], "command": "clone_file_to_pane", "args": {"direction": "up"} }, 13 | { "keys": ["ctrl+k", "alt+right"], "command": "clone_file_to_pane", "args": {"direction": "right"} }, 14 | { "keys": ["ctrl+k", "alt+down"], "command": "clone_file_to_pane", "args": {"direction": "down"} }, 15 | { "keys": ["ctrl+k", "alt+left"], "command": "clone_file_to_pane", "args": {"direction": "left"} }, 16 | // You can also create the pane automatically with the following command (insert empty parameters): 17 | // { "keys": [], "command": "create_pane_with_cloned_file", "args": {"direction": ""} } 18 | 19 | { "keys": ["ctrl+k", "ctrl+up"], "command": "create_pane", "args": {"direction": "up"} }, 20 | { "keys": ["ctrl+k", "ctrl+right"], "command": "create_pane", "args": {"direction": "right"} }, 21 | { "keys": ["ctrl+k", "ctrl+down"], "command": "create_pane", "args": {"direction": "down"} }, 22 | { "keys": ["ctrl+k", "ctrl+left"], "command": "create_pane", "args": {"direction": "left"} }, 23 | // You can also add "give_focus": true to automatically focus on the new pane as follows: 24 | // { "keys": [], "command": "create_pane", "args": {"direction": "", "give_focus": true} } 25 | 26 | { "keys": ["ctrl+k", "ctrl+shift+up"], "command": "destroy_pane", "args": {"direction": "up"} }, 27 | { "keys": ["ctrl+k", "ctrl+shift+right"], "command": "destroy_pane", "args": {"direction": "right"} }, 28 | { "keys": ["ctrl+k", "ctrl+shift+down"], "command": "destroy_pane", "args": {"direction": "down"} }, 29 | { "keys": ["ctrl+k", "ctrl+shift+left"], "command": "destroy_pane", "args": {"direction": "left"} }, 30 | // You can also destroy the current pane by binding the following command: 31 | // { "keys": [], "command": "destroy_pane", "args": {"direction": "self"} }, 32 | 33 | { "keys": ["ctrl+k", "ctrl+alt+up"], "command": "create_pane_with_file", "args": {"direction": "up"} }, 34 | { "keys": ["ctrl+k", "ctrl+alt+right"], "command": "create_pane_with_file", "args": {"direction": "right"} }, 35 | { "keys": ["ctrl+k", "ctrl+alt+down"], "command": "create_pane_with_file", "args": {"direction": "down"} }, 36 | { "keys": ["ctrl+k", "ctrl+alt+left"], "command": "create_pane_with_file", "args": {"direction": "left"} }, 37 | 38 | // You can pull a file from another pane by binding the following command: 39 | // { "keys": [], "command": "pull_file_from_pane", "args": { "direction": ""} } 40 | 41 | // NOTE: The following keybindings are disabled as they conflict with built-in features. 42 | // You can copy those to your user keybindings and change to your choosen combination 43 | // or just enable if you don't care about built-in features. 44 | 45 | // { "keys": ["ctrl+k", "ctrl+z"], "command": "zoom_pane", "args": {"fraction": 0.9} }, 46 | // { "keys": ["ctrl+k", "ctrl+shift+z"], "command": "unzoom_pane", "args": {} }, 47 | 48 | // { "keys": ["ctrl+k", "ctrl+c"], "command": "resize_pane", "args": {"orientation": "cols"} }, 49 | // { "keys": ["ctrl+k", "ctrl+r"], "command": "resize_pane", "args": {"orientation": "rows"} } 50 | ] 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Origami Contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "view", 4 | "children": 5 | [ 6 | { 7 | "caption": "-", 8 | "id": "origami" 9 | }, 10 | { 11 | "caption": "Origami", 12 | "children": 13 | [ 14 | { 15 | "caption": "Pane", 16 | "children": 17 | [ 18 | { 19 | "caption": "Create", 20 | "children": 21 | [ 22 | { "command": "create_pane", "args": {"direction": "up"}, "caption": "Above" }, 23 | { "command": "create_pane", "args": {"direction": "down"}, "caption": "Below" }, 24 | { "command": "create_pane", "args": {"direction": "right"}, "caption": "Right" }, 25 | { "command": "create_pane", "args": {"direction": "left"}, "caption": "Left" } 26 | ] 27 | }, 28 | { 29 | "caption": "Travel to", 30 | "children": 31 | [ 32 | { "command": "travel_to_pane", "args": {"direction": "up"}, "caption": "Above" }, 33 | { "command": "travel_to_pane", "args": {"direction": "down"}, "caption": "Below" }, 34 | { "command": "travel_to_pane", "args": {"direction": "right"}, "caption": "Right" }, 35 | { "command": "travel_to_pane", "args": {"direction": "left"}, "caption": "Left" } 36 | ] 37 | }, 38 | { 39 | "caption": "-" 40 | }, 41 | { 42 | "caption": "Destroy", 43 | "children": 44 | [ 45 | { "command": "destroy_pane", "args": {"direction": "up"}, "caption": "Above" }, 46 | { "command": "destroy_pane", "args": {"direction": "down"}, "caption": "Below" }, 47 | { "command": "destroy_pane", "args": {"direction": "right"}, "caption": "Right" }, 48 | { "command": "destroy_pane", "args": {"direction": "left"}, "caption": "Left" }, 49 | { "command": "destroy_pane", "args": {"direction": "self"}, "caption": "Current Pane" } 50 | ] 51 | } 52 | ] 53 | }, 54 | { 55 | "caption": "File", 56 | "children": 57 | [ 58 | { 59 | "caption": "Create to", 60 | "children": 61 | [ 62 | { "command": "create_pane_with_file", "args": {"direction": "up"}, "caption": "Above" }, 63 | { "command": "create_pane_with_file", "args": {"direction": "down"}, "caption": "Below" }, 64 | { "command": "create_pane_with_file", "args": {"direction": "right"}, "caption": "Right" }, 65 | { "command": "create_pane_with_file", "args": {"direction": "left"}, "caption": "Left" } 66 | ] 67 | }, 68 | { 69 | "caption": "Carry to", 70 | "children": 71 | [ 72 | { "command": "carry_file_to_pane", "args": {"direction": "up"}, "caption": "Above" }, 73 | { "command": "carry_file_to_pane", "args": {"direction": "down"}, "caption": "Below" }, 74 | { "command": "carry_file_to_pane", "args": {"direction": "right"}, "caption": "Right" }, 75 | { "command": "carry_file_to_pane", "args": {"direction": "left"}, "caption": "Left" } 76 | ] 77 | }, 78 | { 79 | "caption": "Clone to", 80 | "children": 81 | [ 82 | { "command": "clone_file_to_pane", "args": {"direction": "up"}, "caption": "Above" }, 83 | { "command": "clone_file_to_pane", "args": {"direction": "down"}, "caption": "Below" }, 84 | { "command": "clone_file_to_pane", "args": {"direction": "right"}, "caption": "Right" }, 85 | { "command": "clone_file_to_pane", "args": {"direction": "left"}, "caption": "Left" } 86 | ] 87 | }, 88 | { 89 | "caption": "Pull from", 90 | "children": 91 | [ 92 | { "command": "pull_file_from_pane", "args": {"direction": "up"}, "caption": "Above" }, 93 | { "command": "pull_file_from_pane", "args": {"direction": "down"}, "caption": "Below" }, 94 | { "command": "pull_file_from_pane", "args": {"direction": "right"}, "caption": "Right" }, 95 | { "command": "pull_file_from_pane", "args": {"direction": "left"}, "caption": "Left" } 96 | ] 97 | } 98 | ] 99 | }, 100 | { 101 | "caption": "Window", 102 | "children": 103 | [ 104 | { 105 | "caption": "New", 106 | "children": 107 | [ 108 | { "command": "new_window_from_saved_layout", "caption": "From Saved Layout" }, 109 | { "command": "new_window_with_current_layout", "caption": "With Current Layout" } 110 | ] 111 | } 112 | ] 113 | } 114 | ] 115 | } 116 | ] 117 | }, 118 | { 119 | "caption": "Preferences", 120 | "mnemonic": "n", 121 | "id": "preferences", 122 | "children": 123 | [ 124 | { 125 | // Include this information in case it is the only package using that menu 126 | "caption": "Package Settings", 127 | "mnemonic": "P", 128 | "id": "package-settings", 129 | "children": 130 | [ 131 | { 132 | "caption": "Origami", 133 | "children": 134 | [ 135 | { 136 | "caption": "Settings", 137 | "command": "edit_settings", 138 | "args": { 139 | "base_file": "${packages}/Origami/Origami.sublime-settings", 140 | "default": "// Settings in here override those in \"Origami/Origami.sublime-settings\"\n{\n\t$0\n}\n" 141 | } 142 | }, 143 | { 144 | "caption": "Key Bindings", 145 | "command": "edit_settings", 146 | "args": { 147 | "base_file": "${packages}/Origami/Default ($platform).sublime-keymap", 148 | "user_file": "${packages}/User/Default (${platform}).sublime-keymap", 149 | "default": "[\n\t$0\n]\n", 150 | } 151 | }, 152 | { "caption": "-" }, 153 | { 154 | "caption": "README", 155 | "command": "open_file", 156 | "args": { 157 | "file": "${packages}/Origami/README.md" 158 | } 159 | }, 160 | ] 161 | } 162 | ] 163 | } 164 | ] 165 | } 166 | ] 167 | -------------------------------------------------------------------------------- /Origami.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { "command": "travel_to_pane", "args": {"direction": "up"}, "caption": "Origami: Focus on Pane Above" }, 3 | { "command": "travel_to_pane", "args": {"direction": "right"}, "caption": "Origami: Focus on Pane on the Right" }, 4 | { "command": "travel_to_pane", "args": {"direction": "down"}, "caption": "Origami: Focus on Pane Below" }, 5 | { "command": "travel_to_pane", "args": {"direction": "left"}, "caption": "Origami: Focus on Pane on the Left" }, 6 | 7 | { "command": "carry_file_to_pane", "args": {"direction": "up"}, "caption": "Origami: Move File to Pane Above" }, 8 | { "command": "carry_file_to_pane", "args": {"direction": "right"}, "caption": "Origami: Move File to Pane on the Right" }, 9 | { "command": "carry_file_to_pane", "args": {"direction": "down"}, "caption": "Origami: Move File to Pane Below" }, 10 | { "command": "carry_file_to_pane", "args": {"direction": "left"}, "caption": "Origami: Move File to Pane on the Left" }, 11 | 12 | { "command": "clone_file_to_pane", "args": {"direction": "up"}, "caption": "Origami: Clone File to Pane Above" }, 13 | { "command": "clone_file_to_pane", "args": {"direction": "right"}, "caption": "Origami: Clone File to Pane on the Right" }, 14 | { "command": "clone_file_to_pane", "args": {"direction": "down"}, "caption": "Origami: Clone File to Pane Below" }, 15 | { "command": "clone_file_to_pane", "args": {"direction": "left"}, "caption": "Origami: Clone File to Pane on the Left" }, 16 | 17 | { "command": "create_pane", "args": {"direction": "up"}, "caption": "Origami: Create Pane Above" }, 18 | { "command": "create_pane", "args": {"direction": "right"}, "caption": "Origami: Create Pane on the Right" }, 19 | { "command": "create_pane", "args": {"direction": "down"}, "caption": "Origami: Create Pane Below" }, 20 | { "command": "create_pane", "args": {"direction": "left"}, "caption": "Origami: Create Pane on the Left" }, 21 | 22 | { "command": "create_pane", "args": {"direction": "up", "give_focus": true}, "caption": "Origami: Create and Focus Pane Above" }, 23 | { "command": "create_pane", "args": {"direction": "right", "give_focus": true}, "caption": "Origami: Create and Focus Pane on the Right" }, 24 | { "command": "create_pane", "args": {"direction": "down", "give_focus": true}, "caption": "Origami: Create and Focus Pane Below" }, 25 | { "command": "create_pane", "args": {"direction": "left", "give_focus": true}, "caption": "Origami: Create and Focus Pane on the Left" }, 26 | 27 | { "command": "pull_file_from_pane", "args": {"direction": "up"}, "caption": "Origami: Pull File from Pane Above" }, 28 | { "command": "pull_file_from_pane", "args": {"direction": "right"}, "caption": "Origami: Pull File from Pane on the Right" }, 29 | { "command": "pull_file_from_pane", "args": {"direction": "down"}, "caption": "Origami: Pull File from Pane Below" }, 30 | { "command": "pull_file_from_pane", "args": {"direction": "left"}, "caption": "Origami: Pull File from Pane on the Left" }, 31 | 32 | { "command": "destroy_pane", "args": {"direction": "up"}, "caption": "Origami: Destroy Pane Above" }, 33 | { "command": "destroy_pane", "args": {"direction": "right"}, "caption": "Origami: Destroy Pane on the Right" }, 34 | { "command": "destroy_pane", "args": {"direction": "down"}, "caption": "Origami: Destroy Pane Below" }, 35 | { "command": "destroy_pane", "args": {"direction": "left"}, "caption": "Origami: Destroy Pane on the Left" }, 36 | { "command": "destroy_pane", "args": {"direction": "self"}, "caption": "Origami: Destroy Current Pane" }, 37 | 38 | { "command": "create_pane_with_file", "args": {"direction": "up"}, "caption": "Origami: Create Pane with File Above" }, 39 | { "command": "create_pane_with_file", "args": {"direction": "right"}, "caption": "Origami: Create Pane with File on the Right" }, 40 | { "command": "create_pane_with_file", "args": {"direction": "down"}, "caption": "Origami: Create Pane with File Below" }, 41 | { "command": "create_pane_with_file", "args": {"direction": "left"}, "caption": "Origami: Create Pane with File on the Left" }, 42 | 43 | { "command": "save_layout", "caption": "Origami: Save Current Layout" }, 44 | { "command": "restore_layout", "caption": "Origami: Restore Saved Layout" }, 45 | { "command": "remove_layout", "caption": "Origami: Remove Saved Layout" }, 46 | { "command": "new_window_from_saved_layout", "caption": "Origami: New Window from Saved Layout" }, 47 | { "command": "new_window_with_current_layout", "caption": "Origami: New Window with Current Layout" }, 48 | 49 | { "command": "toggle_zoom_pane", "args": {"fraction": 0.9}, "caption": "Origami: Zoom/Unzoom Current Pane (Toggle Zoom)" }, 50 | { "command": "zoom_pane", "args": {"fraction": 0.9}, "caption": "Origami: Zoom Current Pane" }, 51 | { "command": "unzoom_pane", "args": {}, "caption": "Origami: Unzoom Current Pane" }, 52 | 53 | { 54 | "caption": "Preferences: Origami Settings", 55 | "command": "edit_settings", 56 | "args": { 57 | "base_file": "${packages}/Origami/Origami.sublime-settings", 58 | "default": "// Settings in here override those in \"Origami/Origami.sublime-settings\"\n{\n\t$0\n}\n"} 59 | }, 60 | { 61 | "caption": "Preferences: Origami Key Bindings", 62 | "command": "edit_settings", 63 | "args": { 64 | "base_file": "${packages}/Origami/Default ($platform).sublime-keymap", 65 | "user_file": "${packages}/User/Default ($platform).sublime-keymap", 66 | "default": "[\n\t$0\n]\n", 67 | } 68 | } 69 | ] 70 | -------------------------------------------------------------------------------- /Origami.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * The "saved_layouts" key holds a list of previously 4 | * saved layout settings. These can be set using the 5 | * "Origami: Save Current Layout" command, they can be 6 | * restored using the "Origami: Restore Saved Layout" 7 | * command and can be removed using the "Origami: Remove 8 | * Saved Layout" command. 9 | * 10 | * Valid format: {"nickname": XXXX, "cells": XXXX, 11 | * "cols": XXXX, "rows": XXXX} 12 | */ 13 | "saved_layouts": [], 14 | 15 | // Create a new pane when switching in a direction without one. 16 | "create_new_pane_if_necessary": true, 17 | 18 | // Automatically zoom the active pane. 19 | // Set it to `true` for the default zoom, or to a user-definable 20 | // fraction of the screen, such as "0.75", or "[0.5, 0.6]" for 21 | // horizontal and vertical correspondingly. 22 | "auto_zoom_on_focus": false, 23 | 24 | // Automatically close a pane once you've closed the last file in it. 25 | "auto_close_empty_panes": false, 26 | 27 | // Destroy unused panes when switching away from them. 28 | "destroy_empty_panes": false, 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Origami 2 | ======= 3 | 4 | Origami is a new way of thinking about panes in Sublime Text: you tell Sublime Text where you want a new pane, and it makes one for you. It works seamlessly alongside the built-in layout commands. 5 | 6 | Ordinarily one uses the commands under View>Layout, or if one is quite intrepid a custom keyboard shortcut can be made to give a specific layout, but both of these solutions were unsatisfactory to me. Perhaps they were to you too! That's what this plugin is for. 7 | 8 | Try it out! I think you'll like it. 9 | 10 | Keyboard shortcuts 11 | ------------------ 12 | 13 | Origami is driven by keyboard shortcuts. By default, these keyboard shortcuts are all two-stage, and are hidden behind `command+k`. First press `command+k`, then press the arrow keys with modifiers: 14 | 15 | > **NOTE**: Windows and Linux use `ctrl` instead of `command`. 16 | 17 | | First | Then | Action | 18 | | ----------- | ------------------------------------------ | ----------------------------------------- | 19 | | `command+k` | ▲►▼◄ | travel to an adjacent pane | 20 | | `command+k` | `shift`+▲►▼◄ | carry the current file to the destination | 21 | | `command+k` | `alt` (`option` on Mac)+▲►▼◄ | clone the current file to the destination | 22 | | `command+k` | `command`+▲►▼◄ | create an adjacent pane | 23 | | `command+k` | `command+shift`+▲►▼◄ | destroy an adjacent pane | 24 | | `command+k` | `ctrl+alt` (`command+option` on Mac)+▲►▼◄ | create an adjacent pane and carry the current file to the destination | 25 | 26 | These keyboard shortcuts are designed to make it really easy to modify the layout of your editor. 27 | 28 | > **NOTE**: The following keyboard shortcuts for zooming and editing pane sizes are not enabled by default due to a conflict with built-in ST features. Open the `Preferences: Origami Key Bindings` from the Command Palette to enable or edit them, or just use the Command Palette to trigger those commands. 29 | 30 | 31 | Additionally, Origami allows one to zoom the current pane, making it take up a large portion of the window: 32 | 33 | | First | Then | Action | 34 | | ----------- | ----------------- | -------------------------------- | 35 | | `command+k` | `command+z` | Zoom the current pane so it takes up 90% of the screen (the fraction is changeable in the keybindings) | 36 | | `command+k` | `shift+command+z` | Un-zoom: equally space all panes | 37 | 38 | It is also possible to edit the pane sizes: 39 | 40 | | First | Then | Action | 41 | | ----------- | ------------| ----------------------------------- | 42 | | `command+k` | `command+r` | Adjust the top and bottom separator | 43 | | `command+k` | `command+c` | Adjust the left and right separator | 44 | 45 | In the keybindings you can change a `mode` which specifies which separation lines you want to edit. 46 | * `ALL` means all horizontal (or vertical) separators 47 | * `RELEVANT` means all horizontal (or vertical) separators which intersect the column (row) of the selected row. 48 | * `NEAREST` means top and bottom (or left and right) separators. This is the default `mode`. 49 | * `BEFORE` means top (or left) separator 50 | * `AFTER` means bottom (or right) separator 51 | 52 | Automation 53 | ---------- 54 | 55 | You can have Origami automatically zoom the active pane by setting `auto_zoom_on_focus` in your Origami user preferences. Set it to `true` for the default zoom, or set it to a user-definable fraction of the screen, such as `0.75`, or `[0.5, 0.6]` for horizontal and vertical correspondingly. 56 | 57 | Origami can also automatically close a pane for you once you've closed the last file in it. Just set `auto_close_empty_panes` to true in the Origami preferences. 58 | 59 | Installation 60 | ------------ 61 | 62 | #### Using package control 63 | 64 | 1. Open up the command palette: ctrl+shift+p (Linux, Windows) / cmd+shift+p (macOS) 65 | 2. Search for `Package Control: Install Package` 66 | 3. Search for `Origami` 67 | 4. Hit enter :wink: 68 | 69 | #### Using the command line 70 | 71 | If you want to contribute to this package, first thanks, and second, you should download this using `git` so that you can propose your changes. 72 | 73 | ```bash 74 | cd "%APPDATA%\Sublime Text 3\Packages" # on Windows 75 | cd ~/Library/Application\ Support/Sublime\ Text\ 3 # on Mac 76 | cd ~/.config/sublime-text-3 # on Linux 77 | 78 | git clone "https://github.com/SublimeText/Origami.git" 79 | ``` 80 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "2.1.0": "messages/2.1.0.txt", 3 | "install": "messages/install.txt" 4 | } 5 | -------------------------------------------------------------------------------- /messages/2.1.0.txt: -------------------------------------------------------------------------------- 1 | **BREAKING**: 2 | - The default keybindings for zooming and changing pane sizes are now disabled due to a conflict with built-in ST features. 3 | 4 | You can re-enable them by opening the `Preferences: Origami Key Bindings` from the Command Palette and copying 5 | disabled keybindings to your user keybindings. Consider changing the key combination if you care about having 6 | built-in features. 7 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | Origami 2 | the art of folding paper 3 | 4 | https://github.com/SublimeText/Origami 5 | 6 | Origami is a new way of thinking about panes in Sublime Text: 7 | you tell Sublime Text where you want a new pane, and it makes one for you. 8 | It works seamlessly alongside the built-in layout commands. 9 | 10 | All commands are available under the View>Origami menu, as well as in the 11 | command palette (search for Origami). 12 | 13 | Keybindings also exist for most commands. Origami shortcuts start by pressing 14 | and releasing ctrl+k (or cmd+k), followed by a second shortcut, like this: 15 | * ctrl/cmd+k, then an arrow: move focus to the pane in that direction 16 | * ctrl/cmd+k, then shift+arrow: move the current file to the destination 17 | * ctrl/cmd+k, then alt/option+arrow: clone the current file to the destination 18 | * ctrl/cmd+k, then ctrl/cmd+arrow: create an adjacent pane 19 | * ctrl/cmd+k, then ctrl/cmd+shift+arrow: destroy an adjacent pane 20 | -------------------------------------------------------------------------------- /origami.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import copy 4 | from functools import partial 5 | 6 | 7 | SETTINGS_FILENAME = 'Origami.sublime-settings' 8 | XMIN, YMIN, XMAX, YMAX = range(4) 9 | 10 | 11 | def increment_if_greater_or_equal(x, threshold): 12 | if x >= threshold: 13 | return x + 1 14 | return x 15 | 16 | 17 | def decrement_if_greater(x, threshold): 18 | if x > threshold: 19 | return x - 1 20 | return x 21 | 22 | 23 | def pull_up_cells_after(cells, threshold): 24 | return [ 25 | [x0, decrement_if_greater(y0, threshold), x1, decrement_if_greater(y1, threshold)] 26 | for (x0, y0, x1, y1) in cells 27 | ] 28 | 29 | 30 | def push_right_cells_after(cells, threshold): 31 | return [ 32 | [increment_if_greater_or_equal(x0, threshold), y0, increment_if_greater_or_equal(x1, threshold), y1] 33 | for (x0, y0, x1, y1) in cells 34 | ] 35 | 36 | 37 | def push_down_cells_after(cells, threshold): 38 | return [ 39 | [x0, increment_if_greater_or_equal(y0, threshold), x1, increment_if_greater_or_equal(y1, threshold)] 40 | for (x0, y0, x1, y1) in cells 41 | ] 42 | 43 | 44 | def pull_left_cells_after(cells, threshold): 45 | return [ 46 | [decrement_if_greater(x0, threshold), y0, decrement_if_greater(x1, threshold), y1] 47 | for (x0, y0, x1, y1) in cells 48 | ] 49 | 50 | 51 | def opposite_direction(direction): 52 | if direction == "up": 53 | return "down" 54 | if direction == "right": 55 | return "left" 56 | if direction == "down": 57 | return "up" 58 | if direction == "left": 59 | return "right" 60 | 61 | 62 | def cells_adjacent_to_cell_in_direction(cells, cell, direction): 63 | if direction == "up": 64 | return [c for c in cells if cell[YMIN] == c[YMAX]] 65 | if direction == "right": 66 | return [c for c in cells if cell[XMAX] == c[XMIN]] 67 | if direction == "down": 68 | return [c for c in cells if cell[YMAX] == c[YMIN]] 69 | if direction == "left": 70 | return [c for c in cells if cell[XMIN] == c[XMAX]] 71 | raise Exception('Unsupported direction "{}"'.format(direction)) 72 | 73 | 74 | class WithSettings: 75 | _settings = None 76 | 77 | def settings(self): 78 | if self._settings is None: 79 | self._settings = sublime.load_settings(SETTINGS_FILENAME) 80 | return self._settings 81 | 82 | 83 | class PaneCommand(sublime_plugin.WindowCommand): 84 | """Abstract base class for commands.""" 85 | 86 | def get_layout(self): 87 | layout = self.window.layout() 88 | rows = layout["rows"] 89 | cols = layout["cols"] 90 | cells = layout["cells"] 91 | return rows, cols, cells 92 | 93 | def get_cells(self): 94 | return self.get_layout()[2] 95 | 96 | def adjacent_cell(self, direction): 97 | cells = self.get_cells() 98 | current_cell = cells[self.window.active_group()] 99 | adjacent_cells = cells_adjacent_to_cell_in_direction(cells, current_cell, direction) 100 | rows, cols, _ = self.get_layout() 101 | 102 | if direction in ("left", "right"): 103 | MIN, MAX, fields = YMIN, YMAX, rows 104 | else: # up or down 105 | MIN, MAX, fields = XMIN, XMAX, cols 106 | 107 | cell_overlap = [] 108 | for cell in adjacent_cells: 109 | start = max(fields[cell[MIN]], fields[current_cell[MIN]]) 110 | end = min(fields[cell[MAX]], fields[current_cell[MAX]]) 111 | overlap = end - start # / (fields[cell[MAX]] - fields[cell[MIN]]) 112 | cell_overlap.append(overlap) 113 | 114 | if len(cell_overlap) != 0: 115 | cell_index = cell_overlap.index(max(cell_overlap)) 116 | return adjacent_cells[cell_index] 117 | return None 118 | 119 | def duplicated_views(self, original_group, duplicating_group): 120 | original_views = self.window.views_in_group(original_group) 121 | original_buffers = {v.buffer_id() for v in original_views} 122 | potential_dupe_views = self.window.views_in_group(duplicating_group) 123 | return [pd for pd in potential_dupe_views if pd.buffer_id() in original_buffers] 124 | 125 | def travel_to_pane(self, direction, create_new_if_necessary=False, destroy_old_if_empty=False): 126 | adjacent_cell = self.adjacent_cell(direction) 127 | if adjacent_cell: 128 | cells = self.get_cells() 129 | new_group_index = cells.index(adjacent_cell) 130 | self.window.focus_group(new_group_index) 131 | if destroy_old_if_empty: 132 | self.destroy_pane(opposite_direction(direction), True) 133 | elif create_new_if_necessary: 134 | self.create_pane(direction, True, destroy_old_if_empty) 135 | 136 | def carry_file_to_pane(self, direction, create_new_if_necessary=False, destroy_old_if_empty=False): 137 | view = self.window.active_view() 138 | if view is None: 139 | # If we're in an empty group, there's no active view 140 | return 141 | window = self.window 142 | self.travel_to_pane(direction, create_new_if_necessary) 143 | 144 | active_group = window.active_group() 145 | views_in_group = window.views_in_group(active_group) 146 | window.set_view_index(view, active_group, len(views_in_group)) 147 | sublime.set_timeout(lambda: window.focus_view(view)) 148 | 149 | if destroy_old_if_empty: 150 | self.destroy_pane(opposite_direction(direction), True) 151 | 152 | def clone_file_to_pane(self, direction, create_new_if_necessary=False): 153 | window = self.window 154 | view = window.active_view() 155 | if view is None: 156 | # If we're in an empty group, there's no active view 157 | return 158 | group, original_index = window.get_view_index(view) 159 | window.run_command("clone_file") 160 | 161 | # If we move the cloned file's tab to the left of the original's, 162 | # then when we remove it from the group, focus will fall to the 163 | # original view. 164 | new_view = window.active_view() 165 | if new_view is None: 166 | return 167 | window.set_view_index(new_view, group, original_index) 168 | 169 | # Fix the new view's selection and viewport 170 | new_sel = new_view.sel() 171 | new_sel.clear() 172 | for s in view.sel(): 173 | new_sel.add(s) 174 | sublime.set_timeout(lambda: new_view.set_viewport_position(view.viewport_position(), False), 0) 175 | 176 | self.carry_file_to_pane(direction, create_new_if_necessary) 177 | 178 | def reorder_panes(self, leave_files_at_position=True): 179 | old_index = self.window.active_group() 180 | on_done = partial(self._on_reorder_done, old_index, leave_files_at_position) 181 | view = self.window.show_input_panel("enter new index", str(old_index + 1), on_done, None, None) 182 | view.sel().clear() 183 | view.sel().add(sublime.Region(0, view.size())) 184 | 185 | def _on_reorder_done(self, old_index, leave_files_at_position, text): 186 | try: 187 | new_index = int(text) - 1 188 | except ValueError: 189 | return 190 | 191 | rows, cols, cells = self.get_layout() 192 | 193 | if new_index < 0 or new_index >= len(cells): 194 | return 195 | 196 | cells[old_index], cells[new_index] = cells[new_index], cells[old_index] 197 | 198 | if leave_files_at_position: 199 | old_files = self.window.views_in_group(old_index) 200 | new_files = self.window.views_in_group(new_index) 201 | for position, v in enumerate(old_files): 202 | self.window.set_view_index(v, new_index, position) 203 | for position, v in enumerate(new_files): 204 | self.window.set_view_index(v, old_index, position) 205 | 206 | layout = {"cols": cols, "rows": rows, "cells": cells} # type: sublime.Layout 207 | self.window.set_layout(layout) 208 | 209 | def resize_panes(self, orientation, mode): 210 | rows, cols, cells = self.get_layout() 211 | 212 | if orientation == "cols": 213 | data = cols 214 | min1 = YMIN 215 | max1 = YMAX 216 | min2 = XMIN 217 | max2 = XMAX 218 | elif orientation == "rows": 219 | data = rows 220 | min1 = XMIN 221 | max1 = XMAX 222 | min2 = YMIN 223 | max2 = YMAX 224 | else: 225 | raise Exception('Unsupported orientation "{}"'.format(orientation)) 226 | 227 | relevant_index = set() 228 | 229 | if mode == "BEFORE": 230 | current_cell = cells[self.window.active_group()] 231 | relevant_index.add(current_cell[min2]) 232 | 233 | elif mode == "AFTER": 234 | current_cell = cells[self.window.active_group()] 235 | relevant_index.add(current_cell[max2]) 236 | 237 | elif mode == "NEAREST": 238 | current_cell = cells[self.window.active_group()] 239 | relevant_index.update((current_cell[min2], current_cell[max2])) 240 | 241 | elif mode == "RELEVANT": 242 | current_cell = cells[self.window.active_group()] 243 | min_val1 = current_cell[min1] 244 | max_val1 = current_cell[max1] 245 | for c in cells: 246 | min_val2 = c[min1] 247 | max_val2 = c[max1] 248 | if min_val1 >= max_val2 or min_val2 >= max_val1: 249 | continue 250 | relevant_index.update((c[min2], c[max2])) 251 | 252 | elif mode == "ALL": 253 | relevant_index.update(range(len(data))) 254 | 255 | relevant_index.difference_update((0, len(data) - 1)) # dont show the first and last value (it's always 0 and 1) 256 | relevant_index = sorted(relevant_index) 257 | 258 | text = ", ".join(str(data[i]) for i in relevant_index) 259 | on_done = partial(self._on_resize_panes, orientation, cells, relevant_index, data) 260 | on_update = partial(self._on_resize_panes_update, orientation, cells, relevant_index, data) 261 | on_cancle = partial(self._on_resize_panes, orientation, cells, relevant_index, data, text) 262 | view = self.window.show_input_panel(orientation, text, on_done, on_update, on_cancle) 263 | view.sel().clear() 264 | view.sel().add(sublime.Region(0, view.size())) 265 | 266 | def _on_resize_panes_get_layout(self, orientation, cells, relevant_index, orig_data, text): 267 | rows, cols, _ = self.get_layout() 268 | 269 | input_data = [float(x) for x in text.split(",")] 270 | if any(d > 1.0 for d in input_data): 271 | return {"cols": cols, "rows": rows, "cells": cells} 272 | 273 | cells = copy.deepcopy(cells) 274 | data = copy.deepcopy(orig_data) 275 | for i, d in zip(relevant_index, input_data): 276 | data[i] = d 277 | 278 | data = sorted(enumerate(data), key=lambda x: x[1]) # sort such that you can swap grid lines 279 | indexes, data = map(list, zip(*data)) # indexes are also sorted 280 | 281 | revelant_cell_entries = [] 282 | if orientation == "cols": 283 | revelant_cell_entries = [XMIN, XMAX] 284 | elif orientation == "rows": 285 | revelant_cell_entries = [YMIN, YMAX] 286 | 287 | # change the cell boundaries according to the sorted indexes 288 | for cell in cells: 289 | for j in revelant_cell_entries: 290 | for new, old in enumerate(indexes): 291 | if new != old and cell[j] == new: 292 | cell[j] = old 293 | break 294 | 295 | if orientation == "cols": 296 | if len(cols) == len(data): 297 | cols = data 298 | elif orientation == "rows": 299 | if len(rows) == len(data): 300 | rows = data 301 | 302 | return {"cols": cols, "rows": rows, "cells": cells} 303 | 304 | def _on_resize_panes_update(self, orientation, cells, relevant_index, orig_data, text): 305 | layout = self._on_resize_panes_get_layout(orientation, cells, relevant_index, orig_data, text) 306 | self.window.set_layout(layout) 307 | 308 | def _on_resize_panes(self, orientation, cells, relevant_index, orig_data, text): 309 | layout = self._on_resize_panes_get_layout(orientation, cells, relevant_index, orig_data, text) 310 | self.window.set_layout(layout) 311 | 312 | def zoom_pane(self, fraction): 313 | fraction_horizontal = fraction_vertical = .9 314 | 315 | if isinstance(fraction, float) or isinstance(fraction, int): 316 | fraction_horizontal = fraction_vertical = fraction 317 | elif isinstance(fraction, list) and len(fraction) == 2: 318 | if isinstance(fraction[0], float) or isinstance(fraction[0], int): 319 | fraction_horizontal = fraction[0] 320 | if isinstance(fraction[1], float) or isinstance(fraction[1], int): 321 | fraction_vertical = fraction[1] 322 | 323 | fraction_horizontal = min(1, max(0, fraction_horizontal)) 324 | fraction_vertical = min(1, max(0, fraction_vertical)) 325 | 326 | window = self.window 327 | rows, cols, cells = self.get_layout() 328 | current_cell = cells[window.active_group()] 329 | 330 | current_col = current_cell[0] 331 | num_cols = len(cols) - 1 332 | 333 | # TODO: the sizes of the unzoomed panes are calculated incorrectly if the 334 | # unzoomed panes have a split that overlaps the zoomed pane. 335 | current_col_width = 1 if num_cols == 1 else fraction_horizontal 336 | other_col_width = 0 if num_cols == 1 else (1 - current_col_width) / (num_cols - 1) 337 | 338 | cols = [0.0] 339 | for i in range(num_cols): 340 | cols.append(cols[i] + (current_col_width if i == current_col else other_col_width)) 341 | 342 | current_row = current_cell[1] 343 | num_rows = len(rows) - 1 344 | 345 | current_row_height = 1 if num_rows == 1 else fraction_vertical 346 | other_row_height = 0 if num_rows == 1 else (1 - current_row_height) / (num_rows - 1) 347 | rows = [0.0] 348 | for i in range(num_rows): 349 | rows.append(rows[i] + (current_row_height if i == current_row else other_row_height)) 350 | 351 | layout = {"cols": cols, "rows": rows, "cells": cells} # type: sublime.Layout 352 | window.set_layout(layout) 353 | 354 | def unzoom_pane(self): 355 | window = self.window 356 | rows, cols, cells = self.get_layout() 357 | 358 | num_cols = len(cols) - 1 359 | col_width = 1.0 / num_cols 360 | 361 | cols = [0.0] 362 | for i in range(num_cols): 363 | cols.append(cols[i] + col_width) 364 | 365 | num_rows = len(rows) - 1 366 | row_height = 1.0 / num_rows 367 | 368 | rows = [0.0] 369 | for i in range(num_rows): 370 | rows.append(rows[i] + row_height) 371 | 372 | layout = {"cols": cols, "rows": rows, "cells": cells} # type: sublime.Layout 373 | window.set_layout(layout) 374 | 375 | def toggle_zoom(self, fraction): 376 | rows, cols, cells = self.get_layout() 377 | equal_spacing = True 378 | 379 | num_cols = len(cols) - 1 380 | col_width = 1 / num_cols 381 | 382 | for i, c in enumerate(cols): 383 | if c != i * col_width: 384 | equal_spacing = False 385 | break 386 | 387 | num_rows = len(rows) - 1 388 | row_height = 1 / num_rows 389 | 390 | for i, r in enumerate(rows): 391 | if r != i * row_height: 392 | equal_spacing = False 393 | break 394 | 395 | if equal_spacing: 396 | self.zoom_pane(fraction) 397 | else: 398 | self.unzoom_pane() 399 | 400 | def create_pane(self, direction, give_focus=False, destroy_old_if_empty=False): 401 | window = self.window 402 | rows, cols, cells = self.get_layout() 403 | current_group = window.active_group() 404 | 405 | old_cell = cells.pop(current_group) 406 | new_cell = [] 407 | 408 | if direction in ("up", "down"): 409 | cells = push_down_cells_after(cells, old_cell[YMAX]) 410 | rows.insert(old_cell[YMAX], (rows[old_cell[YMIN]] + rows[old_cell[YMAX]]) / 2) 411 | new_cell = [old_cell[XMIN], old_cell[YMAX], old_cell[XMAX], old_cell[YMAX] + 1] 412 | old_cell = [old_cell[XMIN], old_cell[YMIN], old_cell[XMAX], old_cell[YMAX]] 413 | 414 | elif direction in ("right", "left"): 415 | cells = push_right_cells_after(cells, old_cell[XMAX]) 416 | cols.insert(old_cell[XMAX], (cols[old_cell[XMIN]] + cols[old_cell[XMAX]]) / 2) 417 | new_cell = [old_cell[XMAX], old_cell[YMIN], old_cell[XMAX] + 1, old_cell[YMAX]] 418 | old_cell = [old_cell[XMIN], old_cell[YMIN], old_cell[XMAX], old_cell[YMAX]] 419 | 420 | if new_cell: 421 | if direction in ("left", "up"): 422 | focused_cell = new_cell 423 | unfocused_cell = old_cell 424 | else: 425 | focused_cell = old_cell 426 | unfocused_cell = new_cell 427 | cells.insert(current_group, focused_cell) 428 | cells.append(unfocused_cell) 429 | layout = {"cols": cols, "rows": rows, "cells": cells} # type: sublime.Layout 430 | window.set_layout(layout) 431 | 432 | if give_focus: 433 | self.travel_to_pane(direction, False, destroy_old_if_empty) 434 | 435 | def destroy_current_pane(self): 436 | # Out of the four adjacent panes, one was split to create this pane. 437 | # Find out which one, move to it, then destroy this pane. 438 | cells = self.get_cells() 439 | 440 | current = cells[self.window.active_group()] 441 | 442 | target_dir = None 443 | for dir in ("up", "right", "down", "left"): 444 | c = self.adjacent_cell(dir) 445 | if not c: 446 | continue 447 | if dir in ("up", "down"): 448 | if c[XMIN] == current[XMIN] and c[XMAX] == current[XMAX]: 449 | target_dir = dir 450 | elif dir in ("left", "right"): 451 | if c[YMIN] == current[YMIN] and c[YMAX] == current[YMAX]: 452 | target_dir = dir 453 | if target_dir: 454 | self.travel_to_pane(target_dir) 455 | self.destroy_pane(opposite_direction(target_dir)) 456 | 457 | def destroy_pane(self, direction, only_on_empty=False): 458 | if direction == "self": 459 | self.destroy_current_pane() 460 | return 461 | 462 | window = self.window 463 | rows, cols, cells = self.get_layout() 464 | current_group = window.active_group() 465 | 466 | cell_to_remove = None 467 | current_cell = cells[current_group] 468 | 469 | adjacent_cells = cells_adjacent_to_cell_in_direction(cells, current_cell, direction) 470 | if len(adjacent_cells) == 1: 471 | cell_to_remove = adjacent_cells[0] 472 | 473 | if cell_to_remove: 474 | active_view = window.active_view() 475 | group_to_remove = cells.index(cell_to_remove) 476 | has_content = len(window.sheets_in_group(group_to_remove)) > 0 477 | if only_on_empty and has_content: 478 | return 479 | 480 | dupe_views = self.duplicated_views(current_group, group_to_remove) 481 | for d in dupe_views: 482 | window.focus_view(d) 483 | window.run_command('close') 484 | if active_view: 485 | window.focus_view(active_view) 486 | 487 | cells.remove(cell_to_remove) 488 | if direction == "up": 489 | rows.pop(cell_to_remove[YMAX]) 490 | adjacent_cells = cells_adjacent_to_cell_in_direction(cells, cell_to_remove, "down") 491 | for cell in adjacent_cells: 492 | cells[cells.index(cell)][YMIN] = cell_to_remove[YMIN] 493 | cells = pull_up_cells_after(cells, cell_to_remove[YMAX]) 494 | elif direction == "right": 495 | cols.pop(cell_to_remove[XMIN]) 496 | adjacent_cells = cells_adjacent_to_cell_in_direction(cells, cell_to_remove, "left") 497 | for cell in adjacent_cells: 498 | cells[cells.index(cell)][XMAX] = cell_to_remove[XMAX] 499 | cells = pull_left_cells_after(cells, cell_to_remove[XMIN]) 500 | elif direction == "down": 501 | rows.pop(cell_to_remove[YMIN]) 502 | adjacent_cells = cells_adjacent_to_cell_in_direction(cells, cell_to_remove, "up") 503 | for cell in adjacent_cells: 504 | cells[cells.index(cell)][YMAX] = cell_to_remove[YMAX] 505 | cells = pull_up_cells_after(cells, cell_to_remove[YMIN]) 506 | elif direction == "left": 507 | cols.pop(cell_to_remove[XMAX]) 508 | adjacent_cells = cells_adjacent_to_cell_in_direction(cells, cell_to_remove, "right") 509 | for cell in adjacent_cells: 510 | cells[cells.index(cell)][XMIN] = cell_to_remove[XMIN] 511 | cells = pull_left_cells_after(cells, cell_to_remove[XMAX]) 512 | 513 | layout = {"cols": cols, "rows": rows, "cells": cells} # type: sublime.Layout 514 | window.set_layout(layout) 515 | 516 | def pull_file_from_pane(self, direction): 517 | adjacent_cell = self.adjacent_cell(direction) 518 | 519 | if adjacent_cell: 520 | cells = self.get_cells() 521 | group_index = cells.index(adjacent_cell) 522 | 523 | view = self.window.active_view_in_group(group_index) 524 | 525 | if view: 526 | active_group_index = self.window.active_group() 527 | views_in_group = self.window.views_in_group(active_group_index) 528 | self.window.set_view_index(view, active_group_index, len(views_in_group)) 529 | 530 | 531 | class TravelToPaneCommand(PaneCommand, WithSettings): 532 | def run(self, direction, create_new_if_necessary=None, destroy_old_if_empty=None): 533 | if create_new_if_necessary is None: 534 | create_new_if_necessary = self.settings().get('create_new_pane_if_necessary') 535 | if destroy_old_if_empty is None: 536 | destroy_old_if_empty = self.settings().get('destroy_empty_panes') 537 | self.travel_to_pane(direction, create_new_if_necessary, destroy_old_if_empty) 538 | 539 | 540 | class CarryFileToPaneCommand(PaneCommand, WithSettings): 541 | def run(self, direction, create_new_if_necessary=None, destroy_old_if_empty=None): 542 | if create_new_if_necessary is None: 543 | create_new_if_necessary = self.settings().get('create_new_pane_if_necessary') 544 | if destroy_old_if_empty is None: 545 | destroy_old_if_empty = self.settings().get('destroy_empty_panes') 546 | self.carry_file_to_pane(direction, create_new_if_necessary, destroy_old_if_empty) 547 | 548 | 549 | class CloneFileToPaneCommand(PaneCommand, WithSettings): 550 | def run(self, direction, create_new_if_necessary=None): 551 | if create_new_if_necessary is None: 552 | create_new_if_necessary = self.settings().get('create_new_pane_if_necessary') 553 | self.clone_file_to_pane(direction, create_new_if_necessary) 554 | 555 | 556 | class CreatePaneWithFileCommand(PaneCommand): 557 | def run(self, direction): 558 | self.create_pane(direction) 559 | self.pull_file_from_pane(opposite_direction(direction)) 560 | 561 | 562 | class CreatePaneWithClonedFileCommand(PaneCommand): 563 | def run(self, direction): 564 | self.create_pane(direction) 565 | self.clone_file_to_pane(direction) 566 | 567 | 568 | class PullFileFromPaneCommand(PaneCommand): 569 | def run(self, direction): 570 | self.pull_file_from_pane(direction) 571 | 572 | 573 | class ZoomPaneCommand(PaneCommand): 574 | def run(self, fraction=None): 575 | self.zoom_pane(fraction) 576 | 577 | 578 | class UnzoomPaneCommand(PaneCommand): 579 | def run(self): 580 | self.unzoom_pane() 581 | 582 | 583 | class ToggleZoomPaneCommand(PaneCommand): 584 | def run(self, fraction=None): 585 | self.toggle_zoom(fraction) 586 | 587 | 588 | class CreatePaneCommand(PaneCommand): 589 | def run(self, direction, give_focus=False): 590 | self.create_pane(direction, give_focus) 591 | 592 | 593 | class DestroyPaneCommand(PaneCommand): 594 | def run(self, direction): 595 | self.destroy_pane(direction) 596 | 597 | 598 | class ResizePaneCommand(PaneCommand): 599 | def run(self, orientation, mode=None): 600 | if mode is None: 601 | mode = "NEAREST" 602 | self.resize_panes(orientation, mode) 603 | 604 | 605 | class ReorderPaneCommand(PaneCommand): 606 | def run(self): 607 | self.reorder_panes() 608 | 609 | 610 | class SaveLayoutCommand(PaneCommand, WithSettings): 611 | """Save the current layout configuration in a settings file.""" 612 | 613 | def __init__(self, window): 614 | self.window = window 615 | super(SaveLayoutCommand, self).__init__(window) 616 | 617 | def on_done(self, nickname): 618 | saved_layouts = self.settings().get('saved_layouts') 619 | layout_names = [layout['nickname'] for layout in saved_layouts] 620 | layout_data = self.get_layout() 621 | 622 | if nickname in layout_names: 623 | dialog_str = ('You already have a layout stored as "{0}".\n\n' 624 | 'Do you want to continue and overwrite that layout?'.format(nickname)) 625 | dialog_btn = 'Overwrite layout' 626 | 627 | if sublime.ok_cancel_dialog(dialog_str, dialog_btn): 628 | def get_index(seq, attr, value): 629 | return next(i for (i, d) in enumerate(seq) if d[attr] == value) 630 | 631 | layout = saved_layouts[get_index(saved_layouts, 'nickname', nickname)] 632 | layout['rows'] = layout_data[0] 633 | layout['cols'] = layout_data[1] 634 | layout['cells'] = layout_data[2] 635 | else: 636 | self.window.run_command("save_layout") 637 | return 638 | else: 639 | layout = { 640 | 'nickname': nickname, 641 | 'rows': layout_data[0], 642 | 'cols': layout_data[1], 643 | 'cells': layout_data[2], 644 | } 645 | saved_layouts.append(layout) 646 | 647 | self.settings().set('saved_layouts', saved_layouts) 648 | sublime.save_settings(SETTINGS_FILENAME) 649 | 650 | def run(self): 651 | self.window.show_input_panel( 652 | 'Window layout nickname:', 653 | '', 654 | self.on_done, 655 | None, 656 | None 657 | ) 658 | 659 | 660 | class RestoreLayoutCommand(PaneCommand, WithSettings): 661 | """Restore a saved layout from a settings file.""" 662 | 663 | def __init__(self, window): 664 | self.window = window 665 | super(RestoreLayoutCommand, self).__init__(window) 666 | 667 | def on_done(self, index): 668 | saved_layouts = self.settings().get('saved_layouts') 669 | 670 | if index != -1: 671 | selected_layout = saved_layouts[index] 672 | layout = { 673 | 'cells': selected_layout['cells'], 674 | 'cols': selected_layout['cols'], 675 | 'rows': selected_layout['rows'] 676 | } # type: sublime.Layout 677 | self.window.set_layout(layout) 678 | 679 | def run(self): 680 | if self.settings().has('saved_layouts'): 681 | saved_layouts = self.settings().get('saved_layouts') 682 | layout_names = [layout['nickname'] for layout in saved_layouts] 683 | self.window.show_quick_panel(layout_names, self.on_done) 684 | 685 | 686 | class RemoveLayoutCommand(PaneCommand, WithSettings): 687 | """Remove a previously saved layout from your settings file.""" 688 | 689 | def __init__(self, window): 690 | self.window = window 691 | super(RemoveLayoutCommand, self).__init__(window) 692 | 693 | def on_done(self, index): 694 | saved_layouts = self.settings().get('saved_layouts') 695 | 696 | if index != -1: 697 | saved_layouts.pop(index) 698 | self.settings().set('saved_layouts', saved_layouts) 699 | sublime.save_settings(SETTINGS_FILENAME) 700 | 701 | def run(self): 702 | if self.settings().has('saved_layouts'): 703 | saved_layouts = self.settings().get('saved_layouts') 704 | layout_names = [layout['nickname'] for layout in saved_layouts] 705 | self.window.show_quick_panel(layout_names, self.on_done) 706 | 707 | 708 | class NewWindowFromSavedLayoutCommand(PaneCommand, WithSettings): 709 | """ 710 | Brings up a list of saved views and allows the user 711 | to create a new window using that layout. 712 | """ 713 | 714 | def __init__(self, window): 715 | self.window = window 716 | super(NewWindowFromSavedLayoutCommand, self).__init__(window) 717 | 718 | def on_done(self, index): 719 | saved_layouts = self.settings().get('saved_layouts') 720 | 721 | if index != -1: 722 | selected_layout = saved_layouts[index] 723 | layout = { 724 | 'cells': selected_layout['cells'], 725 | 'cols': selected_layout['cols'], 726 | 'rows': selected_layout['rows'] 727 | } # type: sublime.Layout 728 | 729 | self.window.run_command("new_window") 730 | new_window = sublime.active_window() 731 | new_window.set_layout(layout) 732 | 733 | def run(self): 734 | if self.settings().has('saved_layouts'): 735 | saved_layouts = self.settings().get('saved_layouts') 736 | layout_names = [layout['nickname'] for layout in saved_layouts] 737 | self.window.show_quick_panel(layout_names, self.on_done) 738 | 739 | 740 | class NewWindowWithCurrentLayoutCommand(PaneCommand): 741 | """Opens a new window using the current layout settings.""" 742 | 743 | def __init__(self, window): 744 | self.window = window 745 | super(NewWindowWithCurrentLayoutCommand, self).__init__(window) 746 | 747 | def run(self): 748 | layout = self.window.layout() 749 | self.window.run_command("new_window") 750 | new_window = sublime.active_window() 751 | new_window.set_layout(layout) 752 | 753 | 754 | class AutoCloseEmptyPanes(sublime_plugin.EventListener, WithSettings): 755 | def is_tabless_view(self, view): 756 | """When you make a new pane, it comes with a tabless view that gets a 757 | tab when you type into it. You also get a similar view when using the 758 | command palette to open a file. 759 | If we think it's this kind of view, return True.""" 760 | if sublime.version()[0] == '2': 761 | return False 762 | elif view.window() and view.window().get_view_index(view)[1] == -1: 763 | return True 764 | return False 765 | 766 | def on_close(self, view): 767 | if sublime.version()[0] == '2': 768 | self.on_pre_close(view) 769 | 770 | def on_pre_close(self, view): 771 | # Read from global settings for backward compatibility 772 | auto_close = view.settings().get("origami_auto_close_empty_panes") 773 | if auto_close is None: 774 | auto_close = self.settings().get("auto_close_empty_panes") 775 | if not auto_close: 776 | return 777 | 778 | window = view.window() 779 | if window is None: 780 | # If we're the last view in the window, then the window closes 781 | # before on_pre_close is called (!!). 782 | # In this case, we don't want to close anything extra because the 783 | # window is already closing. 784 | return 785 | active_group = window.active_group() 786 | 787 | if self.is_tabless_view(view): 788 | # We don't want to close the pane when closing a transient view 789 | return 790 | 791 | # We're in pre_close, so use set_timeout to close the group right after this. 792 | if len(window.views_in_group(active_group)) == 1: 793 | sublime.set_timeout(lambda: window.run_command("destroy_pane", {"direction": "self"}), 0) 794 | 795 | 796 | class AutoZoomOnFocus(sublime_plugin.EventListener, WithSettings): 797 | running = False 798 | active_group = -1 799 | 800 | def delayed_zoom(self, view, fraction): 801 | # zoom_pane hangs sublime if you destroy the pane above or to your left. 802 | # call it in a sublime.set_timeout to fix the issue 803 | 804 | # Sublime Text 2 has issues on startup where views don't have windows yet. 805 | # If we don't have a window yet, bail. 806 | if view.window() is None: 807 | self.running = False 808 | return 809 | 810 | args = {} 811 | # Work correctly if someone sets "origami_auto_zoom_on_focus": true rather 812 | # than e.g. "origami_auto_zoom_on_focus": .8. 813 | if fraction is not True: 814 | args["fraction"] = fraction 815 | view.window().run_command("zoom_pane", args) 816 | self.running = False 817 | 818 | def on_activated(self, view): 819 | if self.running: 820 | return 821 | # Read from global settings for backward compatibility 822 | fraction = view.settings().get("origami_auto_zoom_on_focus") 823 | if fraction is None: 824 | fraction = self.settings().get("auto_zoom_on_focus") 825 | if not fraction: 826 | return 827 | if view.settings().get("is_widget"): 828 | return 829 | 830 | new_active_group = view.window().active_group() 831 | if new_active_group == self.active_group: 832 | return 833 | 834 | self.active_group = new_active_group 835 | self.running = True 836 | 837 | sublime.set_timeout(lambda: self.delayed_zoom(view, fraction), 0) 838 | --------------------------------------------------------------------------------