Refacto: rewrite everything
- The tree is created with libuv functions, which makes it blazingly fast. - The tree may now be faster than any other vim trees, it can handle directories with thousands of files without any latency at all (tested on 40K files, works flawlessly). - More solid logic for opening and closing the tree. - tree state is remembered (closing / opening a folder keeps opened subdirectories open) - detection of multiple git projects in the tree - more icon support - smart rendering - smart updates - ms windows support - gx replacement function running xdg-open on linux, open on macos
This commit is contained in:
parent
afc86a9623
commit
e0bfcb4a6f
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
|||||||
Tree.lua is a simple tree for neovim
|
Tree.lua is a simple tree for neovim
|
||||||
Copyright © 2012 Yazdani Kiyan
|
Copyright © 2019 Yazdani Kiyan
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
68
README.md
68
README.md
@ -2,13 +2,23 @@
|
|||||||
|
|
||||||
## Notice
|
## Notice
|
||||||
|
|
||||||
- This plugin does not work on windows.
|
This plugin doesn't support windows. \
|
||||||
|
This plugin requires [neovim nightly](https://github.com/neovim/neovim/wiki/Installing-Neovim). \
|
||||||
|
You can switch to commit `afc86a9` if you use neovim 0.4.x. \
|
||||||
|
Note that the old version has less features and is much slower than the new one.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Install with [vim-plug](https://github.com/junegunn/vim-plug):
|
Install with [vim-plug](https://github.com/junegunn/vim-plug):
|
||||||
```vim
|
```vim
|
||||||
|
" master (neovim git)
|
||||||
|
Plug 'kyazdani42/nvim-web-devicons' " for file icons
|
||||||
Plug 'kyazdani42/nvim-tree.lua'
|
Plug 'kyazdani42/nvim-tree.lua'
|
||||||
|
|
||||||
|
" old version that runs on neovim 0.4.x
|
||||||
|
Plug 'kyazdani42/nvim-tree.lua' { 'commit': 'afc86a9' }
|
||||||
|
" for icons in old version
|
||||||
|
Plug 'ryanoasis/vim-devicons'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
@ -16,11 +26,10 @@ Plug 'kyazdani42/nvim-tree.lua'
|
|||||||
```vim
|
```vim
|
||||||
let g:lua_tree_side = 'right' | 'left' "left by default
|
let g:lua_tree_side = 'right' | 'left' "left by default
|
||||||
let g:lua_tree_size = 40 "30 by default
|
let g:lua_tree_size = 40 "30 by default
|
||||||
let g:lua_tree_ignore = [ '.git', 'node_modules', '.cache' ] "empty by default, not working on mac atm
|
let g:lua_tree_ignore = [ '.git', 'node_modules', '.cache' ] "empty by default
|
||||||
let g:lua_tree_auto_open = 1 "0 by default, opens the tree when typing `vim $DIR` or `vim`
|
let g:lua_tree_auto_open = 1 "0 by default, opens the tree when typing `vim $DIR` or `vim`
|
||||||
let g:lua_tree_auto_close = 1 "0 by default, closes the tree when it's the last window
|
let g:lua_tree_auto_close = 1 "0 by default, closes the tree when it's the last window
|
||||||
let g:lua_tree_follow = 1 "0 by default, this option will bind BufEnter to the LuaTreeFindFile command
|
let g:lua_tree_follow = 1 "0 by default, this option allows the cursor to be updated when entering a buffer
|
||||||
" :help LuaTreeFindFile for more info
|
|
||||||
let g:lua_tree_show_icons = {
|
let g:lua_tree_show_icons = {
|
||||||
\ 'git': 1,
|
\ 'git': 1,
|
||||||
\ 'folders': 0,
|
\ 'folders': 0,
|
||||||
@ -28,7 +37,7 @@ let g:lua_tree_show_icons = {
|
|||||||
\}
|
\}
|
||||||
"If 0, do not show the icons for one of 'git' 'folder' and 'files'
|
"If 0, do not show the icons for one of 'git' 'folder' and 'files'
|
||||||
"1 by default, notice that if 'files' is 1, it will only display
|
"1 by default, notice that if 'files' is 1, it will only display
|
||||||
"if web-devicons is installed and on your runtimepath
|
"if nvim-web-devicons is installed and on your runtimepath
|
||||||
|
|
||||||
" You can edit keybindings be defining this variable
|
" You can edit keybindings be defining this variable
|
||||||
" You don't have to define all keys.
|
" You don't have to define all keys.
|
||||||
@ -44,14 +53,27 @@ let g:lua_tree_bindings = {
|
|||||||
\ 'rename': 'r'
|
\ 'rename': 'r'
|
||||||
\ }
|
\ }
|
||||||
|
|
||||||
|
" default will show icon by default if no icon is provided
|
||||||
|
" default shows no icon by default
|
||||||
|
let g:lua_tree_icons = {
|
||||||
|
\ 'default': '',
|
||||||
|
\ 'git': {
|
||||||
|
\ 'unstaged': "✗",
|
||||||
|
\ 'staged': "✓",
|
||||||
|
\ 'unmerged': "═",
|
||||||
|
\ 'renamed': "➜",
|
||||||
|
\ 'untracked': "★"
|
||||||
|
\ }
|
||||||
|
\ }
|
||||||
|
|
||||||
nnoremap <C-n> :LuaTreeToggle<CR>
|
nnoremap <C-n> :LuaTreeToggle<CR>
|
||||||
nnoremap <leader>r :LuaTreeRefresh<CR>
|
nnoremap <leader>r :LuaTreeRefresh<CR>
|
||||||
nnoremap <leader>n :LuaTreeFindFile<CR>
|
nnoremap <leader>n :LuaTreeFindFile<CR>
|
||||||
|
" LuaTreeOpen and LuaTreeClose are also available if you need them
|
||||||
|
|
||||||
set termguicolors " this variable must be enabled for colors to be applied properly
|
set termguicolors " this variable must be enabled for colors to be applied properly
|
||||||
|
|
||||||
" a list of groups can be found at `:help lua_tree_highlight`
|
" a list of groups can be found at `:help lua_tree_highlight`
|
||||||
highlight LuaTreeFolderName guibg=cyan gui=bold,underline
|
|
||||||
highlight LuaTreeFolderIcon guibg=blue
|
highlight LuaTreeFolderIcon guibg=blue
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -60,33 +82,33 @@ highlight LuaTreeFolderIcon guibg=blue
|
|||||||
- move around like in any vim buffer
|
- move around like in any vim buffer
|
||||||
- `<CR>` on `..` will cd in the above directory
|
- `<CR>` on `..` will cd in the above directory
|
||||||
- `.` will cd in the directory under the cursor
|
- `.` will cd in the directory under the cursor
|
||||||
- type `a` to add a file
|
- type `a` to add a file. Adding a directory requires leaving a leading `/` at the end of the path.
|
||||||
|
> you can add multiple directories by doing foo/bar/baz/f and it will add foo bar and baz directories and f as a file
|
||||||
- type `r` to rename a file
|
- type `r` to rename a file
|
||||||
- type `d` to delete a file (will prompt for confirmation)
|
- type `d` to delete a file (will prompt for confirmation)
|
||||||
- if the file is a directory, `<CR>` will open the directory
|
- if the file is a directory, `<CR>` will open the directory otherwise it will open the file in the buffer near the tree
|
||||||
- otherwise it will open the file in the buffer near the tree
|
- if the file is a symlink, `<CR>` will follow the symlink (if the target is a file)
|
||||||
- if the file is a symlink, `<CR>` will follow the symlink
|
|
||||||
- type `<C-v>` will open the file in a vertical split
|
- type `<C-v>` will open the file in a vertical split
|
||||||
- type `<C-x>` will open the file in a horizontal split
|
- type `<C-x>` will open the file in a horizontal split
|
||||||
- type `<C-t>` will open the file in a new tab
|
- type `<C-t>` will open the file in a new tab
|
||||||
|
- type `gx` to open the file with the `open` command on MACOS and `xdg-open` in linux
|
||||||
- Double left click acts like `<CR>`
|
- Double left click acts like `<CR>`
|
||||||
- Double right click acts like `.`
|
- Double right click acts like `.`
|
||||||
|
|
||||||
|
## Note
|
||||||
|
|
||||||
|
This plugin is very fast because it uses the `libuv` `scandir` and `scandir_next` functions instead of spawning an `ls` process which can get slow on large files when combining with `stat` to get file informations.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- [x] Open file in current buffer or in split with FzF like bindings (`<CR>`, `<C-v>`, `<C-x>`, `<C-t>`)
|
- Open file in current buffer or in split with FzF like bindings (`<CR>`, `<C-v>`, `<C-x>`, `<C-t>`)
|
||||||
- [x] File icons with vim-devicons
|
- File icons with nvim-web-devicons
|
||||||
- [x] Syntax highlighting ([exa](https://github.com/ogham/exa) like)
|
- Syntax highlighting ([exa](https://github.com/ogham/exa) like)
|
||||||
- [x] Change directory with `.`
|
- Change directory with `.`
|
||||||
- [x] Add / Rename / delete files
|
- Add / Rename / delete files
|
||||||
- [x] Git integration
|
- Git integration
|
||||||
- [x] Mouse support
|
- Mouse support
|
||||||
|
- It's fast
|
||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
- Tree creation could be async
|
|
||||||
- bufferize tree
|
|
||||||
- better default colors (use vim highlight groups)
|
|
||||||
|
|||||||
@ -7,6 +7,8 @@ Author: Yazdani Kiyan <yazdani.kiyan@protonmail.com>
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
INTRODUCTION *nvim-tree-introduction*
|
INTRODUCTION *nvim-tree-introduction*
|
||||||
|
|
||||||
|
This file explorer doesn't work on windows and requires neovim `nightly`
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
QUICK START *nvim-tree-quickstart*
|
QUICK START *nvim-tree-quickstart*
|
||||||
|
|
||||||
@ -19,6 +21,14 @@ open the tree with :LuaTreeToggle
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
COMMANDS *nvim-tree-commands*
|
COMMANDS *nvim-tree-commands*
|
||||||
|
|
||||||
|
|:LuaTreeOpen| *:LuaTreeOpen*
|
||||||
|
|
||||||
|
opens the tree
|
||||||
|
|
||||||
|
|:LuaTreeClose| *:LuaTreeClose*
|
||||||
|
|
||||||
|
closes the tree
|
||||||
|
|
||||||
|:LuaTreeToggle| *:LuaTreeToggle*
|
|:LuaTreeToggle| *:LuaTreeToggle*
|
||||||
|
|
||||||
open or close the tree
|
open or close the tree
|
||||||
@ -32,7 +42,8 @@ refresh the tree
|
|||||||
The command will change the cursor in the tree for the current bufname.
|
The command will change the cursor in the tree for the current bufname.
|
||||||
|
|
||||||
It will also open the leafs of the tree leading to the file in the buffer
|
It will also open the leafs of the tree leading to the file in the buffer
|
||||||
(if you opened a file with something else than the LuaTree, like `fzf`)
|
(if you opened a file with something else than the LuaTree, like `fzf` or
|
||||||
|
`:split`)
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
OPTIONS *nvim-tree-options*
|
OPTIONS *nvim-tree-options*
|
||||||
@ -48,8 +59,8 @@ where the window will open (default to 'left')
|
|||||||
|
|
||||||
|g:lua_tree_ignore| *g:lua_tree_ignore*
|
|g:lua_tree_ignore| *g:lua_tree_ignore*
|
||||||
|
|
||||||
An array of strings that the tree won't display.
|
An array of strings that the tree won't load and display.
|
||||||
Each pattern is passed into the 'ls' function as `--ignore=PATTERN`
|
useful to hide large data/cache folders.
|
||||||
>
|
>
|
||||||
example: let g:lua_tree_ignore = [ '.git', 'node_modules' ]
|
example: let g:lua_tree_ignore = [ '.git', 'node_modules' ]
|
||||||
|
|
||||||
@ -66,12 +77,30 @@ can disable icons per type:
|
|||||||
\}
|
\}
|
||||||
|
|
||||||
Can be one of `1` and `0` for each key. By default the tree will try
|
Can be one of `1` and `0` for each key. By default the tree will try
|
||||||
to render the icons. The `icons` key can only work if `vim-devicons`
|
to render the icons. The `icons` key can only work if `nvim-web-devicons`
|
||||||
is installed and in your |runtimepath|
|
is installed and in your |runtimepath|
|
||||||
|
(https://github.com/kyazdani42/nvim-web-devicons)
|
||||||
|
|
||||||
|
|g:lua_tree_icons| *g:lua_tree_icons*
|
||||||
|
|
||||||
|
You can set some icons for the git status and the default icon that shows
|
||||||
|
when no icon is found for a file.
|
||||||
|
>
|
||||||
|
let g:lua_tree_icons = {
|
||||||
|
\ 'default': '',
|
||||||
|
\ 'git': {
|
||||||
|
\ 'unstaged': "✗",
|
||||||
|
\ 'staged': "✓",
|
||||||
|
\ 'unmerged': "═",
|
||||||
|
\ 'renamed': "➜",
|
||||||
|
\ 'untracked': "★"
|
||||||
|
\ }
|
||||||
|
\ }
|
||||||
|
<
|
||||||
|g:lua_tree_follow| *g:lua_tree_follow*
|
|g:lua_tree_follow| *g:lua_tree_follow*
|
||||||
|
|
||||||
Can be `0` or `1`. When `1`, will bind |:LuaTreeFindFile| to |BufEnter|
|
Can be `0` or `1`. When `1`, will update the cursor to update to the correct
|
||||||
|
location in the tree on |BufEnter|.
|
||||||
Default is 0
|
Default is 0
|
||||||
|
|
||||||
|g:lua_tree_auto_open| *g:lua_tree_auto_open*
|
|g:lua_tree_auto_open| *g:lua_tree_auto_open*
|
||||||
@ -105,6 +134,8 @@ INFORMATIONS *nvim-tree-info*
|
|||||||
- type '<C-v>' will open the file in a vertical split
|
- type '<C-v>' will open the file in a vertical split
|
||||||
- type '<C-x>' will open the file in a horizontal split
|
- type '<C-x>' will open the file in a horizontal split
|
||||||
- type '<C-t>' will open the file in a new tab
|
- type '<C-t>' will open the file in a new tab
|
||||||
|
- type 'gx' to open the file with the `open` command on macos and `xdg-open`
|
||||||
|
on linux.
|
||||||
|
|
||||||
- Double left click acts like '<CR>'
|
- Double left click acts like '<CR>'
|
||||||
- Double right click acts like '.'
|
- Double right click acts like '.'
|
||||||
@ -130,8 +161,7 @@ default keybindings will be applied to undefined keys.
|
|||||||
File icons with vim-devicons.
|
File icons with vim-devicons.
|
||||||
|
|
||||||
Uses other type of icons so a good font support is recommended.
|
Uses other type of icons so a good font support is recommended.
|
||||||
If the tree renders weird glyphs, install correct fonts or try to change
|
If the tree renders weird glyphs, install the correct fonts.
|
||||||
your terminal.
|
|
||||||
|
|
||||||
Syntax highlighting uses g:terminal_color_ from colorschemes, fallbacks to
|
Syntax highlighting uses g:terminal_color_ from colorschemes, fallbacks to
|
||||||
ugly colors otherwise.
|
ugly colors otherwise.
|
||||||
@ -140,8 +170,9 @@ Git integration tells when a file is:
|
|||||||
- ✗ unstaged or folder is dirty
|
- ✗ unstaged or folder is dirty
|
||||||
- ✓ staged
|
- ✓ staged
|
||||||
- ★ new file
|
- ★ new file
|
||||||
- ✓✗ partially staged
|
- ✓ ✗ partially staged
|
||||||
- ✓★ new file staged
|
- ✓ ★ new file staged
|
||||||
|
- ✓ ★ ✗ new file staged and has unstaged modifications
|
||||||
- ═ merging
|
- ═ merging
|
||||||
- ➜ renamed
|
- ➜ renamed
|
||||||
|
|
||||||
|
|||||||
3
doc/tags
3
doc/tags
@ -1,10 +1,13 @@
|
|||||||
|
:LuaTreeClose nvim-tree-lua.txt /*:LuaTreeClose*
|
||||||
:LuaTreeFindFile nvim-tree-lua.txt /*:LuaTreeFindFile*
|
:LuaTreeFindFile nvim-tree-lua.txt /*:LuaTreeFindFile*
|
||||||
|
:LuaTreeOpen nvim-tree-lua.txt /*:LuaTreeOpen*
|
||||||
:LuaTreeRefresh nvim-tree-lua.txt /*:LuaTreeRefresh*
|
:LuaTreeRefresh nvim-tree-lua.txt /*:LuaTreeRefresh*
|
||||||
:LuaTreeToggle nvim-tree-lua.txt /*:LuaTreeToggle*
|
:LuaTreeToggle nvim-tree-lua.txt /*:LuaTreeToggle*
|
||||||
g:lua_tree_auto_close nvim-tree-lua.txt /*g:lua_tree_auto_close*
|
g:lua_tree_auto_close nvim-tree-lua.txt /*g:lua_tree_auto_close*
|
||||||
g:lua_tree_auto_open nvim-tree-lua.txt /*g:lua_tree_auto_open*
|
g:lua_tree_auto_open nvim-tree-lua.txt /*g:lua_tree_auto_open*
|
||||||
g:lua_tree_bindings nvim-tree-lua.txt /*g:lua_tree_bindings*
|
g:lua_tree_bindings nvim-tree-lua.txt /*g:lua_tree_bindings*
|
||||||
g:lua_tree_follow nvim-tree-lua.txt /*g:lua_tree_follow*
|
g:lua_tree_follow nvim-tree-lua.txt /*g:lua_tree_follow*
|
||||||
|
g:lua_tree_icons nvim-tree-lua.txt /*g:lua_tree_icons*
|
||||||
g:lua_tree_ignore nvim-tree-lua.txt /*g:lua_tree_ignore*
|
g:lua_tree_ignore nvim-tree-lua.txt /*g:lua_tree_ignore*
|
||||||
g:lua_tree_show_icons nvim-tree-lua.txt /*g:lua_tree_show_icons*
|
g:lua_tree_show_icons nvim-tree-lua.txt /*g:lua_tree_show_icons*
|
||||||
g:lua_tree_side nvim-tree-lua.txt /*g:lua_tree_side*
|
g:lua_tree_side nvim-tree-lua.txt /*g:lua_tree_side*
|
||||||
|
|||||||
@ -1,11 +1,33 @@
|
|||||||
local api = vim.api
|
local api = vim.api
|
||||||
local get_colors = require 'lib/config'.get_colors
|
|
||||||
|
|
||||||
local colors = get_colors()
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function create_hl()
|
local function get_color_from_hl(hl_name, fallback)
|
||||||
|
local id = vim.api.nvim_get_hl_id_by_name(hl_name)
|
||||||
|
if not id then return fallback end
|
||||||
|
|
||||||
|
local hl = vim.api.nvim_get_hl_by_id(id, true)
|
||||||
|
if not hl or not hl.foreground then return fallback end
|
||||||
|
|
||||||
|
return hl.foreground
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_colors()
|
||||||
|
return {
|
||||||
|
red = vim.g.terminal_color_1 or get_color_from_hl('Keyword', 'Red'),
|
||||||
|
green = vim.g.terminal_color_2 or get_color_from_hl('Character', 'Green'),
|
||||||
|
yellow = vim.g.terminal_color_3 or get_color_from_hl('PreProc', 'Yellow'),
|
||||||
|
blue = vim.g.terminal_color_4 or get_color_from_hl('Include', 'Blue'),
|
||||||
|
purple = vim.g.terminal_color_5 or get_color_from_hl('Define', 'Purple'),
|
||||||
|
cyan = vim.g.terminal_color_6 or get_color_from_hl('Conditional', 'Cyan'),
|
||||||
|
dark_red = vim.g.terminal_color_9 or get_color_from_hl('Keyword', 'DarkRed'),
|
||||||
|
orange = vim.g.terminal_color_11 or get_color_from_hl('Number', 'Orange'),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_hl_groups()
|
||||||
|
local colors = get_colors()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Symlink = { gui = 'bold', fg = colors.cyan },
|
Symlink = { gui = 'bold', fg = colors.cyan },
|
||||||
FolderIcon = { fg = '#90a4ae' },
|
FolderIcon = { fg = '#90a4ae' },
|
||||||
@ -13,14 +35,22 @@ local function create_hl()
|
|||||||
ExecFile = { gui = 'bold', fg = colors.green },
|
ExecFile = { gui = 'bold', fg = colors.green },
|
||||||
SpecialFile = { gui = 'bold,underline', fg = colors.yellow },
|
SpecialFile = { gui = 'bold,underline', fg = colors.yellow },
|
||||||
ImageFile = { gui = 'bold', fg = colors.purple },
|
ImageFile = { gui = 'bold', fg = colors.purple },
|
||||||
MarkdownFile = { fg = colors.purple },
|
|
||||||
|
GitDirty = { fg = colors.dark_red },
|
||||||
|
GitStaged = { fg = colors.green },
|
||||||
|
GitMerge = { fg = colors.orange },
|
||||||
|
GitRenamed = { fg = colors.purple },
|
||||||
|
GitNew = { fg = colors.yellow },
|
||||||
|
|
||||||
|
-- TODO: remove those when we add this to nvim-web-devicons
|
||||||
|
MarkdownIcon = { fg = colors.purple },
|
||||||
LicenseIcon = { fg = colors.yellow },
|
LicenseIcon = { fg = colors.yellow },
|
||||||
YamlIcon = { fg = colors.yellow },
|
YamlIcon = { fg = colors.yellow },
|
||||||
TomlIcon = { fg = colors.yellow },
|
TomlIcon = { fg = colors.yellow },
|
||||||
GitignoreIcon = { fg = colors.yellow },
|
GitignoreIcon = { fg = colors.yellow },
|
||||||
JsonIcon = { fg = colors.yellow },
|
JsonIcon = { fg = colors.yellow },
|
||||||
|
|
||||||
LuaIcon = { fg = '#42a5f5' },
|
LuaIcon = { fg = '#42a5f5' },
|
||||||
|
GoIcon = { fg = '#7Fd5EA' },
|
||||||
PythonIcon = { fg = colors.green },
|
PythonIcon = { fg = colors.green },
|
||||||
ShellIcon = { fg = colors.green },
|
ShellIcon = { fg = colors.green },
|
||||||
JavascriptIcon = { fg = colors.yellow },
|
JavascriptIcon = { fg = colors.yellow },
|
||||||
@ -30,35 +60,62 @@ local function create_hl()
|
|||||||
RustIcon = { fg = colors.orange },
|
RustIcon = { fg = colors.orange },
|
||||||
VimIcon = { fg = colors.green },
|
VimIcon = { fg = colors.green },
|
||||||
TypescriptIcon = { fg = colors.blue },
|
TypescriptIcon = { fg = colors.blue },
|
||||||
|
|
||||||
GitDirty = { fg = colors.dark_red },
|
|
||||||
GitStaged = { fg = colors.green },
|
|
||||||
GitMerge = { fg = colors.orange },
|
|
||||||
GitRenamed = { fg = colors.purple },
|
|
||||||
GitNew = { fg = colors.yellow }
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local HIGHLIGHTS = create_hl()
|
-- TODO: remove those when we add this to nvim-web-devicons
|
||||||
|
M.hl_groups = {
|
||||||
|
['LICENSE'] = 'LicenseIcon';
|
||||||
|
['license'] = 'LicenseIcon';
|
||||||
|
['vim'] = 'VimIcon';
|
||||||
|
['.vimrc'] = 'VimIcon';
|
||||||
|
['c'] = 'CIcon';
|
||||||
|
['cpp'] = 'CIcon';
|
||||||
|
['python'] = 'PythonIcon';
|
||||||
|
['lua'] = 'LuaIcon';
|
||||||
|
['rs'] = 'RustIcon';
|
||||||
|
['sh'] = 'ShellIcon';
|
||||||
|
['csh'] = 'ShellIcon';
|
||||||
|
['zsh'] = 'ShellIcon';
|
||||||
|
['bash'] = 'ShellIcon';
|
||||||
|
['md'] = 'MarkdownIcon';
|
||||||
|
['json'] = 'JsonIcon';
|
||||||
|
['toml'] = 'TomlIcon';
|
||||||
|
['go'] = 'GoIcon';
|
||||||
|
['yaml'] = 'YamlIcon';
|
||||||
|
['yml'] = 'YamlIcon';
|
||||||
|
['conf'] = 'GitignoreIcon';
|
||||||
|
['javascript'] = 'JavascriptIcon';
|
||||||
|
['typescript'] = 'TypescriptIcon';
|
||||||
|
['jsx'] = 'ReactIcon';
|
||||||
|
['tsx'] = 'ReactIcon';
|
||||||
|
['htm'] = 'HtmlIcon';
|
||||||
|
['html'] = 'HtmlIcon';
|
||||||
|
['slim'] = 'HtmlIcon';
|
||||||
|
['haml'] = 'HtmlIcon';
|
||||||
|
['ejs'] = 'HtmlIcon';
|
||||||
|
}
|
||||||
|
|
||||||
local LINKS = {
|
local function get_links()
|
||||||
|
return {
|
||||||
FolderName = 'Directory',
|
FolderName = 'Directory',
|
||||||
Normal = 'Normal',
|
Normal = 'Normal',
|
||||||
EndOfBuffer = 'EndOfBuffer',
|
EndOfBuffer = 'EndOfBuffer',
|
||||||
CursorLine = 'CursorLine',
|
CursorLine = 'CursorLine',
|
||||||
VertSplit = 'VertSplit',
|
VertSplit = 'VertSplit',
|
||||||
CursorColumn = 'CursorColumn'
|
CursorColumn = 'CursorColumn'
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
function M.init_colors()
|
function M.setup()
|
||||||
colors = get_colors()
|
local higlight_groups = get_hl_groups()
|
||||||
HIGHLIGHTS = create_hl()
|
for k, d in pairs(higlight_groups) do
|
||||||
for k, d in pairs(HIGHLIGHTS) do
|
|
||||||
local gui = d.gui or 'NONE'
|
local gui = d.gui or 'NONE'
|
||||||
api.nvim_command('hi def LuaTree'..k..' gui='..gui..' guifg='..d.fg)
|
api.nvim_command('hi def LuaTree'..k..' gui='..gui..' guifg='..d.fg)
|
||||||
end
|
end
|
||||||
|
|
||||||
for k, d in pairs(LINKS) do
|
local links = get_links()
|
||||||
|
for k, d in pairs(links) do
|
||||||
api.nvim_command('hi def link LuaTree'..k..' '..d)
|
api.nvim_command('hi def link LuaTree'..k..' '..d)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,57 +1,50 @@
|
|||||||
local api = vim.api
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function get(var, fallback)
|
function M.get_icon_state()
|
||||||
if api.nvim_call_function('exists', { var }) == 1 then
|
local show_icons = vim.g.lua_tree_show_icons or { git = 1, folders = 1, files = 1 }
|
||||||
return api.nvim_get_var(var)
|
local icons = {
|
||||||
else
|
default = nil,
|
||||||
return fallback
|
git_icons = {
|
||||||
|
unstaged = "✗",
|
||||||
|
staged = "✓",
|
||||||
|
unmerged = "═",
|
||||||
|
renamed = "➜",
|
||||||
|
untracked = "★"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local user_icons = vim.g.lua_tree_icons
|
||||||
|
if user_icons then
|
||||||
|
if user_icons.default then
|
||||||
|
icons.default = user_icons.default
|
||||||
|
end
|
||||||
|
for key, val in pairs(user_icons.git) do
|
||||||
|
if icons.git_icons[key] then
|
||||||
|
icons.git_icons[key] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
local function get_color_from_hl(hl_name, fallback)
|
|
||||||
local id = api.nvim_get_hl_id_by_name(hl_name)
|
|
||||||
if not id then return fallback end
|
|
||||||
|
|
||||||
local hl = api.nvim_get_hl_by_id(id, true)
|
|
||||||
if not hl or not hl.foreground then return fallback end
|
|
||||||
|
|
||||||
return hl.foreground
|
|
||||||
end
|
|
||||||
|
|
||||||
local HAS_DEV_ICONS = api.nvim_call_function('exists', { "*WebDevIconsGetFileTypeSymbol" }) == 1
|
|
||||||
|
|
||||||
local show_icons = get('lua_tree_show_icons', { git = 1, folders = 1, files = 1 })
|
|
||||||
|
|
||||||
M.SHOW_FILE_ICON = HAS_DEV_ICONS and show_icons.files == 1
|
|
||||||
M.SHOW_FOLDER_ICON = show_icons.folders == 1
|
|
||||||
M.SHOW_GIT_ICON = show_icons.git == 1
|
|
||||||
|
|
||||||
function M.get_colors()
|
|
||||||
return {
|
return {
|
||||||
red = get('terminal_color_1', get_color_from_hl('Keyword', 'Red')),
|
show_file_icon = show_icons.files == 1 and vim.g.nvim_web_devicons == 1,
|
||||||
green = get('terminal_color_2', get_color_from_hl('Character', 'Green')),
|
show_folder_icon = show_icons.folders == 1,
|
||||||
yellow = get('terminal_color_3', get_color_from_hl('PreProc', 'Yellow')),
|
show_git_icon = show_icons.git == 1,
|
||||||
blue = get('terminal_color_4', get_color_from_hl('Include', 'Blue')),
|
icons = icons
|
||||||
purple = get('terminal_color_5', get_color_from_hl('Define', 'Purple')),
|
|
||||||
cyan = get('terminal_color_6', get_color_from_hl('Conditional', 'Cyan')),
|
|
||||||
orange = get('terminal_color_11', get_color_from_hl('Number', 'Orange')),
|
|
||||||
dark_red = get('terminal_color_9', get_color_from_hl('Keyword', 'DarkRed')),
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local keybindings = get('lua_tree_bindings', {});
|
function M.get_bindings()
|
||||||
|
local keybindings = vim.g.lua_tree_bindings or {}
|
||||||
M.bindings = {
|
return {
|
||||||
edit = keybindings.edit or '<CR>',
|
edit = keybindings.edit or '<CR>',
|
||||||
edit_vsplit = keybindings.edit_vsplit or '<C-v>',
|
edit_vsplit = keybindings.edit_vsplit or '<C-v>',
|
||||||
edit_split = keybindings.edit_split or '<C-x>',
|
edit_split = keybindings.edit_split or '<C-x>',
|
||||||
edit_tab = keybindings.edit_tab or '<C-t>',
|
edit_tab = keybindings.edit_tab or '<C-t>',
|
||||||
cd = keybindings.cd or '.',
|
cd = keybindings.cd or '<C-]>',
|
||||||
create = keybindings.create or 'a',
|
create = keybindings.create or 'a',
|
||||||
remove = keybindings.remove or 'd',
|
remove = keybindings.remove or 'd',
|
||||||
rename = keybindings.rename or 'r',
|
rename = keybindings.rename or 'r',
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@ -1,183 +0,0 @@
|
|||||||
local api = vim.api
|
|
||||||
local config = require 'lib/config'
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function get_padding(depth)
|
|
||||||
local str = ""
|
|
||||||
|
|
||||||
while 0 < depth do
|
|
||||||
str = str .. " "
|
|
||||||
depth = depth - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
local function default_icons(_, isdir, open)
|
|
||||||
if isdir == true and config.SHOW_FOLDER_ICON then
|
|
||||||
if open == true then return " " end
|
|
||||||
return " "
|
|
||||||
end
|
|
||||||
|
|
||||||
return ""
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_matcher(arr)
|
|
||||||
return function(name)
|
|
||||||
for _, n in pairs(arr) do
|
|
||||||
if name:match(n) then return true end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local is_special = create_matcher({
|
|
||||||
'README',
|
|
||||||
'readme',
|
|
||||||
'Makefile',
|
|
||||||
'Cargo%.toml',
|
|
||||||
})
|
|
||||||
|
|
||||||
local is_pic = create_matcher({
|
|
||||||
'%.jpe?g$',
|
|
||||||
'%.png',
|
|
||||||
'%.gif'
|
|
||||||
})
|
|
||||||
|
|
||||||
local function is_executable(name)
|
|
||||||
return api.nvim_call_function('executable', { name }) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dev_icons(pathname, isdir, open)
|
|
||||||
if isdir == true or is_special(pathname) == true or is_executable(pathname) == true or is_pic(pathname) == true then
|
|
||||||
return default_icons(pathname, isdir, open)
|
|
||||||
end
|
|
||||||
|
|
||||||
local icon = api.nvim_call_function('WebDevIconsGetFileTypeSymbol', { pathname, isdir })
|
|
||||||
if icon == "" then return "" end
|
|
||||||
return icon .. " "
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_icon_func_gen()
|
|
||||||
if config.SHOW_FILE_ICON then
|
|
||||||
return dev_icons
|
|
||||||
else
|
|
||||||
return default_icons
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local get_icon = get_icon_func_gen()
|
|
||||||
|
|
||||||
function M.format_tree(tree)
|
|
||||||
local dirs = {}
|
|
||||||
|
|
||||||
for i, node in pairs(tree) do
|
|
||||||
local padding = get_padding(node.depth)
|
|
||||||
local git = node.git
|
|
||||||
local icon = ""
|
|
||||||
local name = node.name
|
|
||||||
if node.link == true then
|
|
||||||
name = name .. ' ➛ ' .. node.linkto
|
|
||||||
elseif node.icon == true then
|
|
||||||
icon = get_icon(node.path .. node.name, node.dir, node.open)
|
|
||||||
end
|
|
||||||
dirs[i] = padding .. icon .. git .. name
|
|
||||||
end
|
|
||||||
|
|
||||||
return dirs
|
|
||||||
end
|
|
||||||
|
|
||||||
local HIGHLIGHT_ICON_GROUPS = {
|
|
||||||
['^LICENSE$'] = 'LicenseIcon';
|
|
||||||
['^%.?vimrc$'] = 'VimIcon';
|
|
||||||
['%.vim$'] = 'VimIcon';
|
|
||||||
['%.c$'] = 'CIcon';
|
|
||||||
['%.cpp$'] = 'CIcon';
|
|
||||||
['%.cxx$'] = 'CIcon';
|
|
||||||
['%.h$'] = 'CIcon';
|
|
||||||
['%.hpp$'] = 'CIcon';
|
|
||||||
['%.py$'] = 'PythonIcon';
|
|
||||||
['%.lua$'] = 'LuaIcon';
|
|
||||||
['%.rs$'] = 'RustIcon';
|
|
||||||
['%.[cz]?sh$'] = 'ShellIcon';
|
|
||||||
['%.md$'] = 'MarkdownIcon';
|
|
||||||
['%.json$'] = 'JsonIcon';
|
|
||||||
['%.toml$'] = 'TomlIcon';
|
|
||||||
['%.yml$'] = 'YamlIcon';
|
|
||||||
['%.gitignore$'] = 'GitignoreIcon';
|
|
||||||
['%.js$'] = 'JavascriptIcon';
|
|
||||||
['%.ts$'] = 'TypescriptIcon';
|
|
||||||
['%.[tj]sx$'] = 'ReactIcon';
|
|
||||||
['%.html?$'] = 'HtmlIcon';
|
|
||||||
}
|
|
||||||
|
|
||||||
local function highlight_line(buffer)
|
|
||||||
local function highlight(group, line, from, to)
|
|
||||||
api.nvim_buf_add_highlight(buffer, -1, group, line, from, to)
|
|
||||||
end
|
|
||||||
return function(line, node)
|
|
||||||
local text_start = node.depth * 2
|
|
||||||
local gitlen = string.len(node.git)
|
|
||||||
if node.name == '..' then
|
|
||||||
highlight('LuaTreeFolderName', line, 0, -1)
|
|
||||||
|
|
||||||
elseif node.dir == true then
|
|
||||||
if config.SHOW_FOLDER_ICON then
|
|
||||||
text_start = text_start + 4
|
|
||||||
highlight('LuaTreeFolderIcon', line, 0, text_start)
|
|
||||||
end
|
|
||||||
highlight('LuaTreeFolderName', line, text_start + gitlen, -1)
|
|
||||||
|
|
||||||
elseif node.link == true then
|
|
||||||
highlight('LuaTreeSymlink', line, 0, -1)
|
|
||||||
|
|
||||||
elseif is_special(node.name) == true then
|
|
||||||
highlight('LuaTreeSpecialFile', line, text_start + gitlen, -1)
|
|
||||||
|
|
||||||
elseif is_executable(node.path .. node.name) then
|
|
||||||
highlight('LuaTreeExecFile', line, text_start + gitlen, -1)
|
|
||||||
|
|
||||||
elseif is_pic(node.path .. node.name) then
|
|
||||||
highlight('LuaTreeImageFile', line, text_start + gitlen, -1)
|
|
||||||
|
|
||||||
elseif config.SHOW_FILE_ICON then
|
|
||||||
for k, v in pairs(HIGHLIGHT_ICON_GROUPS) do
|
|
||||||
if node.name:match(k) ~= nil then
|
|
||||||
text_start = text_start + 4
|
|
||||||
highlight('LuaTree' .. v, line, 0, text_start)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if node.git == '' then return end
|
|
||||||
|
|
||||||
if node.git == '✗ ' then
|
|
||||||
highlight('LuaTreeGitDirty', line, text_start, text_start + gitlen)
|
|
||||||
elseif node.git == '✓ ' then
|
|
||||||
highlight('LuaTreeGitStaged', line, text_start, text_start + gitlen)
|
|
||||||
elseif node.git == '✓★ ' then
|
|
||||||
highlight('LuaTreeGitStaged', line, text_start, text_start + 3)
|
|
||||||
highlight('LuaTreeGitNew', line, text_start + 3, text_start + gitlen)
|
|
||||||
elseif node.git == '✓✗ ' then
|
|
||||||
highlight('LuaTreeGitStaged', line, text_start, text_start + 3)
|
|
||||||
highlight('LuaTreeGitDirty', line, text_start + 3, text_start + gitlen)
|
|
||||||
elseif node.git == '═ ' then
|
|
||||||
highlight('LuaTreeGitMerge', line, text_start, text_start + gitlen)
|
|
||||||
elseif node.git == '➜ ' then
|
|
||||||
highlight('LuaTreeGitRenamed', line, text_start, text_start + gitlen)
|
|
||||||
elseif node.git == '★ ' then
|
|
||||||
highlight('LuaTreeGitNew', line, text_start, text_start + gitlen)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.highlight_buffer(buffer, tree)
|
|
||||||
local highlight = highlight_line(buffer)
|
|
||||||
for i, node in pairs(tree) do
|
|
||||||
highlight(i - 1, node)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
202
lua/lib/fs.lua
202
lua/lib/fs.lua
@ -1,77 +1,169 @@
|
|||||||
local api = vim.api
|
local api = vim.api
|
||||||
local luv = vim.loop
|
local luv = vim.loop
|
||||||
|
local open_mode = luv.constants.O_CREAT + luv.constants.O_WRONLY + luv.constants.O_TRUNC
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
function M.get_cwd() return luv.cwd() end
|
local function clear_prompt()
|
||||||
|
vim.api.nvim_command('normal :esc<CR>')
|
||||||
function M.is_dir(path)
|
|
||||||
local stat = luv.fs_lstat(path)
|
|
||||||
return stat and stat.type == 'directory' or false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.is_symlink(path)
|
local function refresh_tree()
|
||||||
local stat = luv.fs_lstat(path)
|
vim.api.nvim_command(":LuaTreeRefresh")
|
||||||
return stat and stat.type == 'link' or false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.link_to(path)
|
local function create_file(file)
|
||||||
return luv.fs_readlink(path) or ''
|
luv.fs_open(file, "w", open_mode, vim.schedule_wrap(function(err, fd)
|
||||||
end
|
if err then
|
||||||
|
api.nvim_err_writeln('Could not create file '..file)
|
||||||
function M.check_dir_access(path)
|
|
||||||
if luv.fs_access(path, 'R') == true then
|
|
||||||
return true
|
|
||||||
else
|
else
|
||||||
api.nvim_err_writeln('Permission denied: ' .. path)
|
-- FIXME: i don't know why but libuv keeps creating file with executable permissions
|
||||||
return false
|
-- this is why we need to chmod to default file permissions
|
||||||
|
luv.fs_chmod(file, 0644)
|
||||||
|
luv.fs_close(fd)
|
||||||
|
api.nvim_out_write('File '..file..' was properly created\n')
|
||||||
|
refresh_tree()
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
local handle = nil
|
|
||||||
|
|
||||||
local function run_process(opt, err, cb)
|
|
||||||
handle = luv.spawn(opt.cmd, { args = opt.args }, vim.schedule_wrap(function(code)
|
|
||||||
handle:close()
|
|
||||||
if code ~= 0 then
|
|
||||||
return api.nvim_err_writeln(err)
|
|
||||||
end
|
|
||||||
cb()
|
|
||||||
end))
|
end))
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.rm(path, cb)
|
local function get_num_entries(iter)
|
||||||
local opt = { cmd='rm', args = {'-rf', path } };
|
i = 0
|
||||||
run_process(opt, 'Error removing '..path, cb)
|
for _ in iter do
|
||||||
end
|
i = i + 1
|
||||||
|
|
||||||
|
|
||||||
function M.rename(file, new_path, cb)
|
|
||||||
local opt = { cmd='mv', args = {file, new_path } };
|
|
||||||
run_process(opt, 'Error renaming '..file..' to '..new_path, cb)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.create(path, file, folders, cb)
|
|
||||||
local opt_file = nil
|
|
||||||
local file_path = nil
|
|
||||||
if file ~= nil then
|
|
||||||
file_path = path..folders..file
|
|
||||||
opt_file = { cmd='touch', args = {file_path} }
|
|
||||||
end
|
end
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
if folders ~= "" then
|
function M.create(node)
|
||||||
local folder_path = path..folders
|
if node.name == '..' then return end
|
||||||
local opt = {cmd = 'mkdir', args = {'-p', folder_path }}
|
|
||||||
run_process(opt, 'Error creating folder '..folder_path, function()
|
local add_into
|
||||||
if opt_file then
|
if node.entries ~= nil then
|
||||||
run_process(opt, 'Error creating file '..file_path, cb)
|
add_into = node.absolute_path..'/'
|
||||||
else
|
else
|
||||||
cb()
|
add_into = node.absolute_path:sub(0, -(#node.name + 1))
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
elseif opt_file then
|
local ans = vim.fn.input('Create file '..add_into)
|
||||||
run_process(opt_file, 'Error creating file '..file_path, cb)
|
clear_prompt()
|
||||||
|
if not ans or #ans == 0 then return end
|
||||||
|
|
||||||
|
if not ans:match('/') then
|
||||||
|
return create_file(add_into..ans)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create a foler for each element until / and create a file when element is not ending with /
|
||||||
|
-- if element is ending with / and it's the last element, we need to manually refresh
|
||||||
|
local relpath = ''
|
||||||
|
local idx = 0
|
||||||
|
local num_entries = get_num_entries(ans:gmatch('[^/]+/?'))
|
||||||
|
for path in ans:gmatch('[^/]+/?') do
|
||||||
|
idx = idx + 1
|
||||||
|
relpath = relpath..path
|
||||||
|
if relpath:match('.*/$') then
|
||||||
|
local success = luv.fs_mkdir(add_into..relpath, 493)
|
||||||
|
if not success then
|
||||||
|
api.nvim_err_writeln('Could not create folder '..add_into..relpath)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if idx == num_entries then
|
||||||
|
api.nvim_out_write('Folder '..add_into..relpath..' was properly created\n')
|
||||||
|
refresh_tree()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
create_file(add_into..relpath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local remove_ok = true
|
||||||
|
|
||||||
|
local function remove_callback(name, absolute_path)
|
||||||
|
return function(err, success)
|
||||||
|
if err ~= nil then
|
||||||
|
api.nvim_err_writeln(err)
|
||||||
|
remove_ok = false
|
||||||
|
elseif not success then
|
||||||
|
remove_ok = false
|
||||||
|
api.nvim_err_writeln('Could not remove '..name)
|
||||||
|
else
|
||||||
|
api.nvim_out_write(name..' has been removed\n')
|
||||||
|
for _, buf in pairs(api.nvim_list_bufs()) do
|
||||||
|
if api.nvim_buf_get_name(buf) == absolute_path then
|
||||||
|
api.nvim_command(':bd! '..buf)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove_dir(cwd)
|
||||||
|
local handle = luv.fs_scandir(cwd)
|
||||||
|
if type(handle) == 'string' then
|
||||||
|
return api.nvim_err_writeln(handle)
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local name, t = luv.fs_scandir_next(handle)
|
||||||
|
if not name then break end
|
||||||
|
|
||||||
|
local new_cwd = cwd..'/'..name
|
||||||
|
if t == 'directory' then
|
||||||
|
remove_dir(new_cwd)
|
||||||
|
else
|
||||||
|
luv.fs_unlink(new_cwd, vim.schedule_wrap(remove_callback(new_cwd, new_cwd)))
|
||||||
|
end
|
||||||
|
if not remove_ok then return end
|
||||||
|
end
|
||||||
|
|
||||||
|
luv.fs_rmdir(cwd, vim.schedule_wrap(remove_callback(cwd, cwd)))
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.remove(node)
|
||||||
|
if node.name == '..' then return end
|
||||||
|
|
||||||
|
local ans = vim.fn.input("Remove " ..node.name.. " ? y/n: ")
|
||||||
|
clear_prompt()
|
||||||
|
if ans:match('^y') then
|
||||||
|
remove_ok = true
|
||||||
|
if node.entries ~= nil then
|
||||||
|
remove_dir(node.absolute_path)
|
||||||
|
else
|
||||||
|
luv.fs_unlink(node.absolute_path, vim.schedule_wrap(
|
||||||
|
remove_callback(node.name, node.absolute_path)
|
||||||
|
))
|
||||||
|
end
|
||||||
|
refresh_tree()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rename_callback(node, new_name)
|
||||||
|
return function(err, success)
|
||||||
|
if err ~= nil then
|
||||||
|
api.nvim_err_writeln(err)
|
||||||
|
elseif not success then
|
||||||
|
api.nvim_err_writeln('Could not rename '..node.absolute_path..' to '..new_name)
|
||||||
|
else
|
||||||
|
api.nvim_out_write(node.absolute_path..' ➜ '..new_name..'\n')
|
||||||
|
for _, buf in pairs(api.nvim_list_bufs()) do
|
||||||
|
if api.nvim_buf_get_name(buf) == node.absolute_path then
|
||||||
|
api.nvim_buf_set_name(buf, new_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
refresh_tree()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.rename(node)
|
||||||
|
if node.name == '..' then return end
|
||||||
|
|
||||||
|
local ans = vim.fn.input("Rename " ..node.name.. " to ", node.absolute_path)
|
||||||
|
clear_prompt()
|
||||||
|
if not ans or #ans == 0 then return end
|
||||||
|
|
||||||
|
luv.fs_rename(node.absolute_path, ans, vim.schedule_wrap(rename_callback(node, ans)))
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
local api = vim.api
|
|
||||||
|
|
||||||
local fs = require 'lib/fs'
|
|
||||||
local update_view = require 'lib/winutils'.update_view
|
|
||||||
local refresh_tree = require 'lib/state'.refresh_tree
|
|
||||||
local refresh_git = require 'lib/git'.refresh_git
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function input(v)
|
|
||||||
local param
|
|
||||||
if type(v) == 'string' then param = { v } else param = v end
|
|
||||||
return api.nvim_call_function('input', param)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function clear_prompt()
|
|
||||||
api.nvim_command('normal :<esc>')
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.create_file(path)
|
|
||||||
local new_file = input("Create file: " .. path)
|
|
||||||
|
|
||||||
local file = nil
|
|
||||||
if not new_file:match('.*/$') then
|
|
||||||
file = new_file:reverse():gsub('/.*$', ''):reverse()
|
|
||||||
new_file = new_file:gsub('[^/]*$', '')
|
|
||||||
end
|
|
||||||
|
|
||||||
local folders = ""
|
|
||||||
if #new_file ~= 0 then
|
|
||||||
for p in new_file:gmatch('[^/]*') do
|
|
||||||
if p and p ~= "" then
|
|
||||||
folders = folders .. p .. '/'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
clear_prompt()
|
|
||||||
fs.create(path, file, folders, function()
|
|
||||||
refresh_git()
|
|
||||||
refresh_tree()
|
|
||||||
update_view()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.remove_file(filename, path)
|
|
||||||
local ans = input("Remove " .. filename .. " ? y/n: ")
|
|
||||||
clear_prompt()
|
|
||||||
if ans == "y" then
|
|
||||||
fs.rm(path .. filename, function()
|
|
||||||
refresh_git()
|
|
||||||
refresh_tree()
|
|
||||||
update_view()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.rename_file(filename, path)
|
|
||||||
local new_path = input({"Rename file " .. filename .. ": ", path .. filename})
|
|
||||||
clear_prompt()
|
|
||||||
fs.rename(path .. filename, new_path, function()
|
|
||||||
refresh_git()
|
|
||||||
refresh_tree()
|
|
||||||
update_view()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
local api = vim.api
|
|
||||||
local config = require 'lib/config'
|
|
||||||
local utils = require'lib.utils'
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function system(v) return api.nvim_call_function('system', { v }) end
|
|
||||||
local function systemlist(v) return api.nvim_call_function('systemlist', { v }) end
|
|
||||||
|
|
||||||
local function is_git_repo()
|
|
||||||
local is_git = system('git rev-parse')
|
|
||||||
return is_git:match('fatal') == nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local IS_GIT_REPO = is_git_repo()
|
|
||||||
|
|
||||||
local function set_git_status()
|
|
||||||
if IS_GIT_REPO == false then return '' end
|
|
||||||
return systemlist('git status --porcelain=v1')
|
|
||||||
end
|
|
||||||
|
|
||||||
local GIT_STATUS = set_git_status()
|
|
||||||
|
|
||||||
function M.refresh_git()
|
|
||||||
if IS_GIT_REPO == false then return false end
|
|
||||||
GIT_STATUS = set_git_status()
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.force_refresh_git()
|
|
||||||
IS_GIT_REPO = is_git_repo()
|
|
||||||
M.refresh_git()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_folder_dirty(relpath)
|
|
||||||
for _, status in pairs(GIT_STATUS) do
|
|
||||||
if status:match(utils.path_to_matching_str(relpath)) ~= nil then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_git_checker(pattern)
|
|
||||||
return function(relpath)
|
|
||||||
for _, status in pairs(GIT_STATUS) do
|
|
||||||
local ret = status:match('^.. .*' .. utils.path_to_matching_str(relpath))
|
|
||||||
if ret ~= nil and ret:match(pattern) ~= nil then return true end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local unstaged = create_git_checker('^ ')
|
|
||||||
local staged = create_git_checker('^M ')
|
|
||||||
local staged_new = create_git_checker('^A ')
|
|
||||||
local staged_mod = create_git_checker('^MM')
|
|
||||||
local unmerged = create_git_checker('^[U ][U ]')
|
|
||||||
local renamed = create_git_checker('^R')
|
|
||||||
local untracked = create_git_checker('^%?%?')
|
|
||||||
|
|
||||||
function M.get_git_attr(path, is_dir)
|
|
||||||
if IS_GIT_REPO == false or not config.SHOW_GIT_ICON then return '' end
|
|
||||||
if is_dir then
|
|
||||||
if is_folder_dirty(path) == true then return '✗ ' end
|
|
||||||
else
|
|
||||||
if unstaged(path) then return '✗ '
|
|
||||||
elseif staged(path) then return '✓ '
|
|
||||||
elseif staged_new(path) then return '✓★ '
|
|
||||||
elseif staged_mod(path) then return '✓✗ '
|
|
||||||
elseif unmerged(path) then return '═ '
|
|
||||||
elseif renamed(path) then return '➜ '
|
|
||||||
elseif untracked(path) then return '★ '
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return ''
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
241
lua/lib/populate.lua
Normal file
241
lua/lib/populate.lua
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
local config = require'lib.config'
|
||||||
|
local icon_config = config.get_icon_state()
|
||||||
|
|
||||||
|
local api = vim.api
|
||||||
|
local luv = vim.loop
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local function path_to_matching_str(path)
|
||||||
|
return path:gsub('(%-)', '(%%-)'):gsub('(%.)', '(%%.)')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dir_new(cwd, name)
|
||||||
|
local absolute_path = cwd..'/'..name
|
||||||
|
local stat = luv.fs_stat(absolute_path)
|
||||||
|
return {
|
||||||
|
name = name,
|
||||||
|
absolute_path = absolute_path,
|
||||||
|
-- TODO: last modified could also involve atime and ctime
|
||||||
|
last_modified = stat.mtime.sec,
|
||||||
|
match_name = path_to_matching_str(name),
|
||||||
|
match_path = path_to_matching_str(absolute_path),
|
||||||
|
open = false,
|
||||||
|
entries = {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function file_new(cwd, name)
|
||||||
|
local absolute_path = cwd..'/'..name
|
||||||
|
local is_exec = luv.fs_access(absolute_path, 'X')
|
||||||
|
return {
|
||||||
|
name = name,
|
||||||
|
absolute_path = absolute_path,
|
||||||
|
executable = is_exec,
|
||||||
|
extension = vim.fn.fnamemodify(name, ':e') or "",
|
||||||
|
match_name = path_to_matching_str(name),
|
||||||
|
match_path = path_to_matching_str(absolute_path),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function link_new(cwd, name)
|
||||||
|
local absolute_path = cwd..'/'..name
|
||||||
|
local link_to = luv.fs_realpath(absolute_path)
|
||||||
|
return {
|
||||||
|
name = name,
|
||||||
|
absolute_path = absolute_path,
|
||||||
|
link_to = link_to,
|
||||||
|
match_name = path_to_matching_str(name),
|
||||||
|
match_path = path_to_matching_str(absolute_path),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function gen_ignore_check()
|
||||||
|
local ignore_list = {}
|
||||||
|
if vim.g.lua_tree_ignore and #vim.g.lua_tree_ignore > 0 then
|
||||||
|
for _, entry in pairs(vim.g.lua_tree_ignore) do
|
||||||
|
ignore_list[entry] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(path)
|
||||||
|
return ignore_list[path] == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local should_ignore = gen_ignore_check()
|
||||||
|
|
||||||
|
function M.refresh_entries(entries, cwd)
|
||||||
|
local handle = luv.fs_scandir(cwd)
|
||||||
|
if type(handle) == 'string' then
|
||||||
|
api.nvim_err_writeln(handle)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local named_entries = {}
|
||||||
|
local cached_entries = {}
|
||||||
|
local entries_idx = {}
|
||||||
|
for i, node in ipairs(entries) do
|
||||||
|
cached_entries[i] = node.name
|
||||||
|
entries_idx[node.name] = i
|
||||||
|
named_entries[node.name] = node
|
||||||
|
end
|
||||||
|
|
||||||
|
local dirs = {}
|
||||||
|
local links = {}
|
||||||
|
local files = {}
|
||||||
|
local new_entries = {}
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local name, t = luv.fs_scandir_next(handle)
|
||||||
|
if not name then break end
|
||||||
|
if should_ignore(name) then goto continue end
|
||||||
|
|
||||||
|
if t == 'directory' then
|
||||||
|
table.insert(dirs, name)
|
||||||
|
new_entries[name] = true
|
||||||
|
elseif t == 'file' then
|
||||||
|
table.insert(files, name)
|
||||||
|
new_entries[name] = true
|
||||||
|
elseif t == 'link' then
|
||||||
|
table.insert(links, name)
|
||||||
|
new_entries[name] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
|
||||||
|
local all = {
|
||||||
|
{ entries = dirs, fn = dir_new },
|
||||||
|
{ entries = links, fn = link_new },
|
||||||
|
{ entries = files, fn = file_new }
|
||||||
|
}
|
||||||
|
|
||||||
|
local prev = nil
|
||||||
|
for _, e in ipairs(all) do
|
||||||
|
for _, name in ipairs(e.entries) do
|
||||||
|
if not named_entries[name] then
|
||||||
|
local n = e.fn(cwd, name)
|
||||||
|
|
||||||
|
local idx = 1
|
||||||
|
if prev then
|
||||||
|
idx = entries_idx[prev] + 1
|
||||||
|
end
|
||||||
|
table.insert(entries, idx, n)
|
||||||
|
entries_idx[name] = idx
|
||||||
|
cached_entries[idx] = name
|
||||||
|
end
|
||||||
|
prev = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local idx = 1
|
||||||
|
for _, name in ipairs(cached_entries) do
|
||||||
|
if not new_entries[name] then
|
||||||
|
table.remove(entries, idx, idx + 1)
|
||||||
|
else
|
||||||
|
idx = idx + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.populate(entries, cwd)
|
||||||
|
local handle = luv.fs_scandir(cwd)
|
||||||
|
if type(handle) == 'string' then
|
||||||
|
api.nvim_err_writeln(handle)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local dirs = {}
|
||||||
|
local links = {}
|
||||||
|
local files = {}
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local name, t = luv.fs_scandir_next(handle)
|
||||||
|
if not name then break end
|
||||||
|
|
||||||
|
if t == 'directory' then
|
||||||
|
table.insert(dirs, name)
|
||||||
|
elseif t == 'file' then
|
||||||
|
table.insert(files, name)
|
||||||
|
elseif t == 'link' then
|
||||||
|
table.insert(links, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create Nodes --
|
||||||
|
|
||||||
|
for _, dirname in ipairs(dirs) do
|
||||||
|
local dir = dir_new(cwd, dirname)
|
||||||
|
if not should_ignore(dir.name) and luv.fs_access(dir.absolute_path, 'R') then
|
||||||
|
table.insert(entries, dir)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, linkname in ipairs(links) do
|
||||||
|
local link = link_new(cwd, linkname)
|
||||||
|
if not should_ignore(link.name) then
|
||||||
|
table.insert(entries, link)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, filename in ipairs(files) do
|
||||||
|
local file = file_new(cwd, filename)
|
||||||
|
if not should_ignore(file.name) then
|
||||||
|
table.insert(entries, file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not icon_config.show_git_icon then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
M.update_git_status(entries, cwd)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.update_git_status(entries, cwd)
|
||||||
|
local git_root = vim.fn.system('cd '..cwd..' && git rev-parse --show-toplevel')
|
||||||
|
if not git_root or #git_root == 0 or git_root:match('fatal: not a git repository') then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
git_root = git_root:sub(0, -2)
|
||||||
|
|
||||||
|
local git_statuslist = vim.fn.systemlist('cd '..cwd..' && git status --porcelain=v1')
|
||||||
|
local git_status = {}
|
||||||
|
|
||||||
|
for _, v in pairs(git_statuslist) do
|
||||||
|
local head = v:sub(0, 2)
|
||||||
|
local body = v:sub(4, -1)
|
||||||
|
if body:match('%->') ~= nil then
|
||||||
|
body = body:gsub('^.* %-> ', '')
|
||||||
|
end
|
||||||
|
git_status[body] = head
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local matching_cwd = path_to_matching_str(git_root..'/')
|
||||||
|
for _, node in pairs(entries) do
|
||||||
|
local relpath = node.absolute_path:gsub(matching_cwd, '')
|
||||||
|
if node.entries ~= nil then
|
||||||
|
relpath = relpath..'/'
|
||||||
|
node.git_status = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local status = git_status[relpath]
|
||||||
|
if status then
|
||||||
|
node.git_status = status
|
||||||
|
elseif node.entries ~= nil then
|
||||||
|
local matcher = '^'..path_to_matching_str(relpath)
|
||||||
|
for key, _ in pairs(git_status) do
|
||||||
|
if key:match(matcher) then
|
||||||
|
node.git_status = 'dirty'
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
node.git_status = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
183
lua/lib/renderer.lua
Normal file
183
lua/lib/renderer.lua
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
local colors = require'lib.colors'
|
||||||
|
local config = require'lib.config'
|
||||||
|
|
||||||
|
local api = vim.api
|
||||||
|
|
||||||
|
local lines = {}
|
||||||
|
local hl = {}
|
||||||
|
local index = 0
|
||||||
|
local namespace_id = api.nvim_create_namespace('LuaTreeHighlights')
|
||||||
|
|
||||||
|
local icon_state = config.get_icon_state()
|
||||||
|
|
||||||
|
local get_folder_icon = function() return "" end
|
||||||
|
local set_folder_hl = function(index, depth, git_icon_len)
|
||||||
|
table.insert(hl, {'LuaTreeFolderName', index, depth+git_icon_len, -1})
|
||||||
|
end
|
||||||
|
|
||||||
|
if icon_state.show_folder_icon then
|
||||||
|
get_folder_icon = function(open)
|
||||||
|
if open then
|
||||||
|
return " "
|
||||||
|
else
|
||||||
|
return " "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
set_folder_hl = function(index, depth, icon_len, name_len)
|
||||||
|
table.insert(hl, {'LuaTreeFolderName', index, depth+icon_len, depth+icon_len+name_len})
|
||||||
|
table.insert(hl, {'LuaTreeFolderIcon', index, depth, depth+icon_len})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_file_icon = function() return "" end
|
||||||
|
if icon_state.show_file_icon then
|
||||||
|
local web_devicons = require'nvim-web-devicons'
|
||||||
|
|
||||||
|
get_file_icon = function(fname, extension, index, depth)
|
||||||
|
local icon, hl_group = web_devicons.get_icon(fname, extension)
|
||||||
|
-- TODO: remove this hl_group and make this in nvim-web-devicons
|
||||||
|
if #extension == 0 then
|
||||||
|
hl_group = colors.hl_groups[fname]
|
||||||
|
else
|
||||||
|
hl_group = colors.hl_groups[extension]
|
||||||
|
end
|
||||||
|
if hl_group and icon then
|
||||||
|
table.insert(hl, { 'LuaTree'..hl_group, index, depth, depth + #icon })
|
||||||
|
return icon.." "
|
||||||
|
else
|
||||||
|
return icon_state.icons.default and icon_state.icons.default.." " or ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_git_icons = function() return "" end
|
||||||
|
local git_icon_state = {}
|
||||||
|
if icon_state.show_git_icon then
|
||||||
|
|
||||||
|
git_icon_state = {
|
||||||
|
["M "] = { { icon = icon_state.icons.git_icons.staged, hl = "LuaTreeGitStaged" } },
|
||||||
|
[" M"] = { { icon = icon_state.icons.git_icons.unstaged, hl = "LuaTreeGitDirty" } },
|
||||||
|
["MM"] = {
|
||||||
|
{ icon = icon_state.icons.git_icons.staged, hl = "LuaTreeGitStaged" },
|
||||||
|
{ icon = icon_state.icons.git_icons.unstaged, hl = "LuaTreeGitDirty" }
|
||||||
|
},
|
||||||
|
["A "] = {
|
||||||
|
{ icon = icon_state.icons.git_icons.staged, hl = "LuaTreeGitStaged" },
|
||||||
|
{ icon = icon_state.icons.git_icons.untracked, hl = "LuaTreeGitNew" }
|
||||||
|
},
|
||||||
|
["AM"] = {
|
||||||
|
{ icon = icon_state.icons.git_icons.staged, hl = "LuaTreeGitStaged" },
|
||||||
|
{ icon = icon_state.icons.git_icons.untracked, hl = "LuaTreeGitNew" },
|
||||||
|
{ icon = icon_state.icons.git_icons.unstaged, hl = "LuaTreeGitDirty" }
|
||||||
|
},
|
||||||
|
["??"] = { { icon = icon_state.icons.git_icons.untracked, hl = "LuaTreeGitNew" } },
|
||||||
|
["R "] = { { icon = icon_state.icons.git_icons.renamed, hl = "LuaTreeGitRenamed" } },
|
||||||
|
["UU"] = { { icon = icon_state.icons.git_icons.unmerged, hl = "LuaTreeGitMerge" } },
|
||||||
|
dirty = { { icon = icon_state.icons.git_icons.unstaged, hl = "LuaTreeGitDirty" } },
|
||||||
|
}
|
||||||
|
|
||||||
|
get_git_icons = function(node, index, depth, icon_len)
|
||||||
|
local git_status = node.git_status
|
||||||
|
if not git_status then return "" end
|
||||||
|
|
||||||
|
local icon = ""
|
||||||
|
local icons = git_icon_state[git_status]
|
||||||
|
for _, v in ipairs(icons) do
|
||||||
|
table.insert(hl, { v.hl, index, depth+icon_len+#icon, depth+icon_len+#icon+#v.icon })
|
||||||
|
icon = icon..v.icon.." "
|
||||||
|
end
|
||||||
|
|
||||||
|
return icon
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local picture = {
|
||||||
|
jpg = true,
|
||||||
|
jpeg = true,
|
||||||
|
png = true,
|
||||||
|
gif = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local special = {
|
||||||
|
["Cargo.toml"] = true,
|
||||||
|
Makefile = true,
|
||||||
|
["README.md"] = true,
|
||||||
|
["readme.md"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function update_draw_data(tree, depth)
|
||||||
|
if tree.cwd and tree.cwd ~= '/' then
|
||||||
|
table.insert(lines, "..")
|
||||||
|
table.insert(hl, {'LuaTreeFolderName', index, 0, 2})
|
||||||
|
index = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, node in ipairs(tree.entries) do
|
||||||
|
local padding = string.rep(" ", depth)
|
||||||
|
if node.entries then
|
||||||
|
local icon = get_folder_icon(node.open)
|
||||||
|
local git_icon = get_git_icons(node, index, depth+#node.name, #icon+1)
|
||||||
|
set_folder_hl(index, depth, #icon, #node.name)
|
||||||
|
index = index + 1
|
||||||
|
if node.open then
|
||||||
|
table.insert(lines, padding..icon..node.name.." "..git_icon)
|
||||||
|
update_draw_data(node, depth + 2)
|
||||||
|
else
|
||||||
|
table.insert(lines, padding..icon..node.name.." "..git_icon)
|
||||||
|
end
|
||||||
|
elseif node.link_to then
|
||||||
|
table.insert(hl, { 'LuaTreeSymlink', index, depth, -1 })
|
||||||
|
table.insert(lines, padding..node.name.." ➛ "..node.link_to)
|
||||||
|
index = index + 1
|
||||||
|
|
||||||
|
else
|
||||||
|
local icon
|
||||||
|
local git_icons
|
||||||
|
if special[node.name] then
|
||||||
|
icon = ""
|
||||||
|
git_icons = get_git_icons(node, index, depth, 0)
|
||||||
|
table.insert(hl, {'LuaTreeSpecialFile', index, depth+#git_icons, -1})
|
||||||
|
else
|
||||||
|
icon = get_file_icon(node.name, node.extension, index, depth)
|
||||||
|
git_icons = get_git_icons(node, index, depth, #icon)
|
||||||
|
end
|
||||||
|
table.insert(lines, padding..icon..git_icons..node.name)
|
||||||
|
if node.executable then
|
||||||
|
table.insert(hl, {'LuaTreeExecFile', index, depth+#icon+#git_icons, -1 })
|
||||||
|
elseif picture[node.extension] then
|
||||||
|
table.insert(hl, {'LuaTreeImageFile', index, depth+#icon+#git_icons, -1 })
|
||||||
|
end
|
||||||
|
index = index + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.draw(tree, reload)
|
||||||
|
api.nvim_buf_set_option(tree.bufnr, 'modifiable', true)
|
||||||
|
local cursor = api.nvim_win_get_cursor(tree.winnr)
|
||||||
|
if reload then
|
||||||
|
index = 0
|
||||||
|
lines = {}
|
||||||
|
hl = {}
|
||||||
|
update_draw_data(tree, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
api.nvim_buf_set_lines(tree.bufnr, 0, -1, false, lines)
|
||||||
|
M.render_hl(tree.bufnr)
|
||||||
|
if #lines > cursor[1] then
|
||||||
|
api.nvim_win_set_cursor(tree.winnr, cursor)
|
||||||
|
end
|
||||||
|
api.nvim_buf_set_option(tree.bufnr, 'modifiable', false)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.render_hl(bufnr)
|
||||||
|
api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1)
|
||||||
|
for _, data in ipairs(hl) do
|
||||||
|
api.nvim_buf_add_highlight(bufnr, namespace_id, data[1], data[2], data[3], data[4])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@ -1,179 +0,0 @@
|
|||||||
local api = vim.api
|
|
||||||
local utils = require'lib.utils'
|
|
||||||
local gitutils = require 'lib.git'
|
|
||||||
local fs = require 'lib.fs'
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local ROOT_PATH = fs.get_cwd() .. '/'
|
|
||||||
|
|
||||||
function M.set_root_path(path)
|
|
||||||
ROOT_PATH = path
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.get_root_path()
|
|
||||||
return ROOT_PATH
|
|
||||||
end
|
|
||||||
|
|
||||||
local Tree = {}
|
|
||||||
|
|
||||||
local IGNORE_LIST = ""
|
|
||||||
|
|
||||||
local MACOS = api.nvim_call_function('has', { 'macunix' }) == 1
|
|
||||||
|
|
||||||
-- --ignore does not work with mac ls
|
|
||||||
if not MACOS and api.nvim_call_function('exists', { 'g:lua_tree_ignore' }) == 1 then
|
|
||||||
local ignore_patterns = api.nvim_get_var('lua_tree_ignore')
|
|
||||||
if type(ignore_patterns) == 'table' then
|
|
||||||
for _, pattern in pairs(ignore_patterns) do
|
|
||||||
IGNORE_LIST = IGNORE_LIST .. '--ignore='..pattern..' '
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function list_dirs(path)
|
|
||||||
return api.nvim_call_function('systemlist', { 'ls -A '..IGNORE_LIST..path })
|
|
||||||
end
|
|
||||||
|
|
||||||
local function sort_dirs(dirs)
|
|
||||||
local sorted_tree = {}
|
|
||||||
for _, node in pairs(dirs) do
|
|
||||||
if node.dir == true then
|
|
||||||
table.insert(sorted_tree, 1, node)
|
|
||||||
else
|
|
||||||
table.insert(sorted_tree, node)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return sorted_tree
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_nodes(path, relpath, depth, dirs)
|
|
||||||
local tree = {}
|
|
||||||
|
|
||||||
if not path:find('^.*/$') then path = path .. '/' end
|
|
||||||
if not relpath:find('^.*/$') and depth > 0 then relpath = relpath .. '/' end
|
|
||||||
|
|
||||||
for i, name in pairs(dirs) do
|
|
||||||
local full_path = path..name
|
|
||||||
local dir = fs.is_dir(full_path)
|
|
||||||
local link = fs.is_symlink(full_path)
|
|
||||||
local linkto = link == true and fs.link_to(full_path) or nil
|
|
||||||
local rel_path = relpath ..name
|
|
||||||
tree[i] = {
|
|
||||||
path = path,
|
|
||||||
relpath = rel_path,
|
|
||||||
link = link,
|
|
||||||
linkto = linkto,
|
|
||||||
name = name,
|
|
||||||
depth = depth,
|
|
||||||
dir = dir,
|
|
||||||
open = false,
|
|
||||||
icon = true,
|
|
||||||
git = gitutils.get_git_attr(rel_path, dir)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return sort_dirs(tree)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.init_tree()
|
|
||||||
Tree = create_nodes(ROOT_PATH, '', 0, list_dirs(ROOT_PATH))
|
|
||||||
if ROOT_PATH ~= '/' then
|
|
||||||
table.insert(Tree, 1, {
|
|
||||||
path = ROOT_PATH,
|
|
||||||
name = '..',
|
|
||||||
depth = 0,
|
|
||||||
dir = true,
|
|
||||||
open = false,
|
|
||||||
icon = false,
|
|
||||||
git = ''
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.refresh_tree()
|
|
||||||
local cache = {}
|
|
||||||
|
|
||||||
for _, v in pairs(Tree) do
|
|
||||||
if v.dir == true and v.open == true then
|
|
||||||
table.insert(cache, v.path .. v.name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
M.init_tree()
|
|
||||||
|
|
||||||
for i, node in pairs(Tree) do
|
|
||||||
if node.dir == true then
|
|
||||||
for _, path in pairs(cache) do
|
|
||||||
if node.path .. node.name == path then
|
|
||||||
node.open = true
|
|
||||||
local dirs = list_dirs(path)
|
|
||||||
for j, n in pairs(create_nodes(path, node.relpath, node.depth + 1, dirs)) do
|
|
||||||
table.insert(Tree, i + j, n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function clone(obj)
|
|
||||||
if type(obj) ~= 'table' then return obj end
|
|
||||||
local res = {}
|
|
||||||
for k, v in pairs(obj) do res[clone(k)] = clone(v) end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.find_file(path)
|
|
||||||
local relpath = string.sub(path, #ROOT_PATH + 1, -1)
|
|
||||||
|
|
||||||
local tree_copy = clone(Tree)
|
|
||||||
|
|
||||||
for i, node in pairs(tree_copy) do
|
|
||||||
if node.relpath and relpath:find(utils.path_to_matching_str(node.relpath)) then
|
|
||||||
if node.relpath == relpath then
|
|
||||||
Tree = clone(tree_copy)
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
if node.dir and not node.open then
|
|
||||||
local dirpath = node.path .. node.name
|
|
||||||
node.open = true
|
|
||||||
local dirs = list_dirs(dirpath)
|
|
||||||
for j, n in pairs(create_nodes(dirpath, node.relpath, node.depth + 1, dirs)) do
|
|
||||||
table.insert(tree_copy, i + j, n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.open_dir(tree_index)
|
|
||||||
local node = Tree[tree_index];
|
|
||||||
node.open = not node.open
|
|
||||||
|
|
||||||
if node.open == false then
|
|
||||||
local next_index = tree_index + 1;
|
|
||||||
local next_node = Tree[next_index]
|
|
||||||
|
|
||||||
while next_node ~= nil and next_node.depth > node.depth do
|
|
||||||
table.remove(Tree, next_index)
|
|
||||||
next_node = Tree[next_index]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local dirlist = list_dirs(tostring(node.path .. node.name))
|
|
||||||
local child_dirs = create_nodes(node.path .. node.name .. '/', node.relpath, node.depth + 1, dirlist)
|
|
||||||
|
|
||||||
for i, n in pairs(child_dirs) do
|
|
||||||
table.insert(Tree, tree_index + i, n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.get_tree()
|
|
||||||
return Tree
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
270
lua/lib/tree.lua
Normal file
270
lua/lib/tree.lua
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
local api = vim.api
|
||||||
|
local luv = vim.loop
|
||||||
|
|
||||||
|
local renderer = require'lib.renderer'
|
||||||
|
local config = require'lib.config'
|
||||||
|
local pops = require'lib.populate'
|
||||||
|
local populate = pops.populate
|
||||||
|
local refresh_entries = pops.refresh_entries
|
||||||
|
local update_git = pops.update_git_status
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.Tree = {
|
||||||
|
entries = {},
|
||||||
|
buf_name = 'LuaTree',
|
||||||
|
cwd = nil,
|
||||||
|
win_width = vim.g.lua_tree_width or 30,
|
||||||
|
loaded = false,
|
||||||
|
side = 'H',
|
||||||
|
bufnr = nil,
|
||||||
|
winnr = nil,
|
||||||
|
buf_options = {
|
||||||
|
'nowrap', 'sidescroll=5', 'nospell', 'nolist', 'nofoldenable',
|
||||||
|
'foldmethod=manual', 'foldcolumn=0', 'nonumber',
|
||||||
|
'noswapfile', 'splitbelow', 'noruler', 'noshowmode', 'noshowcmd'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vim.g.lua_tree_side == 'right' then
|
||||||
|
M.Tree.side = 'L'
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.init(with_open, with_render)
|
||||||
|
M.Tree.cwd = luv.cwd()
|
||||||
|
populate(M.Tree.entries, M.Tree.cwd, M.Tree)
|
||||||
|
|
||||||
|
local stat = luv.fs_stat(M.Tree.cwd)
|
||||||
|
M.Tree.last_modified = stat.mtime.sec
|
||||||
|
|
||||||
|
if with_open then
|
||||||
|
M.open()
|
||||||
|
end
|
||||||
|
|
||||||
|
if with_render then
|
||||||
|
renderer.draw(M.Tree, true)
|
||||||
|
M.Tree.loaded = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_node_at_line(line)
|
||||||
|
local index = 2
|
||||||
|
local function iter(entries)
|
||||||
|
for _, node in ipairs(entries) do
|
||||||
|
if index == line then
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
index = index + 1
|
||||||
|
if node.open == true then
|
||||||
|
local child = iter(node.entries)
|
||||||
|
if child ~= nil then return child end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return iter
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.get_node_at_cursor()
|
||||||
|
local cursor = api.nvim_win_get_cursor(M.Tree.winnr)
|
||||||
|
local line = cursor[1]
|
||||||
|
if line == 1 and M.Tree.cwd ~= "/" then
|
||||||
|
return { name = ".." }
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.Tree.cwd == "/" then
|
||||||
|
line = line + 1
|
||||||
|
end
|
||||||
|
return get_node_at_line(line)(M.Tree.entries)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.unroll_dir(node)
|
||||||
|
node.open = not node.open
|
||||||
|
if #node.entries > 0 then
|
||||||
|
renderer.draw(M.Tree, true)
|
||||||
|
else
|
||||||
|
populate(node.entries, node.absolute_path)
|
||||||
|
renderer.draw(M.Tree, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function refresh_git(node)
|
||||||
|
update_git(node.entries, node.absolute_path or node.cwd)
|
||||||
|
for _, entry in pairs(node.entries) do
|
||||||
|
if entry.entries ~= nil then
|
||||||
|
refresh_git(entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO update only entries where directory has changed
|
||||||
|
local function refresh_nodes(node)
|
||||||
|
refresh_entries(node.entries, node.absolute_path or node.cwd)
|
||||||
|
for _, entry in ipairs(node.entries) do
|
||||||
|
if entry.entries and entry.open then
|
||||||
|
refresh_nodes(entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.refresh_tree()
|
||||||
|
local stat = luv.fs_stat(M.Tree.cwd)
|
||||||
|
-- if stat.mtime.sec ~= M.Tree.last_modified then
|
||||||
|
refresh_nodes(M.Tree)
|
||||||
|
-- end
|
||||||
|
if config.get_icon_state().show_git_icon then
|
||||||
|
refresh_git(M.Tree)
|
||||||
|
end
|
||||||
|
if M.Tree.winnr ~= nil then
|
||||||
|
renderer.draw(M.Tree, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.set_index_and_redraw(fname)
|
||||||
|
local i
|
||||||
|
if M.Tree.cwd == '/' then
|
||||||
|
i = 0
|
||||||
|
else
|
||||||
|
i = 1
|
||||||
|
end
|
||||||
|
local reload = false
|
||||||
|
|
||||||
|
local function iter(entries)
|
||||||
|
for _, entry in ipairs(entries) do
|
||||||
|
i = i + 1
|
||||||
|
if entry.absolute_path == fname then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
if fname:match(entry.match_path..'/') ~= nil then
|
||||||
|
if #entry.entries == 0 then
|
||||||
|
reload = true
|
||||||
|
populate(entry.entries, entry.absolute_path)
|
||||||
|
end
|
||||||
|
if entry.open == false then
|
||||||
|
reload = true
|
||||||
|
entry.open = true
|
||||||
|
end
|
||||||
|
if iter(entry.entries) ~= nil then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
elseif entry.open == true then
|
||||||
|
iter(entry.entries)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local index = iter(M.Tree.entries)
|
||||||
|
if index then
|
||||||
|
api.nvim_win_set_cursor(M.Tree.winnr, {index, 0})
|
||||||
|
end
|
||||||
|
|
||||||
|
renderer.draw(M.Tree, reload)
|
||||||
|
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.open_file(mode, filename)
|
||||||
|
if vim.g.lua_tree_side == 'right' then
|
||||||
|
api.nvim_command('noautocmd wincmd h')
|
||||||
|
else
|
||||||
|
api.nvim_command('noautocmd wincmd l')
|
||||||
|
end
|
||||||
|
api.nvim_command(string.format("%s %s", mode, filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.change_dir(foldername)
|
||||||
|
api.nvim_command('cd '..foldername)
|
||||||
|
M.Tree.entries = {}
|
||||||
|
M.init(false, M.Tree.bufnr ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_mappings()
|
||||||
|
local buf = M.Tree.bufnr
|
||||||
|
local bindings = config.get_bindings()
|
||||||
|
|
||||||
|
local mappings = {
|
||||||
|
['<2-LeftMouse>'] = 'on_keypress("edit")';
|
||||||
|
['<2-RightMouse>'] = 'on_keypress("cd")';
|
||||||
|
[bindings.cd] = 'on_keypress("cd")';
|
||||||
|
[bindings.edit] = 'on_keypress("edit")';
|
||||||
|
[bindings.edit_vsplit] = 'on_keypress("vsplit")';
|
||||||
|
[bindings.edit_split] = 'on_keypress("split")';
|
||||||
|
[bindings.edit_tab] = 'on_keypress("tabnew")';
|
||||||
|
[bindings.create] = 'on_keypress("create")';
|
||||||
|
[bindings.remove] = 'on_keypress("remove")';
|
||||||
|
[bindings.rename] = 'on_keypress("rename")';
|
||||||
|
gx = "xdg_open()";
|
||||||
|
}
|
||||||
|
|
||||||
|
for k,v in pairs(mappings) do
|
||||||
|
api.nvim_buf_set_keymap(buf, 'n', k, ':lua require"tree".'..v..'<cr>', {
|
||||||
|
nowait = true, noremap = true, silent = true
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_buf()
|
||||||
|
local options = {
|
||||||
|
bufhidden = 'delete';
|
||||||
|
buftype = 'nofile';
|
||||||
|
modifiable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
M.Tree.bufnr = api.nvim_create_buf(false, true)
|
||||||
|
api.nvim_buf_set_name(M.Tree.bufnr, M.Tree.buf_name)
|
||||||
|
api.nvim_buf_set_option(M.Tree.bufnr, 'filetype', M.Tree.buf_name)
|
||||||
|
|
||||||
|
for opt, val in pairs(options) do
|
||||||
|
api.nvim_buf_set_option(M.Tree.bufnr, opt, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, opt in pairs(M.Tree.buf_options) do
|
||||||
|
api.nvim_command('setlocal '..opt)
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.Tree.side == 'L' then
|
||||||
|
api.nvim_command('setlocal nosplitright')
|
||||||
|
else
|
||||||
|
api.nvim_command('setlocal splitright')
|
||||||
|
end
|
||||||
|
|
||||||
|
set_mappings()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_win()
|
||||||
|
api.nvim_command("vsplit")
|
||||||
|
api.nvim_command("wincmd "..M.Tree.side)
|
||||||
|
api.nvim_command("vertical resize "..M.Tree.win_width)
|
||||||
|
|
||||||
|
M.Tree.winnr = api.nvim_get_current_win()
|
||||||
|
|
||||||
|
api.nvim_win_set_option(M.Tree.winnr, 'relativenumber', false)
|
||||||
|
api.nvim_win_set_option(M.Tree.winnr, 'winfixwidth', true)
|
||||||
|
api.nvim_win_set_option(M.Tree.winnr, 'winfixheight', true)
|
||||||
|
api.nvim_command('setlocal winhighlight+=EndOfBuffer:LuaTreeEndOfBuffer,Normal:LuaTreeNormal,CursorLine:LuaTreeCursorLine,VertSplit:LuaTreeVertSplit')
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.close()
|
||||||
|
api.nvim_win_close(M.Tree.winnr, true)
|
||||||
|
M.Tree.winnr = nil
|
||||||
|
M.Tree.bufnr = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.open()
|
||||||
|
create_buf()
|
||||||
|
create_win()
|
||||||
|
api.nvim_win_set_buf(M.Tree.winnr, M.Tree.bufnr)
|
||||||
|
renderer.draw(M.Tree, not M.Tree.loaded)
|
||||||
|
M.Tree.loaded = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.win_open()
|
||||||
|
for _, win in pairs(api.nvim_list_wins()) do
|
||||||
|
if win == M.Tree.winnr then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@ -1,7 +0,0 @@
|
|||||||
local M = {}
|
|
||||||
|
|
||||||
function M.path_to_matching_str(path)
|
|
||||||
return path:gsub('(%-)', '(%%-)'):gsub('(%.)', '(%%.)')
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@ -1,164 +0,0 @@
|
|||||||
local api = vim.api
|
|
||||||
|
|
||||||
local libformat = require 'lib/format'
|
|
||||||
local format = libformat.format_tree
|
|
||||||
local highlight = libformat.highlight_buffer
|
|
||||||
|
|
||||||
local stateutils = require 'lib/state'
|
|
||||||
local get_tree = stateutils.get_tree
|
|
||||||
|
|
||||||
local bindings = require 'lib/config'.bindings
|
|
||||||
|
|
||||||
local M = {
|
|
||||||
BUF_NAME = 'LuaTree'
|
|
||||||
}
|
|
||||||
|
|
||||||
function M.get_buf()
|
|
||||||
local regex = '.*'..M.BUF_NAME..'$';
|
|
||||||
|
|
||||||
for _, win in pairs(api.nvim_list_wins()) do
|
|
||||||
local buf = api.nvim_win_get_buf(win)
|
|
||||||
local buf_name = api.nvim_buf_get_name(buf)
|
|
||||||
|
|
||||||
if string.match(buf_name, regex) ~= nil then return buf end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.get_win()
|
|
||||||
local regex = '.*'..M.BUF_NAME..'$';
|
|
||||||
|
|
||||||
for _, win in pairs(api.nvim_list_wins()) do
|
|
||||||
local buf_name = api.nvim_buf_get_name(api.nvim_win_get_buf(win))
|
|
||||||
if string.match(buf_name, regex) ~= nil then return win end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local BUF_OPTIONS = {
|
|
||||||
'nowrap', 'sidescroll=5', 'nospell', 'nolist', 'nofoldenable',
|
|
||||||
'foldmethod=manual', 'foldcolumn=0', 'nonumber', 'norelativenumber',
|
|
||||||
'winfixwidth', 'winfixheight', 'noswapfile', 'splitbelow', 'noruler',
|
|
||||||
'noshowmode', 'noshowcmd'
|
|
||||||
}
|
|
||||||
|
|
||||||
local WIN_WIDTH = 30
|
|
||||||
local SIDE = 'H'
|
|
||||||
|
|
||||||
if api.nvim_call_function('exists', { 'g:lua_tree_width' }) == 1 then
|
|
||||||
WIN_WIDTH = api.nvim_get_var('lua_tree_width')
|
|
||||||
end
|
|
||||||
|
|
||||||
if api.nvim_call_function('exists', { 'g:lua_tree_side' }) == 1 then
|
|
||||||
if api.nvim_get_var('lua_tree_side') == 'right' then
|
|
||||||
SIDE = 'L'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.open()
|
|
||||||
local options = {
|
|
||||||
bufhidden = 'wipe';
|
|
||||||
buftype = 'nowrite';
|
|
||||||
modifiable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
local buf = api.nvim_create_buf(false, true)
|
|
||||||
api.nvim_buf_set_name(buf, M.BUF_NAME)
|
|
||||||
api.nvim_buf_set_option(buf, 'filetype', M.BUF_NAME)
|
|
||||||
|
|
||||||
for opt, val in pairs(options) do
|
|
||||||
api.nvim_buf_set_option(buf, opt, val)
|
|
||||||
end
|
|
||||||
|
|
||||||
api.nvim_command('vsplit')
|
|
||||||
api.nvim_command('wincmd '..SIDE)
|
|
||||||
api.nvim_command('vertical resize '..WIN_WIDTH)
|
|
||||||
api.nvim_win_set_buf(0, buf)
|
|
||||||
|
|
||||||
api.nvim_command('setlocal winhighlight=EndOfBuffer:LuaTreeEndOfBuffer,Normal:LuaTreeNormal,CursorLine:LuaTreeCursorLine,VertSplit:LuaTreeVertSplit')
|
|
||||||
for _, opt in pairs(BUF_OPTIONS) do
|
|
||||||
api.nvim_command('setlocal '..opt)
|
|
||||||
end
|
|
||||||
if SIDE == 'L' then
|
|
||||||
api.nvim_command('setlocal nosplitright')
|
|
||||||
else
|
|
||||||
api.nvim_command('setlocal splitright')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.replace_tree()
|
|
||||||
local win = M.get_win()
|
|
||||||
if not win then return end
|
|
||||||
|
|
||||||
local tree_position = api.nvim_win_get_position(win)
|
|
||||||
local win_width = api.nvim_win_get_width(win)
|
|
||||||
if win_width == WIN_WIDTH then
|
|
||||||
if SIDE == 'H' and tree_position[2] == 0 then return end
|
|
||||||
local columns = api.nvim_get_option('columns')
|
|
||||||
if SIDE == 'L' and tree_position[2] ~= columns - win_width then return end
|
|
||||||
end
|
|
||||||
|
|
||||||
local current_win = api.nvim_get_current_win()
|
|
||||||
|
|
||||||
api.nvim_set_current_win(win)
|
|
||||||
api.nvim_command('wincmd '..SIDE)
|
|
||||||
api.nvim_command('vertical resize '..WIN_WIDTH)
|
|
||||||
|
|
||||||
api.nvim_set_current_win(current_win)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.close()
|
|
||||||
local win = M.get_win()
|
|
||||||
if not win then return end
|
|
||||||
|
|
||||||
api.nvim_win_close(win, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.update_view(update_cursor)
|
|
||||||
local buf = M.get_buf();
|
|
||||||
if not buf then return end
|
|
||||||
|
|
||||||
local cursor = api.nvim_win_get_cursor(0)
|
|
||||||
local tree = get_tree()
|
|
||||||
|
|
||||||
api.nvim_buf_set_option(buf, 'modifiable', true)
|
|
||||||
api.nvim_buf_set_lines(buf, 0, -1, false, format(tree))
|
|
||||||
highlight(buf, tree)
|
|
||||||
api.nvim_buf_set_option(buf, 'modifiable', false)
|
|
||||||
|
|
||||||
if update_cursor == true then
|
|
||||||
api.nvim_win_set_cursor(0, cursor)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.set_mappings()
|
|
||||||
local buf = M.get_buf()
|
|
||||||
if not buf then return end
|
|
||||||
|
|
||||||
local mappings = {
|
|
||||||
['<2-LeftMouse>'] = 'open_file("edit")';
|
|
||||||
['<2-RightMouse>'] = 'open_file("chdir")';
|
|
||||||
[bindings.edit] = 'open_file("edit")';
|
|
||||||
[bindings.edit_vsplit] = 'open_file("vsplit")';
|
|
||||||
[bindings.edit_split] = 'open_file("split")';
|
|
||||||
[bindings.edit_tab] = 'open_file("tabnew")';
|
|
||||||
[bindings.cd] = 'open_file("chdir")';
|
|
||||||
[bindings.create] = 'edit_file("create")';
|
|
||||||
[bindings.remove] = 'edit_file("remove")';
|
|
||||||
[bindings.rename] = 'edit_file("rename")';
|
|
||||||
}
|
|
||||||
|
|
||||||
for k,v in pairs(mappings) do
|
|
||||||
api.nvim_buf_set_keymap(buf, 'n', k, ':lua require"tree".'..v..'<cr>', {
|
|
||||||
nowait = true, noremap = true, silent = true
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.is_win_open()
|
|
||||||
return M.get_buf() ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
276
lua/tree.lua
276
lua/tree.lua
@ -1,206 +1,150 @@
|
|||||||
|
local luv = vim.loop
|
||||||
|
local tree = require'lib.tree'
|
||||||
|
local colors = require'lib.colors'
|
||||||
|
local renderer = require'lib.renderer'
|
||||||
|
local fs = require'lib.fs'
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
|
|
||||||
local fs_update = require 'lib/fs_update'
|
|
||||||
local create_file = fs_update.create_file
|
|
||||||
local rename_file = fs_update.rename_file
|
|
||||||
local remove_file = fs_update.remove_file
|
|
||||||
|
|
||||||
local fs = require 'lib/fs'
|
|
||||||
local check_dir_access = fs.check_dir_access
|
|
||||||
local is_dir = fs.is_dir
|
|
||||||
local get_cwd = fs.get_cwd
|
|
||||||
|
|
||||||
local state = require 'lib/state'
|
|
||||||
local get_tree = state.get_tree
|
|
||||||
local init_tree = state.init_tree
|
|
||||||
local open_dir = state.open_dir
|
|
||||||
local refresh_tree = state.refresh_tree
|
|
||||||
local set_root_path = state.set_root_path
|
|
||||||
local find_file = state.find_file
|
|
||||||
|
|
||||||
local winutils = require 'lib/winutils'
|
|
||||||
local update_view = winutils.update_view
|
|
||||||
local is_win_open = winutils.is_win_open
|
|
||||||
local close = winutils.close
|
|
||||||
local open = winutils.open
|
|
||||||
local set_mappings = winutils.set_mappings
|
|
||||||
local get_win = winutils.get_win
|
|
||||||
|
|
||||||
local git = require 'lib/git'
|
|
||||||
local refresh_git = git.refresh_git
|
|
||||||
local force_refresh_git = git.force_refresh_git
|
|
||||||
|
|
||||||
local colors = require 'lib/colors'
|
|
||||||
colors.init_colors()
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.replace_tree = winutils.replace_tree
|
|
||||||
|
|
||||||
init_tree()
|
|
||||||
|
|
||||||
function M.toggle()
|
function M.toggle()
|
||||||
if is_win_open() == true then
|
if tree.win_open() then
|
||||||
local wins = api.nvim_list_wins()
|
tree.close()
|
||||||
if #wins > 1 then close() end
|
|
||||||
else
|
else
|
||||||
open()
|
tree.open()
|
||||||
update_view()
|
|
||||||
set_mappings()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local MOVE_TO = 'l'
|
function M.close()
|
||||||
if api.nvim_call_function('exists', { 'g:lua_tree_side' }) == 1 then
|
if tree.win_open() then
|
||||||
if api.nvim_get_var('lua_tree_side') == 'right' then
|
tree.close()
|
||||||
MOVE_TO = 'h'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_new_buf(open_type, bufname)
|
function M.open()
|
||||||
if open_type == 'edit' or open_type == 'split' then
|
if not tree.win_open() then
|
||||||
api.nvim_command('wincmd '..MOVE_TO..' | '..open_type..' '..bufname)
|
tree.open()
|
||||||
elseif open_type == 'vsplit' then
|
|
||||||
local windows = api.nvim_list_wins();
|
|
||||||
api.nvim_command(#windows..'wincmd '..MOVE_TO..' | vsplit '..bufname)
|
|
||||||
elseif open_type == 'tabnew' then
|
|
||||||
api.nvim_command('tabnew '..bufname)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.open_file(open_type)
|
function M.on_keypress(mode)
|
||||||
local tree_index = api.nvim_win_get_cursor(0)[1]
|
local node = tree.get_node_at_cursor()
|
||||||
local tree = get_tree()
|
if not node then return end
|
||||||
local node = tree[tree_index]
|
|
||||||
|
|
||||||
if node.name == '..' then
|
if mode == 'create' then
|
||||||
api.nvim_command('cd '..node.path..'/..')
|
return fs.create(node)
|
||||||
|
elseif mode == 'remove' then
|
||||||
local new_path = get_cwd()
|
return fs.remove(node)
|
||||||
if new_path ~= '/' then
|
elseif mode == 'rename' then
|
||||||
new_path = new_path .. '/'
|
return fs.rename(node)
|
||||||
end
|
end
|
||||||
|
|
||||||
set_root_path(new_path)
|
if node.name == ".." then
|
||||||
force_refresh_git()
|
return tree.change_dir("..")
|
||||||
init_tree(new_path)
|
elseif mode == "cd" and node.entries ~= nil then
|
||||||
update_view()
|
return tree.change_dir(node.absolute_path)
|
||||||
|
elseif mode == "cd" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
elseif open_type == 'chdir' then
|
if node.link_to then
|
||||||
if node.dir == false or check_dir_access(node.path .. node.name) == false then return end
|
local stat = luv.fs_stat(node.link_to)
|
||||||
|
if stat.type == 'directory' then return end
|
||||||
api.nvim_command('cd ' .. node.path .. node.name)
|
tree.open_file(mode, node.link_to)
|
||||||
local new_path = get_cwd() .. '/'
|
elseif node.entries ~= nil then
|
||||||
set_root_path(new_path)
|
tree.unroll_dir(node)
|
||||||
force_refresh_git()
|
|
||||||
init_tree(new_path)
|
|
||||||
update_view()
|
|
||||||
|
|
||||||
elseif node.link == true then
|
|
||||||
local link_to_dir = is_dir(node.linkto)
|
|
||||||
if link_to_dir == true and check_dir_access(node.linkto) == false then return end
|
|
||||||
|
|
||||||
if link_to_dir == true then
|
|
||||||
api.nvim_command('cd ' .. node.linkto)
|
|
||||||
local new_path = get_cwd() .. '/'
|
|
||||||
set_root_path(new_path)
|
|
||||||
force_refresh_git()
|
|
||||||
init_tree(new_path)
|
|
||||||
update_view()
|
|
||||||
else
|
else
|
||||||
create_new_buf(open_type, node.link_to);
|
tree.open_file(mode, node.absolute_path)
|
||||||
end
|
|
||||||
|
|
||||||
elseif node.dir == true then
|
|
||||||
if check_dir_access(node.path .. node.name) == false then return end
|
|
||||||
open_dir(tree_index)
|
|
||||||
update_view(true)
|
|
||||||
else
|
|
||||||
create_new_buf(open_type, node.path .. node.name);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.edit_file(edit_type)
|
|
||||||
local tree = get_tree()
|
|
||||||
local tree_index = api.nvim_win_get_cursor(0)[1]
|
|
||||||
local node = tree[tree_index]
|
|
||||||
|
|
||||||
if edit_type == 'create' then
|
|
||||||
if node.dir == true then
|
|
||||||
create_file(node.path .. node.name .. '/')
|
|
||||||
else
|
|
||||||
create_file(node.path)
|
|
||||||
end
|
|
||||||
elseif edit_type == 'remove' then
|
|
||||||
remove_file(node.name, node.path)
|
|
||||||
elseif edit_type == 'rename' then
|
|
||||||
rename_file(node.name, node.path)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.refresh()
|
function M.refresh()
|
||||||
if refresh_git() == true then
|
tree.refresh_tree()
|
||||||
refresh_tree()
|
end
|
||||||
update_view()
|
|
||||||
|
function M.on_enter()
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
local bufname = api.nvim_buf_get_name(bufnr)
|
||||||
|
|
||||||
|
local stats = luv.fs_stat(bufname)
|
||||||
|
local is_dir = stats and stats.type == 'directory'
|
||||||
|
if is_dir then
|
||||||
|
api.nvim_command('cd '..bufname)
|
||||||
|
end
|
||||||
|
local should_open = vim.g.lua_tree_auto_open == 1 and (bufname == '' or is_dir)
|
||||||
|
colors.setup()
|
||||||
|
tree.init(should_open, should_open)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_file_readable(fname)
|
||||||
|
local stat = luv.fs_stat(fname)
|
||||||
|
if not stat or not stat.type == 'file' or not luv.fs_access(fname, 'R') then return false end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_file()
|
||||||
|
if not tree.win_open() then return end
|
||||||
|
local bufname = api.nvim_buf_get_name(api.nvim_get_current_buf())
|
||||||
|
if not is_file_readable(bufname) then return end
|
||||||
|
|
||||||
|
tree.set_index_and_redraw(bufname)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_leave()
|
||||||
|
if #api.nvim_list_wins() == 1 and tree.win_open() then
|
||||||
|
api.nvim_command(':qa!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.check_windows_and_close()
|
local function update_root_dir()
|
||||||
local wins = api.nvim_list_wins()
|
local bufname = api.nvim_buf_get_name(api.nvim_get_current_buf())
|
||||||
|
if not is_file_readable(bufname) or not tree.Tree.cwd then return end
|
||||||
|
|
||||||
if #wins == 1 and is_win_open() then
|
-- this logic is a hack
|
||||||
api.nvim_command('q!')
|
-- depending on vim-rooter or autochdir, it would not behave the same way when those two are not enabled
|
||||||
end
|
-- until i implement multiple workspaces/project, it should stay like this
|
||||||
end
|
if bufname:match(tree.Tree.cwd:gsub('(%-)', '(%%-)'):gsub('(%.)', '(%%.)')) ~= nil then
|
||||||
|
|
||||||
function M.navigate_to_buffer_dir(bufname)
|
|
||||||
local new_path = get_cwd()
|
|
||||||
if new_path ~= '/' then
|
|
||||||
new_path = new_path .. '/'
|
|
||||||
end
|
|
||||||
if new_path == state.get_root_path() then
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
set_root_path(new_path)
|
local new_cwd = luv.cwd()
|
||||||
init_tree()
|
if tree.Tree.cwd == new_cwd then return end
|
||||||
|
|
||||||
|
tree.change_dir(new_cwd)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.check_buffer_and_open()
|
function M.buf_enter()
|
||||||
local bufname = api.nvim_buf_get_name(0)
|
if vim.g.lua_tree_auto_close ~= 0 then
|
||||||
if bufname == '' then
|
on_leave()
|
||||||
M.toggle()
|
|
||||||
elseif is_dir(bufname) then
|
|
||||||
api.nvim_command('cd ' .. bufname)
|
|
||||||
|
|
||||||
local new_path = get_cwd()
|
|
||||||
if new_path ~= '/' then
|
|
||||||
new_path = new_path .. '/'
|
|
||||||
end
|
|
||||||
set_root_path(new_path)
|
|
||||||
init_tree()
|
|
||||||
|
|
||||||
M.toggle()
|
|
||||||
else
|
|
||||||
M.navigate_to_buffer_dir()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.find()
|
|
||||||
local line = find_file(api.nvim_buf_get_name(0))
|
|
||||||
if not line then return end
|
|
||||||
|
|
||||||
update_view()
|
|
||||||
|
|
||||||
local win = get_win()
|
|
||||||
if win then
|
|
||||||
api.nvim_win_set_cursor(win, { line, 0 })
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
update_root_dir()
|
||||||
|
if vim.g.lua_tree_follow ~= 0 then
|
||||||
|
find_file()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.reset_highlight()
|
function M.reset_highlight()
|
||||||
colors.init_colors()
|
colors.setup()
|
||||||
update_view()
|
renderer.render_hl(tree.Tree.bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.xdg_open()
|
||||||
|
local node = tree.get_node_at_cursor()
|
||||||
|
-- TODO: this should open symlink targets
|
||||||
|
if not node or node.entries or node.link_to then return end
|
||||||
|
|
||||||
|
local cmd
|
||||||
|
if vim.fn.has('unix') == 1 then
|
||||||
|
cmd = 'xdg-open'
|
||||||
|
else
|
||||||
|
cmd = 'open'
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.loop.spawn(cmd, {args={node.absolute_path}}, vim.schedule_wrap(function(code)
|
||||||
|
if code ~= 0 then
|
||||||
|
api.nvim_err_writeln("Could not open "..node.absolute_path)
|
||||||
|
end
|
||||||
|
end))
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@ -10,32 +10,16 @@ hi def link LuaTreePopup Normal
|
|||||||
|
|
||||||
augroup LuaTree
|
augroup LuaTree
|
||||||
au BufWritePost * lua require'tree'.refresh()
|
au BufWritePost * lua require'tree'.refresh()
|
||||||
|
au BufEnter * lua require'tree'.buf_enter()
|
||||||
if get(g:, 'lua_tree_auto_close') != 0
|
au VimEnter * lua require'tree'.on_enter()
|
||||||
au BufEnter * lua require'tree'.check_windows_and_close()
|
|
||||||
endif
|
|
||||||
|
|
||||||
if get(g:, 'lua_tree_auto_open') != 0
|
|
||||||
au VimEnter * lua require'tree'.check_buffer_and_open()
|
|
||||||
endif
|
|
||||||
|
|
||||||
if get(g:, 'lua_tree_follow') != 0
|
|
||||||
au BufEnter * :LuaTreeFindFile
|
|
||||||
endif
|
|
||||||
|
|
||||||
au BufEnter * lua require'tree'.navigate_to_buffer_dir()
|
|
||||||
au ColorScheme * lua require'tree'.reset_highlight()
|
au ColorScheme * lua require'tree'.reset_highlight()
|
||||||
augroup end
|
augroup end
|
||||||
|
|
||||||
" TODO: WinEnter is not the right autocommand for this task,
|
command! LuaTreeOpen lua require'tree'.open()
|
||||||
" but we do not have LayoutChange or WinMove kind of option atm,
|
command! LuaTreeClose lua require'tree'.close()
|
||||||
" so this is deactivated by default to avoid messing up users workflows
|
|
||||||
|
|
||||||
" au WinEnter * lua require'tree'.replace_tree()
|
|
||||||
|
|
||||||
command! LuaTreeToggle lua require'tree'.toggle()
|
command! LuaTreeToggle lua require'tree'.toggle()
|
||||||
command! LuaTreeRefresh lua require'tree'.refresh()
|
command! LuaTreeRefresh lua require'tree'.refresh()
|
||||||
command! LuaTreeFindFile lua require'tree'.find()
|
command! LuaTreeFindFile lua require'tree'.find_file()
|
||||||
|
|
||||||
let &cpo = s:save_cpo
|
let &cpo = s:save_cpo
|
||||||
unlet s:save_cpo
|
unlet s:save_cpo
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user