├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── keymaps ├── nerd-treeview.cson ├── tree-view.cson └── vim-mode.cson ├── lib ├── nerd-treeview.coffee └── search-view.coffee ├── package.json └── styles └── nerd-treeview.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.5 2 | * Fixed stay commands that misbehaved (dcalhoun) 3 | 4 | ## 0.5.4 5 | * Added support for Atom 1.17.0 (thanks dcalhoun for the bug report) 6 | 7 | ## 0.5.3 8 | * Readme text adjustments (dcalhoun) 9 | * Fixed crash when trying to delete project root with D 10 | 11 | ## 0.5.2 12 | * Implemented simple search 13 | 14 | ## 0.5.1 15 | * Better navigation - more vim like (ctrl + f/b/d/u) 16 | * Scroll up and down (ctrl + e/y) are now fixed 17 | 18 | ## 0.5.0 19 | * Fix by markovicdenis and jondot of jquery functions 20 | * Removed scroll up and down (ctrl e/y) because of malfunction 21 | 22 | ## 0.4.3 23 | * Fix by averrin for duplicate key '?' which is 'reverse search' not help 24 | 25 | ## 0.4.2 26 | * Fixed open-stay when no buffers are opened or when active buffer is not saved 27 | 28 | ## 0.4.1 29 | * Fixed saving state while changing root which was broken 30 | 31 | ## 0.4.0 32 | * Changed Hide VSC ignored mapping from H to h to avoid conflicting with 33 | 'move to the top of the screen mapping' (Thanks, dcalhoun) 34 | * Fixed -stay methods that retain focus on tree view 35 | This tree-view commit broke it initially: 36 | atom/tree-view@a2b9827fdcbb2aa798854ef50ce3e204723777e6 37 | * Fixed tree being unable to open (#6 and #3) 38 | 39 | ## 0.3.1 40 | * Fixed going down (j) error when cursor stopped in case of (file in dir in dir 41 | case) 42 | 43 | ## 0.3.0 44 | * To simplify keymap management keymaps were separated into: 45 | * tree-view: default Tree View **toggle and focus** mappings (C-\, A-\, etc) 46 | * nerd-treeview: nerd treeview mappings 47 | * vim-mode: nerd treeview navigation (j, k, gg, G, etc) 48 | * Added missing Vim bindings (enter, +, -, etc) 49 | * Implemented number-prefixed jumps (5j, 6k, etc) 50 | * G can now jump to the line 51 | * Added functionality to copy selected file name (not full path) 52 | * Fixed issue when navigating down doesn't work for opened folders without files 53 | 54 | ## 0.2.3 55 | * Code cleaning and readme/changelog updates 56 | * Added missing Vim mappings for navigation (top arrow, down arrow) 57 | 58 | ## 0.2.2 - Update for Readme 59 | * Cosmetic Readme updates 60 | 61 | ## 0.2.1 - Fixed Wrong pane item 62 | * Fixed Issue #1: When opened tab is not TextEditor (e.g. About page) 63 | opening new file crashes 64 | 65 | ## 0.2.0 - Smooth Scroll 66 | * j, k, J, K, C-J, C-K, gg and G will now smoothly scroll 67 | * Fixed the issue with hidden files begin selected when moving with keyboard 68 | 69 | ## 0.1.0 - Initial Release 70 | * Initial Release 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 sQu1rr 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nerd TreeView 2 | 3 | ### Unfortunately, I switched back to NeoVim and I am not planning any improvements in the future 4 | 5 | The Nerd TreeView package will transform the native atom Tree View to somewhat 6 | similar to the famous NERD Tree Vim plugin - hence the name 7 | 8 | Some of the NERD Tree's functionality is present in the native Tree View by 9 | default, but it doesn't expose any interface so apart from CSS developers 10 | can't really change much. This package will try to replicate Vim's original 11 | NERD Tree functionality 12 | 13 | ## Current functionality 14 | * Most of the key VIM NERD Tree default key bindings are working 15 | * Completely replaces standard Tree View bindings 16 | * Smooth scroll 17 | * Search - accepts js regular expressions only, without slashes or flags 18 | 19 | ## Planned/Not Available functionality 20 | * Bookmarks (Integrate with Core Bookmarks?) 21 | * NERD Tree menu (Subtree management) 22 | * cd/CD (through ex-mode extention) 23 | * Try to use vim-mode functionality where/if possible 24 | * Expose API through service 25 | * Show line numbers on demand (both relative and absolute) 26 | 27 | ## Installation Notes 28 | * **This is first, and very unstable version** 29 | * Best to use in conjunction with [vim-mode-plus](https://atom.io/packages/vim-mode-plus) 30 | and [vim-mode-plus-ex-mode](https://atom.io/packages/vim-mode-plus-ex-mode) 31 | * Repetated operations (e.g. 10j to jump up 10 times) require disabling native 32 | tree view keybindings 33 | 34 | ## Tips 35 | #### Use ZZ to save-and-close current tab/split 36 | See [vim-mode-zz](https://atom.io/packages/vim-mode-zz) 37 | 38 | ## Default key bindings and events 39 | 40 | ### General 41 | 42 | Key | Event | Description 43 | --- | ----- | ----------- 44 | C-\, D-\ (mac), C-k C-b, D-k D-b (mac) | nerd-treeview:toggle | toggle the tree (default) 45 | A-\, C-0 (mac) | nerd-treeview:toggle-focus | activate the tree (default) 46 | C-|, D-| (mac) | nerd-treeview:reveal-active-file | jump selection to the active file 47 | ZZ, q | nerd-treeview:toggle | hides tree View 48 | 49 | ### Open Files 50 | 51 | Key | Event | Description 52 | --- | ----- | ----------- 53 | o, | nerd-treeview:open | open file or toggle folder 54 | go | nerd-treeview:open-stay | same as "o" but tree stays active 55 | t | nerd-treeview:open-tab | open selected file in a new tab 56 | gt | nerd-treeview:open-tab-stay | same as "t" but tree stays active 57 | T | nerd-treeview:add | same as "t" but current tab will stay active 58 | gT | nerd-treeview:add-tab-stay | same as "T" but tree stays active 59 | i | nerd-treeview:open-split-vertical | split open file vertically downwards 60 | gi | nerd-treeview:open-split-vertical-stay | same as "i" but tree stays active 61 | s | nerd-treeview:open-split-horizontal | split open file horizontally to the right 62 | gs | nerd-treeview:open-split-horizontal-stay | same as "s" but tree stays active 63 | 64 | ### Interact with Folders 65 | 66 | Key | Event | Description 67 | --- | ----- | ----------- 68 | O | nerd-treeview:expand | recursively expand directory 69 | x | nerd-treeview:close-parent | close parent directory 70 | X | nerd-treeview:close-children | close children directories recursively 71 | e | nerd-treeview:open-tree | add selected folder as a new project root 72 | E | nerd-treeview:open-tree-stay | same as "e" but cursor stays where it is 73 | 74 | ### Navigation 75 | 76 | Key | Event | Description | Can be prefixed 77 | --- | ----- | ----------- | --------------- 78 | j, +, down | nerd-treeview:jump-down | move cursor down | **YES** 79 | k, -, up | nerd-treeview:jump-up | move cursor up | **YES** 80 | K | nerd-treeview:jump-first | jump cursor to the first element in this folder | NO 81 | J | nerd-treeview:jump-last | jump cursor to the last element in this folder | NO 82 | C-J | nerd-treeview:jump-next | jump to the next sibling | **YES** 83 | C-K | nerd-treeview:jump-prev | jump to the previous sibling | **YES** 84 | gg | core:move-to-top | move to the top | NO 85 | G | nerd-treeview:jump-line | move to the bottom | **YES** 86 | P | nerd-treeview:jump-root | jump cursor to the current root folder | NO 87 | p | nerd-treeview:jump-parent | jump cursor to the parent folder | **YES** 88 | H | nerd-treeview:move-to-top-of-screen | Select top line | NO 89 | L | nerd-treeview:move-to-bottom-of-screen | Select bottom line | NO 90 | M | nerd-treeview:move-to-middle-of-screen | Select middle line | NO 91 | 92 | ### Tree modification 93 | 94 | Key | Event | Description 95 | --- | ----- | ----------- 96 | c | nerd-treeview:change-root | set selected directory as root 97 | C | nerd-treeview:change-root | set selected directory as root saving folder expansion state 98 | u | nerd-treeview:change-root | set root's parent directory as root 99 | U | nerd-treeview:change-root | set root's parent directory as root saving folder expansion state 100 | I | nerd-treeview:toggle-ignored-names | toggle visibility of hidden files 101 | h | nerd-treeview:toggle-vcs-ignored-files | toggle visibility of hidden VCS files 102 | F | nerd-treeview:toggle-files | toggle visibility of files 103 | 104 | ### Filesystem interaction 105 | 106 | Key | Event | Description 107 | --- | ----- | ----------- 108 | Y | nerd-treeview:copy-full-path | copy full path of the selected file 109 | yn | nerd-treeview:copy-name | copy file name **without** extension 110 | yN | nerd-treeview:copy-name-ext | copy file name **with** extension 111 | a | nerd-treeview:add-file | create new file 112 | A | nerd-treeview:add-folder | create new folder 113 | D | nerd-treeview:remove | delete file or folder, or remove project root from workspace 114 | mm | nerd-treeview:move | rename/move 115 | mp | nerd-treeview:paste | paste 116 | yp | nerd-treeview:duplicate | duplicate 117 | yy | nerd-treeview:copy | copy 118 | dd | nerd-treeview:cut | cut 119 | 120 | ### Scroll 121 | 122 | Key | Event | Description 123 | --- | ----- | ----------- 124 | C-u | nerd-treeview:scroll-half-screen-up | Scroll half screen up 125 | C-b | nerd-treeview:scroll-full-screen-up | Scroll full screen up 126 | C-d | nerd-treeview:scroll-half-screen-down | Scroll half screen down 127 | C-f | nerd-treeview:scroll-full-screen-down | Scroll full screen down 128 | C-e | nerd-treeview:scroll-down | Scroll Down 129 | C-y | nerd-treeview:scroll-up | Scroll Up 130 | 131 | ### Zoom 132 | 133 | Key | Event | Description 134 | --- | ----- | ----------- 135 | z, zt | nerd-treeview:scroll-cursor-to-top | scroll current line to the top 136 | z., zz | nerd-treeview:scroll-cursor-to-middle | scroll current line to the middle 137 | z-, zb | nerd-treeview:scroll-cursor-to-bottom | scroll current line to the bottom 138 | 139 | ### Search 140 | 141 | Key | Event | Description 142 | --- | ----- | ----------- 143 | / | nerd-treeview:search | search 144 | ? | nerd-treeview:reverse-search | reverse search 145 | n | nerd-treeview:repeat-search | go to the next match **(can be prefixed)** 146 | N | nerd-treeview:repeat-search-backwards | go to the previous match **(can be prefixed)** 147 | : | nerd-treeview:search-clear-highlight | clear match highlight 148 | -------------------------------------------------------------------------------- /keymaps/nerd-treeview.cson: -------------------------------------------------------------------------------- 1 | '.tree-view': 2 | 'q': 'nerd-treeview:toggle' 3 | 4 | 'o': 'nerd-treeview:open' 5 | 'enter': 'nerd-treeview:open' 6 | 'g o': 'nerd-treeview:open-stay' 7 | 8 | 't': 'nerd-treeview:open-tab' 9 | 'g t': 'nerd-treeview:open-tab-stay' 10 | 'T': 'nerd-treeview:add-tab' 11 | 'g T': 'nerd-treeview:add-tab-stay' 12 | 13 | 'i': 'nerd-treeview:open-split-vertical' 14 | 'g i': 'nerd-treeview:open-split-vertical-stay' 15 | 's': 'nerd-treeview:open-split-horizontal' 16 | 'g s': 'nerd-treeview:open-split-horizontal-stay' 17 | 18 | 'O': 'nerd-treeview:expand' 19 | 'x': 'nerd-treeview:close-parent' 20 | 'X': 'nerd-treeview:close-children' 21 | 'e': 'nerd-treeview:open-tree' 22 | 'E': 'nerd-treeview:open-tree-stay' 23 | 24 | 'P': 'nerd-treeview:jump-root' 25 | 'p': 'nerd-treeview:jump-parent' 26 | 'K': 'nerd-treeview:jump-first' 27 | 'J': 'nerd-treeview:jump-last' 28 | 'ctrl-J': 'nerd-treeview:jump-next' 29 | 'ctrl-K': 'nerd-treeview:jump-prev' 30 | 31 | 'c': 'nerd-treeview:change-root' 32 | 'C': 'nerd-treeview:change-root-save-state' 33 | 'u': 'nerd-treeview:up' 34 | 'U': 'nerd-treeview:up-save-state' 35 | 36 | 'I': 'nerd-treeview:toggle-ignored-names' 37 | 'h': 'nerd-treeview:toggle-vcs-ignored-files' 38 | 'F': 'nerd-treeview:toggle-files' 39 | 40 | 'a': 'nerd-treeview:add-file' 41 | 'A': 'nerd-treeview:add-folder' 42 | 'D': 'nerd-treeview:remove' 43 | 'Y': 'nerd-treeview:copy-full-path' 44 | 'y n': 'nerd-treeview:copy-name' 45 | 'y N': 'nerd-treeview:copy-name-ext' 46 | 'm m': 'nerd-treeview:move' 47 | 'm p': 'nerd-treeview:paste' 48 | 'y p': 'nerd-treeview:duplicate' 49 | 'y y': 'nerd-treeview:copy' 50 | 'd d': 'nerd-treeview:cut' 51 | 52 | '/': 'nerd-treeview:search' 53 | '?': 'nerd-treeview:reverse-search' 54 | 'n': 'nerd-treeview:repeat-search' 55 | 'N': 'nerd-treeview:repeat-search-backwards' 56 | ':': 'nerd-treeview:search-clear-highlight' 57 | -------------------------------------------------------------------------------- /keymaps/tree-view.cson: -------------------------------------------------------------------------------- 1 | '.platform-darwin': 2 | 'cmd-\\': 'nerd-treeview:toggle' 3 | 'cmd-k cmd-b': 'nerd-treeview:toggle' 4 | 'cmd-|': 'nerd-treeview:reveal-active-file' 5 | 'ctrl-0': 'nerd-treeview:toggle-focus' 6 | 7 | '.platform-win32, .platform-linux': 8 | 'ctrl-\\': 'nerd-treeview:toggle' 9 | 'ctrl-k ctrl-b': 'nerd-treeview:toggle' 10 | 'ctrl-|': 'nerd-treeview:reveal-active-file' 11 | 'alt-\\': 'nerd-treeview:toggle-focus' 12 | -------------------------------------------------------------------------------- /keymaps/vim-mode.cson: -------------------------------------------------------------------------------- 1 | '.tree-view': 2 | 'k': 'nerd-treeview:jump-up' 3 | 'up': 'nerd-treeview:jump-up' 4 | '-': 'nerd-treeview:jump-up' 5 | 6 | 'j': 'nerd-treeview:jump-down' 7 | 'down': 'nerd-treeview:jump-down' 8 | '+': 'nerd-treeview:jump-down' 9 | 10 | 'g g': 'nerd-treeview:jump-top' 11 | 'G': 'nerd-treeview:jump-line' 12 | 13 | 'Z Z': 'nerd-treeview:toggle' 14 | 15 | 'ctrl-u': 'nerd-treeview:scroll-half-screen-up' 16 | 'ctrl-b': 'nerd-treeview:scroll-full-screen-up' 17 | 'ctrl-d': 'nerd-treeview:scroll-half-screen-down' 18 | 'ctrl-f': 'nerd-treeview:scroll-full-screen-down' 19 | 'ctrl-e': 'nerd-treeview:scroll-down' 20 | 'ctrl-y': 'nerd-treeview:scroll-up' 21 | 22 | 'z enter': 'nerd-treeview:scroll-cursor-to-top' 23 | 'z t': 'nerd-treeview:scroll-cursor-to-top' 24 | 'z .': 'nerd-treeview:scroll-cursor-to-middle' 25 | 'z z': 'nerd-treeview:scroll-cursor-to-middle' 26 | 'z -': 'nerd-treeview:scroll-cursor-to-bottom' 27 | 'z b': 'nerd-treeview:scroll-cursor-to-bottom' 28 | 29 | 'H': 'nerd-treeview:move-to-top-of-screen' 30 | 'L': 'nerd-treeview:move-to-bottom-of-screen' 31 | 'M': 'nerd-treeview:move-to-middle-of-screen' 32 | 33 | '/': 'nerd-treeview:search' 34 | '?': 'nerd-treeview:reverse-search' 35 | 'n': 'nerd-treeview:repeat-search' 36 | 'N': 'nerd-treeview:repeat-search-backwards' 37 | 38 | '1': 'nerd-treeview:repeat-prefix' 39 | '2': 'nerd-treeview:repeat-prefix' 40 | '3': 'nerd-treeview:repeat-prefix' 41 | '4': 'nerd-treeview:repeat-prefix' 42 | '5': 'nerd-treeview:repeat-prefix' 43 | '6': 'nerd-treeview:repeat-prefix' 44 | '7': 'nerd-treeview:repeat-prefix' 45 | '8': 'nerd-treeview:repeat-prefix' 46 | '9': 'nerd-treeview:repeat-prefix' 47 | '0': 'nerd-treeview:repeat-prefix' 48 | 49 | 'escape': 'nerd-treeview:clear-prefix' 50 | 'ctrl-[': 'nerd-treeview:clear-prefix' 51 | 'ctrl-c': 'nerd-treeview:clear-prefix' 52 | -------------------------------------------------------------------------------- /lib/nerd-treeview.coffee: -------------------------------------------------------------------------------- 1 | $ = jQuery = require 'jquery' 2 | SearchView = require './search-view' 3 | 4 | # influenced by https://github.com/customd/jquery-visible 5 | visible = ($e, $tree) -> 6 | top = $e.offset().top 7 | bottom = top + $e.height() 8 | treeTop = $tree.offset().top 9 | treeBottom = treeTop + $tree.height() 10 | return top >= treeTop and bottom <= treeBottom 11 | 12 | scrollIfInvisible = ($e, $tree) -> 13 | if not visible($e, $tree) 14 | $e[0]?.scrollIntoView($e.offset().top < $tree.offset().top) 15 | 16 | toggleConfig = (keyPath) -> 17 | atom.config.set(keyPath, not atom.config.get(keyPath)) 18 | 19 | wrapTreeView = (treeView) -> 20 | if !(treeView instanceof jQuery) 21 | if treeView.nodeType == 1 22 | return $(treeView) 23 | else if treeView.element.nodeType == 1 24 | return $(treeView.element) 25 | return treeView 26 | 27 | module.exports = 28 | num: 0 29 | 30 | regex: null 31 | 32 | openCallbacks: [] 33 | 34 | activate: -> 35 | atom.commands.add('body', { 36 | 'nerd-treeview:toggle': => 37 | if not @delegate('toggle') 38 | atom.commands.dispatch( 39 | atom.views.getView(atom.workspace), 40 | 'tree-view:toggle' 41 | ) 42 | 'nerd-treeview:reveal-active-file': => 43 | @delegate('revealActiveFile') 44 | 'nerd-treeview:toggle-focus': => 45 | @delegate('toggleFocus') 46 | }) 47 | atom.commands.add('.tree-view', { 48 | 'nerd-treeview:open': => @open(true) 49 | 'nerd-treeview:open-stay': => @open(false) 50 | 'nerd-treeview:open-tab': => @openTab(true) 51 | 'nerd-treeview:open-tab-stay': => @openTab(false) 52 | 53 | 'nerd-treeview:add-tab': => @addTab(true) 54 | 'nerd-treeview:add-tab-stay': => @addTab(false) 55 | 56 | 'nerd-treeview:open-split-vertical': => @splitVertical(true) 57 | 'nerd-treeview:open-split-vertical-stay': => @splitVertical(false) 58 | 'nerd-treeview:open-split-horizontal': => @splitHorizontal(true) 59 | 'nerd-treeview:open-split-horizontal-stay': => 60 | @splitHorizontal(false) 61 | 62 | 'nerd-treeview:expand': => @expand(true) 63 | 64 | 'nerd-treeview:close-parent': => @closeParent() 65 | 'nerd-treeview:close-children': => @closeChildren() 66 | 67 | 'nerd-treeview:open-tree': => @openTree(true) 68 | 'nerd-treeview:open-tree-stay': => @openTree(false) 69 | 70 | 'nerd-treeview:jump-up': => @jumpUp() 71 | 'nerd-treeview:jump-down': => @jumpDown() 72 | 'nerd-treeview:jump-root': => @jumpRoot() 73 | 'nerd-treeview:jump-parent': => @jumpParent() 74 | 'nerd-treeview:jump-first': => @jumpFirst() 75 | 'nerd-treeview:jump-last': => @jumpLast() 76 | 'nerd-treeview:jump-next': => @jumpNext() 77 | 'nerd-treeview:jump-prev': => @jumpPrev() 78 | 'nerd-treeview:jump-top': => @jumpLine(1) 79 | 'nerd-treeview:jump-line': => @jumpLine() 80 | 81 | 'nerd-treeview:change-root': => @changeRoot(false, false) 82 | 'nerd-treeview:change-root-save-state': => @changeRoot(false, true) 83 | 'nerd-treeview:up': => @changeRoot(true, false) 84 | 'nerd-treeview:up-save-state': => @changeRoot(true, true) 85 | 86 | 'nerd-treeview:toggle-ignored-names': 87 | -> toggleConfig 'tree-view.hideIgnoredNames' 88 | 'nerd-treeview:toggle-vcs-ignored-files': 89 | -> toggleConfig 'tree-view.hideVcsIgnoredFiles' 90 | 'nerd-treeview:toggle-files': => @toggleFiles() 91 | 92 | 'nerd-treeview:add-file': => @delegate('add', true) 93 | 'nerd-treeview:add-folder': => @delegate('add') 94 | 'nerd-treeview:copy-full-path': 95 | => @delegate('copySelectedEntryPath') 96 | 'nerd-treeview:remove': => @remove() 97 | 'nerd-treeview:copy-name': => @copyName(false) 98 | 'nerd-treeview:copy-name-ext': => @copyName(true) 99 | 100 | 'nerd-treeview:move': => @delegate('moveSelectedEntry') 101 | 'nerd-treeview:paste': => @delegate('pasteEntries') 102 | 'nerd-treeview:duplicate': => @delegate('copySelectedEntry') 103 | 'nerd-treeview:copy': => @delegate('copySelectedEntries') 104 | 'nerd-treeview:cut': => @delegate('cutSelectedEntries') 105 | 106 | 'nerd-treeview:scroll-up': => @scroll(false) 107 | 'nerd-treeview:scroll-down': => @scroll(true) 108 | 'nerd-treeview:scroll-half-screen-up': => 109 | @scrollScreen(false, false) 110 | 'nerd-treeview:scroll-half-screen-down': => 111 | @scrollScreen(true, false) 112 | 'nerd-treeview:scroll-full-screen-up': => 113 | @scrollScreen(false, true) 114 | 'nerd-treeview:scroll-full-screen-down': => 115 | @scrollScreen(true, true) 116 | 117 | 'nerd-treeview:scroll-cursor-to-top': => @cursor(true) 118 | 'nerd-treeview:scroll-cursor-to-middle': => @centreCursor() 119 | 'nerd-treeview:scroll-cursor-to-bottom': => @cursor(false) 120 | 121 | 'nerd-treeview:move-to-top-of-screen': => @move('top') 122 | 'nerd-treeview:move-to-middle-of-screen': => @move('middle') 123 | 'nerd-treeview:move-to-bottom-of-screen': => @move('bottom') 124 | 125 | 'nerd-treeview:repeat-prefix': (e) => @prefix(e) 126 | 'nerd-treeview:clear-prefix': => @clearPrefix() 127 | 128 | 'nerd-treeview:search': => @search(false) 129 | 'nerd-treeview:reverse-search': => @search(true) 130 | 'nerd-treeview:repeat-search': => @find(false) 131 | 'nerd-treeview:repeat-search-backwards': => @find(true) 132 | 'nerd-treeview:search-clear-highlight': => @noh() 133 | }) 134 | 135 | atom.workspace.onDidOpen (e) => 136 | callback(e) for callback in @openCallbacks 137 | @openCallbacks = [] 138 | 139 | getTreeView: -> 140 | treeView = atom.packages.getActivePackage('tree-view') 141 | treeView = treeView?.mainModule.treeView 142 | 143 | root = $('.project-root')[0] 144 | if treeView and root and not treeView.selectedEntry() 145 | treeView.selectEntry(root) 146 | 147 | return treeView 148 | 149 | clearPrefix: -> 150 | @num = 0 151 | 152 | delegate: (method, arg) -> 153 | @clearPrefix() 154 | 155 | return if not treeView = @getTreeView() 156 | treeView[method](arg) 157 | return true 158 | 159 | open: (activatePane) -> 160 | @clearPrefix() 161 | 162 | return if not treeView = @getTreeView() 163 | activePane = atom.workspace.getCenter().getActivePane() 164 | 165 | selected = treeView.selectedEntry() 166 | if not $(selected).is('.file') 167 | return treeView.openSelectedEntry({activatePane}) 168 | 169 | item = atom.workspace.getCenter().getActivePaneItem() 170 | replace = item and !item.isModified?() 171 | 172 | same = false 173 | for paneItem in activePane.getItems() 174 | if (selected.getPath() == paneItem.getPath?()) 175 | same = true 176 | break 177 | 178 | if not (item and same) 179 | if replace 180 | treeView.openSelectedEntry({activatePane}) 181 | @openCallbacks.push -> item?.destroy() 182 | else 183 | treeView.openSelectedEntryDown() 184 | if not activatePane 185 | @openCallbacks.push => @delegate('toggleFocus') 186 | else treeView.openSelectedEntry({activatePane}) 187 | 188 | openTab: (activatePane) -> 189 | @clearPrefix() 190 | 191 | return if not treeView = @getTreeView() 192 | treeView.openSelectedEntry({activatePane}) 193 | 194 | addTab: (activatePane) -> 195 | @clearPrefix() 196 | 197 | return if not treeView = @getTreeView() 198 | treeView.openSelectedEntry({activatePane}) 199 | 200 | activePane = atom.workspace.getCenter().getActivePane() 201 | item = activePane.getActiveItem() 202 | if item 203 | selected = treeView.selectedEntry() 204 | @openCallbacks.push -> 205 | activePane.activateItem(item) 206 | treeView.selectEntry(selected) 207 | 208 | splitVertical: (activatePane) -> 209 | @clearPrefix() 210 | 211 | return if not treeView = @getTreeView() 212 | treeView.openSelectedEntryDown({activatePane}) 213 | @openCallbacks.push -> treeView.focus() unless activatePane 214 | 215 | splitHorizontal: (activatePane) -> 216 | @clearPrefix() 217 | 218 | return if not treeView = @getTreeView() 219 | treeView.openSelectedEntryRight({activatePane}) 220 | @openCallbacks.push -> treeView.focus() unless activatePane 221 | 222 | expand: (recursive) -> 223 | @clearPrefix() 224 | 225 | return if not treeView = @getTreeView() 226 | treeView.expandDirectory(recursive) 227 | 228 | closeParent: -> 229 | @clearPrefix() 230 | 231 | return if not treeView = @getTreeView() 232 | selected = treeView.selectedEntry() 233 | directory = $(selected).parents('.directory')[0] 234 | if directory 235 | directory.collapse() 236 | treeView.selectEntry(directory) 237 | 238 | closeChildren: -> 239 | @clearPrefix() 240 | 241 | return if not treeView = @getTreeView() 242 | 243 | selected = treeView.selectedEntry() 244 | directories = $(selected).find('>ol>li.directory') 245 | directory.collapse(true) for directory in directories 246 | 247 | openTree: (activate, path) -> 248 | @clearPrefix() 249 | 250 | return if not treeView = @getTreeView() 251 | 252 | if not path 253 | selected = treeView.selectedEntry() 254 | if $(selected).is('.directory') 255 | path = selected.getPath() 256 | 257 | if path 258 | atom.project.addPath(path) 259 | 260 | treeView.selectEntry($('.project-root').last()[0]) if activate 261 | 262 | return true 263 | 264 | jump: (getNode) -> 265 | return if not treeView = @getTreeView() 266 | 267 | selected = treeView.selectedEntry() 268 | node = getNode(selected)[0] 269 | if node 270 | $treeView = wrapTreeView(treeView); 271 | treeView.selectEntry(node) 272 | scrollIfInvisible($(node).find('.name').eq(0), $treeView) 273 | 274 | getNextEntry: (selected) -> 275 | node = $(selected) 276 | if node.is('.directory.expanded') and node.find('li:visible').size() 277 | li = node.find('li:visible') 278 | node = li.first() if li.size() 279 | else 280 | while node.size() and not node.next(':visible').size() 281 | node = node.parents('.directory:visible').eq(0) 282 | node = node.next(':visible') if node.size() 283 | return node 284 | 285 | getPrevEntry: (selected) -> 286 | node = $(selected).prev(':visible') 287 | if not node.size() 288 | node = $(selected).parents('.directory:visible').eq(0) 289 | else if node.is('.directory.expanded') 290 | li = node.find('li:visible') 291 | node = li.last() if li.size() 292 | return node 293 | 294 | repeatJump: (selected, getNode) -> 295 | oldSelection = $('#non-existing-id') 296 | for _ in [0..Math.max(@num - 1, 0)] 297 | selected = getNode(selected) 298 | break unless selected.size() 299 | oldSelection = selected 300 | 301 | @clearPrefix() 302 | return oldSelection 303 | 304 | jumpUp: -> 305 | @jump (selected) => @repeatJump(selected, @getPrevEntry) 306 | 307 | jumpDown: -> 308 | @jump (selected) => @repeatJump(selected, @getNextEntry) 309 | 310 | jumpRoot: -> 311 | @jump (selected) -> $(selected).parents('.project-root:visible') 312 | 313 | jumpParent: -> 314 | @jump (selected) => 315 | @repeatJump(selected, (selected) -> 316 | $(selected).parents('.directory:visible') 317 | ) 318 | 319 | jumpFirst: -> 320 | @jump (selected) -> $(selected).parent().children('li:visible').first() 321 | 322 | jumpLast: -> 323 | @jump (selected) -> $(selected).parent().children('li:visible').last() 324 | 325 | jumpNext: -> 326 | @jump (selected) => @repeatJump(selected, (selected) -> 327 | $(selected).next(':visible') 328 | ) 329 | 330 | jumpPrev: -> 331 | @jump (selected) => @repeatJump(selected, (selected) -> 332 | $(selected).prev(':visible') 333 | ) 334 | 335 | jumpLine: (num) -> 336 | return if not treeView = @getTreeView() 337 | $treeView = wrapTreeView(treeView); 338 | $elements = $treeView.find('li:visible') 339 | 340 | if not num 341 | num = if @num then @num else $elements.size() 342 | num = $elements.size() if num > $elements.size() 343 | 344 | $entry = $elements.eq(num - 1) 345 | treeView.selectEntry($entry[0]) 346 | scrollIfInvisible($entry, $treeView) 347 | 348 | @clearPrefix() 349 | 350 | moveInArray: (array, i) -> 351 | array.splice(i, 0, array.splice(array.length - 1, 1)[0]) 352 | 353 | rootIndex: (path, rootDirectories) -> 354 | for directory, i in rootDirectories 355 | if directory.getPath() == path 356 | return i 357 | return null 358 | 359 | fixState: (treeView, up, root, selected, state, index) -> 360 | if up 361 | (entries = {})[root.directory.name] = state 362 | state = 363 | isExpanded: true, entries: entries 364 | selection = $("span[data-path='#{selected.directory.path}]") 365 | .closest('li')[0] 366 | else 367 | selection = $('.project-root')[index] 368 | 369 | (expansionState = {})[treeView.roots[index].getPath()] = state 370 | treeView.updateRoots(expansionState) 371 | treeView.selectEntry(selection) 372 | 373 | changeRoot: (up, state) -> 374 | @clearPrefix() 375 | 376 | return if not treeView = @getTreeView() 377 | 378 | selected = treeView.selectedEntry() 379 | $selected = $(selected) 380 | if not $selected.is('.directory') 381 | selected = $selected.parents('.directory')[0] 382 | 383 | rootDirectories = atom.project.getDirectories() 384 | root = $selected.closest('.project-root')[0] 385 | index = @rootIndex(root.getPath(), rootDirectories) 386 | 387 | path = selected?.getPath() 388 | path = rootDirectories[index].getParent().getPath() if up 389 | return if path == root.getPath() 390 | 391 | lastIndex = rootDirectories.length 392 | ser = (if up then root else selected).directory 393 | .serializeExpansionState() 394 | if @openTree(false, path) and rootDirectories.length > lastIndex 395 | @fixState(treeView, up, root, selected, ser, lastIndex) if state 396 | @moveInArray(atom.project.rootDirectories, index) 397 | @moveInArray(atom.project.repositories, index) 398 | atom.project.removePath(root.getPath()) 399 | 400 | toggleFiles: -> 401 | @clearPrefix() 402 | 403 | return if not treeView = @getTreeView() 404 | wrapTreeView(treeView).toggleClass('hide-files') 405 | 406 | remove: -> 407 | @clearPrefix() 408 | 409 | return if not treeView = @getTreeView() 410 | selected = treeView.selectedEntry() 411 | 412 | if $(selected).is('.project-root') 413 | treeView.removeProjectFolder( 414 | target: $(selected).find('.header .name').get(0) 415 | ) 416 | else treeView.removeSelectedEntries() 417 | 418 | copyName: (ext) -> 419 | @clearPrefix() 420 | 421 | return if not treeView = @getTreeView() 422 | 423 | selected = treeView.selectedEntry() 424 | name = $(selected).find('.name').first().data('name') 425 | name = name.replace(/\.[^\.]+$/, '') unless ext or /^\./.test(name) 426 | atom.clipboard.write(name) 427 | 428 | scroll: (down) -> 429 | @clearPrefix() 430 | 431 | return if not treeView = @getTreeView() 432 | $treeView = wrapTreeView(treeView); 433 | 434 | elHeight = $treeView.find('li:visible:last').height(); 435 | curScroll = treeView.scrollTop() 436 | treeView.scrollTop(curScroll + elHeight * (if down then 1 else -1)) 437 | 438 | scrollScreen: (down, full) -> 439 | @clearPrefix() 440 | 441 | return if not treeView = @getTreeView() 442 | $treeView = wrapTreeView(treeView); 443 | $selected = $(treeView.selectedEntry()) 444 | 445 | treeHeight = $treeView.height(); 446 | elHeight = $treeView.find('li:visible:last').height(); 447 | 448 | D = if full then 1 else 2 449 | @num = Math.floor(treeHeight / elHeight / D); 450 | 451 | if down then @jumpDown() else @jumpUp() 452 | if not full then @centreCursor() 453 | 454 | cursor: (up) -> 455 | @clearPrefix() 456 | 457 | return if not treeView = @getTreeView() 458 | $treeView = wrapTreeView(treeView); 459 | $selected = $(treeView.selectedEntry()).find('.name').eq(0) 460 | 461 | top = $selected.offset().top 462 | treeTop = $treeView.offset().top 463 | 464 | target = if up then treeTop else treeTop + $treeView.height() 465 | source = if up then top else top + $selected.height() 466 | 467 | curScroll = treeView.scrollTop() 468 | treeView.scrollTop(curScroll + source - target) 469 | 470 | centreCursor: -> 471 | @clearPrefix() 472 | 473 | return if not treeView = @getTreeView() 474 | $treeView = wrapTreeView(treeView); 475 | $selected = $(treeView.selectedEntry()).find('.name').eq(0) 476 | 477 | middle = parseInt($selected.offset().top + $selected.height() / 2) 478 | treeMiddle = parseInt($treeView.offset().top + $treeView.height() / 2) 479 | 480 | curScroll = treeView.scrollTop() 481 | treeView.scrollTop(curScroll + middle - treeMiddle) 482 | 483 | move: (where) -> 484 | @clearPrefix() 485 | 486 | return if not treeView = @getTreeView() 487 | $treeView = wrapTreeView(treeView); 488 | 489 | centre = parseInt(($treeView.offset().left + $treeView.width()) / 2) 490 | if where is 'top' 491 | point = $treeView.offset().top + 16 492 | else if where is 'middle' 493 | point = parseInt($treeView.offset().top + $treeView.height() / 2) 494 | else 495 | point = $treeView.height() - $treeView.offset().top - 16 496 | 497 | $e = $(document.elementFromPoint(centre, point)) 498 | .closest('li:visible') 499 | 500 | if not $e.size() 501 | $e = $treeView.find('li:visible') 502 | $e = if where is 'top' then $e.first() else $e.last() 503 | 504 | treeView.selectEntry($e[0]) if $e.size() 505 | 506 | prefix: (e) -> 507 | keyboardEvent = e.originalEvent?.originalEvent ? e.originalEvent 508 | num = parseInt(atom.keymaps.keystrokeForKeyboardEvent(keyboardEvent)) 509 | @num = @num * 10 + num 510 | 511 | # TODO: temp fix to prevent freeze, ideally each function should handle 512 | @num = 9999 if @num > 9999 513 | 514 | search: (backwards) -> 515 | @clearPrefix() 516 | 517 | @view = new SearchView((regex) => 518 | @searchObj = 519 | regex: regex, 520 | backwards: backwards 521 | 522 | setTimeout((=> @find()), 1) # to prevent try/catch 523 | ) 524 | @view.attach() 525 | 526 | find: (backwards) -> 527 | if not @searchObj then return 528 | return if not treeView = @getTreeView() 529 | $treeView = wrapTreeView(treeView); 530 | 531 | if @searchObj.backwards then backwards = not backwards 532 | 533 | elements = $treeView.find('span.name').get() 534 | if backwards then elements.reverse() 535 | 536 | highlighted = treeView.selectedEntry() 537 | 538 | if highlighted and elements.length 539 | highlighted = $(highlighted).find('span.name')[0] 540 | while elements[0] != highlighted 541 | temp = elements.shift() 542 | elements.push(temp) 543 | temp = elements.shift() 544 | elements.push(temp) 545 | 546 | next = null 547 | for element in elements 548 | if element.innerText.match(@searchObj.regex) 549 | next = element 550 | if @num then @num -= 1 551 | else break 552 | 553 | $treeView.find('span.name span.match').parent().each(-> 554 | $(@).html($(@).text()) 555 | ) 556 | 557 | if next 558 | replace = '$&' 559 | next.innerHTML = next.innerText.replace(@searchObj.regex, replace) 560 | 561 | treeView.selectEntry($(next).parents('li')[0]) 562 | scrollIfInvisible($(next), $treeView) 563 | 564 | @clearPrefix() 565 | 566 | noh: () -> 567 | @clearPrefix() 568 | 569 | if not @searchObj then return 570 | 571 | return if not treeView = @getTreeView() 572 | $treeView = wrapTreeView(treeView); 573 | 574 | $treeView.find('span.name span.match').parent().each(-> 575 | $(@).html($(@).text()) 576 | ) 577 | -------------------------------------------------------------------------------- /lib/search-view.coffee: -------------------------------------------------------------------------------- 1 | {TextEditor, CompositeDisposable, Disposable} = require 'atom' 2 | 3 | module.exports = 4 | class SearchView 5 | nochange: false 6 | 7 | constructor: (@callback) -> 8 | @disposables = new CompositeDisposable() 9 | 10 | # root 11 | @element = document.createElement('div') 12 | @element.classList.add('tree-view-dialog') 13 | 14 | # blur 15 | @miniEditor = new TextEditor({mini: true}) 16 | blurHandler = => @close() if document.hasFocus() 17 | @miniEditor.element.addEventListener('blur', blurHandler) 18 | @disposables.add(new Disposable(=> 19 | @miniEditor.element.removeEventListener('blur', blurHandler) 20 | )) 21 | 22 | # colour change 23 | @miniEditor.onDidStopChanging(=> 24 | if not @nochange 25 | @miniEditor.element.style.color = 'white' 26 | ) 27 | 28 | # append to root 29 | @element.appendChild(@miniEditor.element) 30 | 31 | atom.commands.add @element, 32 | 'core:confirm': => @confirm() 33 | 'core:cancel': => @close() 34 | 35 | attach: -> 36 | @panel = atom.workspace.addModalPanel(item: this) 37 | @miniEditor.element.focus() 38 | @miniEditor.scrollToCursorPosition() 39 | 40 | close: -> 41 | panelToDestroy = @panel 42 | @panel = null 43 | panelToDestroy?.destroy() 44 | @disposables.dispose() 45 | @miniEditor.destroy() 46 | document.querySelector('.tree-view')?.focus() 47 | 48 | confirm: -> 49 | @nochange = true 50 | text = @miniEditor.getText() 51 | 52 | try 53 | @callback(new RegExp(text, 'i')) 54 | @close() 55 | catch e 56 | @miniEditor.element.style.color = 'red' 57 | setTimeout((=> @nochange = false), 400) 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerd-treeview", 3 | "main": "./lib/nerd-treeview", 4 | "version": "0.5.4", 5 | "coffeelintConfig": { 6 | "indentation": { 7 | "level": "error", 8 | "value": 4 9 | }, 10 | "line_endings": { 11 | "value": "unix", 12 | "level": "error" 13 | } 14 | }, 15 | "description": "Vim-like NERD Tree behaviour for Atom.io Tree View", 16 | "keywords": [ 17 | "treeview", 18 | "nerdtree", 19 | "tree", 20 | "nerd", 21 | "vim", 22 | "bindings" 23 | ], 24 | "repository": "https://github.com/sQu1rr/nerd-treeview", 25 | "license": "MIT", 26 | "engines": { 27 | "atom": ">=1.0.0 <2.0.0" 28 | }, 29 | "dependencies": { 30 | "jquery": "^2.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /styles/nerd-treeview.less: -------------------------------------------------------------------------------- 1 | @import "syntax-variables"; 2 | 3 | .hide-files .tree-view li.file { 4 | display: none; 5 | } 6 | 7 | @search-match-base: @syntax-result-marker-color; 8 | @search-match: hsla(hue(@search-match-base), saturation(@search-match-base), lightness(@search-match-base), 0.4); 9 | @search-match-border: hsla(hue(@search-match-base), saturation(@search-match-base), lightness(@search-match-base), 1.0); 10 | 11 | .tree-view:not(.noh) span.name span.match { 12 | background-color: @search-match; 13 | border: 1px solid @search-match-border; 14 | } 15 | --------------------------------------------------------------------------------