mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-04-13 13:59:10 +08:00
feat(neotree): add neotree support
This commit is contained in:
parent
f7fd9a193c
commit
5c83ab2f06
@ -86,6 +86,9 @@ function! SpaceVim#layers#core#plugins() abort
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/defx-sftp',{'merged' : 0}])
|
||||
elseif g:spacevim_filemanager ==# 'nvim-tree'
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/nvim-tree.lua',{'merged' : 0, 'loadconf' : 1}])
|
||||
elseif g:spacevim_filemanager ==# 'neo-tree'
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/neo-tree.nvim',{'merged' : 0, 'loadconf' : 1}])
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/nui.nvim',{'merged' : 0}])
|
||||
endif
|
||||
|
||||
if !g:spacevim_vimcompatible
|
||||
|
@ -452,6 +452,13 @@ function! SpaceVim#layers#core#statusline#get(...) abort
|
||||
\ . ' NvimTree '
|
||||
\ . '%#SpaceVim_statusline_b_SpaceVim_statusline_c#'
|
||||
\ . s:lsep . ' '
|
||||
elseif &filetype ==# 'neo-tree'
|
||||
return '%#SpaceVim_statusline_ia#' . s:winnr(1)
|
||||
\ . '%#SpaceVim_statusline_ia_SpaceVim_statusline_b#' . s:lsep
|
||||
\ . '%#SpaceVim_statusline_b#'
|
||||
\ . ' NeoTree '
|
||||
\ . '%#SpaceVim_statusline_b_SpaceVim_statusline_c#'
|
||||
\ . s:lsep . ' '
|
||||
elseif &filetype ==# 'Fuzzy'
|
||||
return '%#SpaceVim_statusline_a_bold# Fuzzy %#SpaceVim_statusline_a_SpaceVim_statusline_b#' . s:lsep
|
||||
\ . '%#SpaceVim_statusline_b# %{fuzzy#statusline()} %#SpaceVim_statusline_b_SpaceVim_statusline_c#' . s:lsep
|
||||
|
10
bundle/neo-tree.nvim/.codecov.yml
vendored
Normal file
10
bundle/neo-tree.nvim/.codecov.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
only_pulls: true
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
only_pulls: true
|
73
bundle/neo-tree.nvim/.github/workflows/ci.yml
vendored
Normal file
73
bundle/neo-tree.nvim/.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v1.x
|
||||
- v2.x
|
||||
- v3.x
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
stylua-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Check formatting
|
||||
uses: JohnnyMorganz/stylua-action@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: v0.16.0
|
||||
args: --color always --check -g '!**/defaults.lua' lua/
|
||||
|
||||
plenary-tests:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: date +%F > todays-date
|
||||
- name: Restore cache for today's nightly.
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: build
|
||||
key: ${{ runner.os }}-appimage-${{ hashFiles('todays-date') }}
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
test -d build || {
|
||||
mkdir -p build
|
||||
wget https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage
|
||||
chmod +x nvim.appimage
|
||||
mv nvim.appimage ./build/nvim
|
||||
}
|
||||
|
||||
# - name: Get Luver Cache Key
|
||||
# id: luver-cache-key
|
||||
# env:
|
||||
# CI_RUNNER_OS: ${{ runner.os }}
|
||||
# run: |
|
||||
# echo "::set-output name=value::${CI_RUNNER_OS}-luver-v1-$(date -u +%Y-%m-%d)"
|
||||
# shell: bash
|
||||
# - name: Setup Luver Cache
|
||||
# uses: actions/cache@v2
|
||||
# with:
|
||||
# path: ~/.local/share/luver
|
||||
# key: ${{ steps.luver-cache-key.outputs.value }}
|
||||
|
||||
# - name: Setup Lua
|
||||
# uses: MunifTanjim/luver-action@v1
|
||||
# with:
|
||||
# default: 5.1.5
|
||||
# lua_versions: 5.1.5
|
||||
# luarocks_versions: 5.1.5:3.8.0
|
||||
# - name: Setup luacov
|
||||
# run: |
|
||||
# luarocks install luacov
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
export PATH="${PWD}/build/:${PATH}"
|
||||
./scripts/test.sh
|
||||
|
||||
# - name: Upload coverage to Codecov
|
||||
# uses: codecov/codecov-action@v2
|
29
bundle/neo-tree.nvim/.github/workflows/protect_release_branches.yml
vendored
Normal file
29
bundle/neo-tree.nvim/.github/workflows/protect_release_branches.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: No PRs to Release Branches
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the v1.x branch
|
||||
pull_request:
|
||||
types: [opened, edited, ready_for_review]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
check_target:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Runs a single command using the runners shell
|
||||
- name: Fail when targeting v2
|
||||
run: |
|
||||
target=${{ github.base_ref }}
|
||||
echo "Target is: $target"
|
||||
if [[ $target == "v2.x" ]]; then
|
||||
echo "PRs must target main"
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
47
bundle/neo-tree.nvim/.gitignore
vendored
Normal file
47
bundle/neo-tree.nvim/.gitignore
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
# Compiled Lua sources
|
||||
luac.out
|
||||
|
||||
# luarocks build files
|
||||
*.src.rock
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.os
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
*.def
|
||||
*.exp
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Vim tag files
|
||||
tags
|
||||
|
||||
# Others
|
||||
.testcache
|
||||
luacov.*.out
|
3
bundle/neo-tree.nvim/.luacov
vendored
Normal file
3
bundle/neo-tree.nvim/.luacov
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
include = {
|
||||
"lua%/neo%-tree",
|
||||
}
|
3
bundle/neo-tree.nvim/.luarc.json
vendored
Normal file
3
bundle/neo-tree.nvim/.luarc.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"diagnostics.globals": ["vim"]
|
||||
}
|
5
bundle/neo-tree.nvim/.stylua.toml
vendored
Normal file
5
bundle/neo-tree.nvim/.stylua.toml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
column_width = 100
|
||||
line_endings = "Unix"
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
quote_style = "AutoPreferDouble"
|
1
bundle/neo-tree.nvim/.styluaignore
vendored
Normal file
1
bundle/neo-tree.nvim/.styluaignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
**/defaults.lua
|
58
bundle/neo-tree.nvim/CONTRIBUTING.md
vendored
Normal file
58
bundle/neo-tree.nvim/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
# Contributing to Neo-tree
|
||||
|
||||
Contributions are welcome! To keep everything clean and tidy, please follow the
|
||||
guidelines below.
|
||||
|
||||
## Code Style
|
||||
|
||||
This is open for debate, but here is the current style choices being observed:
|
||||
|
||||
- snake_case for all variables and functions
|
||||
- unless it is a class, then use PascalCase
|
||||
- other OOP things, like method names should use camelCase
|
||||
- BUT we don't currently have any OOP parts and I don't think we want any
|
||||
|
||||
I prefer `local name = function()` over `local function name()`, just to be
|
||||
consistent with the `M.name = function()` exports.
|
||||
|
||||
### StyLua
|
||||
|
||||
We use (StyLua)[https://github.com/JohnnyMorganz/StyLua] to enforce consistency
|
||||
in code. You should install it on your local machine. PRs will be checked with
|
||||
this tool.
|
||||
|
||||
## Commit Messages
|
||||
|
||||
We use **semantic**, aka **conventional** commit messages. The official guide
|
||||
can be found here: https://www.conventionalcommits.org/en/v1.0.0/
|
||||
|
||||
You can also just take a look at the commit history to get the idea. The
|
||||
optional scope for this project would usually be the source, i.e.
|
||||
`feat(filesystem): add awesome feature that does xyz`.
|
||||
|
||||
## Branching
|
||||
|
||||
The default branch is set to the current major version to make it simple for end
|
||||
users visiting the repo. Pull Requests, however, should go to the `main`
|
||||
branch. After a short testing period, it will be merged to the current release
|
||||
branch.
|
||||
|
||||
This project requires a **linear history**. I don't trust merge commits.
|
||||
This means you will have to rebase your branch on main before the pull request
|
||||
can be merged. This can get a bit annoying in a busy repository, but I think it
|
||||
is worth the effort.
|
||||
|
||||
## Documentation
|
||||
|
||||
All new features should be documented in the commit they were added in. The
|
||||
current strategy is to maintain:
|
||||
|
||||
- Config Options: added to [defaults](lua/neo-tree/defaults.lua) and described
|
||||
in comments
|
||||
- The README contains "back of the box" high level overview of features. It is
|
||||
meant for people trying to decide if they want to install this plugin or not.
|
||||
It should include references to the help file for more information:
|
||||
`:h neo-tree-setup`
|
||||
- The vim help file [doc/neo-tree.txt](doc/neo-tree.txt) is the definitive
|
||||
reference and should contain all information needed to configure and use the
|
||||
plugin.
|
18
bundle/neo-tree.nvim/Dockerfile
vendored
Normal file
18
bundle/neo-tree.nvim/Dockerfile
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt update
|
||||
# install neovim dependencies
|
||||
RUN apt install -y git ninja-build gettext libtool libtool-bin autoconf \
|
||||
automake cmake g++ pkg-config unzip curl doxygen
|
||||
|
||||
# install neovim
|
||||
RUN git clone https://github.com/neovim/neovim
|
||||
RUN cd neovim && make CMAKE_BUILD_TYPE=RelWithDebInfo && make install
|
||||
|
||||
# install required plugins
|
||||
ARG PLUG_DIR="root/.local/share/nvim/site/pack/packer/start"
|
||||
RUN git clone https://github.com/nvim-lua/plenary.nvim $PLUG_DIR/plenary.nvim
|
||||
RUN git clone https://github.com/MunifTanjim/nui.nvim $PLUG_DIR/nui.nvim
|
||||
COPY . $PLUG_DIR/neo-tree.nvim
|
||||
|
||||
WORKDIR $PLUG_DIR/neo-tree.nvim
|
22
bundle/neo-tree.nvim/LICENSE
vendored
Normal file
22
bundle/neo-tree.nvim/LICENSE
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 cseickel (https://github.com/cseickel) and nvim-neo-tree
|
||||
maintainers.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
12
bundle/neo-tree.nvim/Makefile
vendored
Normal file
12
bundle/neo-tree.nvim/Makefile
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
.PHONY: test
|
||||
test:
|
||||
nvim --headless --noplugin -u tests/mininit.lua -c "lua require('plenary.test_harness').test_directory('tests/neo-tree/', {minimal_init='tests/mininit.lua',sequential=true})"
|
||||
|
||||
.PHONY: test-docker
|
||||
test-docker:
|
||||
docker build -t neo-tree .
|
||||
docker run --rm neo-tree make test
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
stylua --glob '*.lua' --glob '!defaults.lua' .
|
770
bundle/neo-tree.nvim/README.md
vendored
Normal file
770
bundle/neo-tree.nvim/README.md
vendored
Normal file
@ -0,0 +1,770 @@
|
||||
# Neo-tree.nvim
|
||||
|
||||
Neo-tree is a Neovim plugin to browse the file system and other tree like
|
||||
structures in whatever style suits you, including sidebars, floating windows,
|
||||
netrw split style, or all of them at once!
|
||||
|
||||

|
||||
|
||||
### Breaking Changes BAD :bomb: :imp:
|
||||
|
||||
The biggest and most important feature of Neo-tree is that we will never
|
||||
knowingly push a breaking change and interrupt your day. Bugs happen, but
|
||||
breaking changes can always be avoided. When breaking changes are needed, there
|
||||
will be a new branch that you can opt into, when it is a good time for you.
|
||||
|
||||
See [What is a Breaking Change?](#what-is-a-breaking-change) for details.
|
||||
|
||||
See [Changelog 2.0](https://github.com/nvim-neo-tree/neo-tree.nvim/wiki/Changelog#20)
|
||||
for breaking changes and deprecations in 2.0.
|
||||
|
||||
|
||||
### User Experience GOOD :slightly_smiling_face: :thumbsup:
|
||||
|
||||
Aside from being polite about breaking changes, Neo-tree is also focused on the
|
||||
little details of user experience. Everything should work exactly as you would
|
||||
expect a sidebar to work without all of the glitchy behavior that is normally
|
||||
accepted in (neo)vim sidebars. I can't stand glitchy behavior, and neither
|
||||
should you!
|
||||
|
||||
- Neo-tree won't let other buffers take over its window.
|
||||
- Neo-tree won't leave its window scrolled to the last line when there is
|
||||
plenty of room to display the whole tree.
|
||||
- Neo-tree does not need to be manually refreshed (set `use_libuv_file_watcher=true`)
|
||||
- Neo-tree can intelligently follow the current file (set `follow_current_file=true`)
|
||||
- Neo-tree is thoughtful about maintaining or setting focus on the right node
|
||||
- Neo-tree windows in different tabs are completely separate
|
||||
- `respect_gitignore` actually works!
|
||||
|
||||
Neo-tree is smooth, efficient, stable, and pays attention to the little details.
|
||||
If you find anything janky, wanky, broken, or unintuitive, please open an issue
|
||||
so we can fix it.
|
||||
|
||||
|
||||
## Minimal Quickstart
|
||||
|
||||
#### Minimal Example for Packer:
|
||||
```lua
|
||||
-- Unless you are still migrating, remove the deprecated commands from v1.x
|
||||
vim.cmd([[ let g:neo_tree_remove_legacy_commands = 1 ]])
|
||||
|
||||
use {
|
||||
"nvim-neo-tree/neo-tree.nvim",
|
||||
branch = "v2.x",
|
||||
requires = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
"nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
|
||||
"MunifTanjim/nui.nvim",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After installing, run:
|
||||
```
|
||||
:Neotree
|
||||
```
|
||||
|
||||
Press `?` in the Neo-tree window to view the list of mappings.
|
||||
|
||||
|
||||
## Quickstart
|
||||
|
||||
#### Longer Example for Packer:
|
||||
|
||||
```lua
|
||||
use {
|
||||
"nvim-neo-tree/neo-tree.nvim",
|
||||
branch = "v2.x",
|
||||
requires = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
"nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
|
||||
"MunifTanjim/nui.nvim",
|
||||
{
|
||||
-- only needed if you want to use the commands with "_with_window_picker" suffix
|
||||
's1n7ax/nvim-window-picker',
|
||||
tag = "v1.*",
|
||||
config = function()
|
||||
require'window-picker'.setup({
|
||||
autoselect_one = true,
|
||||
include_current = false,
|
||||
filter_rules = {
|
||||
-- filter using buffer options
|
||||
bo = {
|
||||
-- if the file type is one of following, the window will be ignored
|
||||
filetype = { 'neo-tree', "neo-tree-popup", "notify" },
|
||||
|
||||
-- if the buffer type is one of following, the window will be ignored
|
||||
buftype = { 'terminal', "quickfix" },
|
||||
},
|
||||
},
|
||||
other_win_hl_color = '#e35e4f',
|
||||
})
|
||||
end,
|
||||
}
|
||||
},
|
||||
config = function ()
|
||||
-- Unless you are still migrating, remove the deprecated commands from v1.x
|
||||
vim.cmd([[ let g:neo_tree_remove_legacy_commands = 1 ]])
|
||||
|
||||
-- If you want icons for diagnostic errors, you'll need to define them somewhere:
|
||||
vim.fn.sign_define("DiagnosticSignError",
|
||||
{text = " ", texthl = "DiagnosticSignError"})
|
||||
vim.fn.sign_define("DiagnosticSignWarn",
|
||||
{text = " ", texthl = "DiagnosticSignWarn"})
|
||||
vim.fn.sign_define("DiagnosticSignInfo",
|
||||
{text = " ", texthl = "DiagnosticSignInfo"})
|
||||
vim.fn.sign_define("DiagnosticSignHint",
|
||||
{text = "", texthl = "DiagnosticSignHint"})
|
||||
-- NOTE: this is changed from v1.x, which used the old style of highlight groups
|
||||
-- in the form "LspDiagnosticsSignWarning"
|
||||
|
||||
require("neo-tree").setup({
|
||||
close_if_last_window = false, -- Close Neo-tree if it is the last window left in the tab
|
||||
popup_border_style = "rounded",
|
||||
enable_git_status = true,
|
||||
enable_diagnostics = true,
|
||||
open_files_do_not_replace_types = { "terminal", "trouble", "qf" }, -- when opening files, do not use windows containing these filetypes or buftypes
|
||||
sort_case_insensitive = false, -- used when sorting files and directories in the tree
|
||||
sort_function = nil , -- use a custom function for sorting files and directories in the tree
|
||||
-- sort_function = function (a,b)
|
||||
-- if a.type == b.type then
|
||||
-- return a.path > b.path
|
||||
-- else
|
||||
-- return a.type > b.type
|
||||
-- end
|
||||
-- end , -- this sorts files and directories descendantly
|
||||
default_component_configs = {
|
||||
container = {
|
||||
enable_character_fade = true
|
||||
},
|
||||
indent = {
|
||||
indent_size = 2,
|
||||
padding = 1, -- extra padding on left hand side
|
||||
-- indent guides
|
||||
with_markers = true,
|
||||
indent_marker = "│",
|
||||
last_indent_marker = "└",
|
||||
highlight = "NeoTreeIndentMarker",
|
||||
-- expander config, needed for nesting files
|
||||
with_expanders = nil, -- if nil and file nesting is enabled, will enable expanders
|
||||
expander_collapsed = "",
|
||||
expander_expanded = "",
|
||||
expander_highlight = "NeoTreeExpander",
|
||||
},
|
||||
icon = {
|
||||
folder_closed = "",
|
||||
folder_open = "",
|
||||
folder_empty = "ﰊ",
|
||||
-- The next two settings are only a fallback, if you use nvim-web-devicons and configure default icons there
|
||||
-- then these will never be used.
|
||||
default = "*",
|
||||
highlight = "NeoTreeFileIcon"
|
||||
},
|
||||
modified = {
|
||||
symbol = "[+]",
|
||||
highlight = "NeoTreeModified",
|
||||
},
|
||||
name = {
|
||||
trailing_slash = false,
|
||||
use_git_status_colors = true,
|
||||
highlight = "NeoTreeFileName",
|
||||
},
|
||||
git_status = {
|
||||
symbols = {
|
||||
-- Change type
|
||||
added = "", -- or "✚", but this is redundant info if you use git_status_colors on the name
|
||||
modified = "", -- or "", but this is redundant info if you use git_status_colors on the name
|
||||
deleted = "✖",-- this can only be used in the git_status source
|
||||
renamed = "",-- this can only be used in the git_status source
|
||||
-- Status type
|
||||
untracked = "",
|
||||
ignored = "",
|
||||
unstaged = "",
|
||||
staged = "",
|
||||
conflict = "",
|
||||
}
|
||||
},
|
||||
},
|
||||
-- A list of functions, each representing a global custom command
|
||||
-- that will be available in all sources (if not overridden in `opts[source_name].commands`)
|
||||
-- see `:h neo-tree-global-custom-commands`
|
||||
commands = {},
|
||||
window = {
|
||||
position = "left",
|
||||
width = 40,
|
||||
mapping_options = {
|
||||
noremap = true,
|
||||
nowait = true,
|
||||
},
|
||||
mappings = {
|
||||
["<space>"] = {
|
||||
"toggle_node",
|
||||
nowait = false, -- disable `nowait` if you have existing combos starting with this char that you want to use
|
||||
},
|
||||
["<2-LeftMouse>"] = "open",
|
||||
["<cr>"] = "open",
|
||||
["<esc>"] = "revert_preview",
|
||||
["P"] = { "toggle_preview", config = { use_float = true } },
|
||||
["l"] = "focus_preview",
|
||||
["S"] = "open_split",
|
||||
["s"] = "open_vsplit",
|
||||
-- ["S"] = "split_with_window_picker",
|
||||
-- ["s"] = "vsplit_with_window_picker",
|
||||
["t"] = "open_tabnew",
|
||||
-- ["<cr>"] = "open_drop",
|
||||
-- ["t"] = "open_tab_drop",
|
||||
["w"] = "open_with_window_picker",
|
||||
--["P"] = "toggle_preview", -- enter preview mode, which shows the current node without focusing
|
||||
["C"] = "close_node",
|
||||
-- ['C'] = 'close_all_subnodes',
|
||||
["z"] = "close_all_nodes",
|
||||
--["Z"] = "expand_all_nodes",
|
||||
["a"] = {
|
||||
"add",
|
||||
-- this command supports BASH style brace expansion ("x{a,b,c}" -> xa,xb,xc). see `:h neo-tree-file-actions` for details
|
||||
-- some commands may take optional config options, see `:h neo-tree-mappings` for details
|
||||
config = {
|
||||
show_path = "none" -- "none", "relative", "absolute"
|
||||
}
|
||||
},
|
||||
["A"] = "add_directory", -- also accepts the optional config.show_path option like "add". this also supports BASH style brace expansion.
|
||||
["d"] = "delete",
|
||||
["r"] = "rename",
|
||||
["y"] = "copy_to_clipboard",
|
||||
["x"] = "cut_to_clipboard",
|
||||
["p"] = "paste_from_clipboard",
|
||||
["c"] = "copy", -- takes text input for destination, also accepts the optional config.show_path option like "add":
|
||||
-- ["c"] = {
|
||||
-- "copy",
|
||||
-- config = {
|
||||
-- show_path = "none" -- "none", "relative", "absolute"
|
||||
-- }
|
||||
--}
|
||||
["m"] = "move", -- takes text input for destination, also accepts the optional config.show_path option like "add".
|
||||
["q"] = "close_window",
|
||||
["R"] = "refresh",
|
||||
["?"] = "show_help",
|
||||
["<"] = "prev_source",
|
||||
[">"] = "next_source",
|
||||
}
|
||||
},
|
||||
nesting_rules = {},
|
||||
filesystem = {
|
||||
filtered_items = {
|
||||
visible = false, -- when true, they will just be displayed differently than normal items
|
||||
hide_dotfiles = true,
|
||||
hide_gitignored = true,
|
||||
hide_hidden = true, -- only works on Windows for hidden files/directories
|
||||
hide_by_name = {
|
||||
--"node_modules"
|
||||
},
|
||||
hide_by_pattern = { -- uses glob style patterns
|
||||
--"*.meta",
|
||||
--"*/src/*/tsconfig.json",
|
||||
},
|
||||
always_show = { -- remains visible even if other settings would normally hide it
|
||||
--".gitignored",
|
||||
},
|
||||
never_show = { -- remains hidden even if visible is toggled to true, this overrides always_show
|
||||
--".DS_Store",
|
||||
--"thumbs.db"
|
||||
},
|
||||
never_show_by_pattern = { -- uses glob style patterns
|
||||
--".null-ls_*",
|
||||
},
|
||||
},
|
||||
follow_current_file = false, -- This will find and focus the file in the active buffer every
|
||||
-- time the current file is changed while the tree is open.
|
||||
group_empty_dirs = false, -- when true, empty folders will be grouped together
|
||||
hijack_netrw_behavior = "open_default", -- netrw disabled, opening a directory opens neo-tree
|
||||
-- in whatever position is specified in window.position
|
||||
-- "open_current", -- netrw disabled, opening a directory opens within the
|
||||
-- window like netrw would, regardless of window.position
|
||||
-- "disabled", -- netrw left alone, neo-tree does not handle opening dirs
|
||||
use_libuv_file_watcher = false, -- This will use the OS level file watchers to detect changes
|
||||
-- instead of relying on nvim autocmd events.
|
||||
window = {
|
||||
mappings = {
|
||||
["<bs>"] = "navigate_up",
|
||||
["."] = "set_root",
|
||||
["H"] = "toggle_hidden",
|
||||
["/"] = "fuzzy_finder",
|
||||
["D"] = "fuzzy_finder_directory",
|
||||
["#"] = "fuzzy_sorter", -- fuzzy sorting using the fzy algorithm
|
||||
-- ["D"] = "fuzzy_sorter_directory",
|
||||
["f"] = "filter_on_submit",
|
||||
["<c-x>"] = "clear_filter",
|
||||
["[g"] = "prev_git_modified",
|
||||
["]g"] = "next_git_modified",
|
||||
},
|
||||
fuzzy_finder_mappings = { -- define keymaps for filter popup window in fuzzy_finder_mode
|
||||
["<down>"] = "move_cursor_down",
|
||||
["<C-n>"] = "move_cursor_down",
|
||||
["<up>"] = "move_cursor_up",
|
||||
["<C-p>"] = "move_cursor_up",
|
||||
},
|
||||
},
|
||||
|
||||
commands = {} -- Add a custom command or override a global one using the same function name
|
||||
},
|
||||
buffers = {
|
||||
follow_current_file = true, -- This will find and focus the file in the active buffer every
|
||||
-- time the current file is changed while the tree is open.
|
||||
group_empty_dirs = true, -- when true, empty folders will be grouped together
|
||||
show_unloaded = true,
|
||||
window = {
|
||||
mappings = {
|
||||
["bd"] = "buffer_delete",
|
||||
["<bs>"] = "navigate_up",
|
||||
["."] = "set_root",
|
||||
}
|
||||
},
|
||||
},
|
||||
git_status = {
|
||||
window = {
|
||||
position = "float",
|
||||
mappings = {
|
||||
["A"] = "git_add_all",
|
||||
["gu"] = "git_unstage_file",
|
||||
["ga"] = "git_add_file",
|
||||
["gr"] = "git_revert_file",
|
||||
["gc"] = "git_commit",
|
||||
["gp"] = "git_push",
|
||||
["gg"] = "git_commit_and_push",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
vim.cmd([[nnoremap \ :Neotree reveal<cr>]])
|
||||
end
|
||||
}
|
||||
```
|
||||
|
||||
_The above configuration is not everything that can be changed, it's just the
|
||||
parts you might want to change first._
|
||||
|
||||
|
||||
See `:h neo-tree` for full documentation. You can also preview that online at
|
||||
[doc/neo-tree.txt](doc/neo-tree.txt), although it's best viewed within vim.
|
||||
|
||||
|
||||
To see all of the default config options with commentary, you can view it online
|
||||
at [lua/neo-tree/defaults.lua](lua/neo-tree/defaults.lua). You can also paste it
|
||||
into a buffer after installing Neo-tree by running:
|
||||
|
||||
```
|
||||
:lua require("neo-tree").paste_default_config()
|
||||
```
|
||||
|
||||
#### Configuration for Nerd Fonts v3 Users
|
||||
|
||||
The following configuration should fix broken icons if you are using Nerd Fonts v3:
|
||||
|
||||
```lua
|
||||
require("neo-tree").setup({
|
||||
default_component_configs = {
|
||||
icon = {
|
||||
folder_empty = "",
|
||||
folder_empty_open = "",
|
||||
},
|
||||
git_status = {
|
||||
symbols = {
|
||||
renamed = "",
|
||||
unstaged = "",
|
||||
},
|
||||
},
|
||||
},
|
||||
document_symbols = {
|
||||
kinds = {
|
||||
File = { icon = "", hl = "Tag" },
|
||||
Namespace = { icon = "", hl = "Include" },
|
||||
Package = { icon = "", hl = "Label" },
|
||||
Class = { icon = "", hl = "Include" },
|
||||
Property = { icon = "", hl = "@property" },
|
||||
Enum = { icon = "", hl = "@number" },
|
||||
Function = { icon = "", hl = "Function" },
|
||||
String = { icon = "", hl = "String" },
|
||||
Number = { icon = "", hl = "Number" },
|
||||
Array = { icon = "", hl = "Type" },
|
||||
Object = { icon = "", hl = "Type" },
|
||||
Key = { icon = "", hl = "" },
|
||||
Struct = { icon = "", hl = "Type" },
|
||||
Operator = { icon = "", hl = "Operator" },
|
||||
TypeParameter = { icon = "", hl = "Type" },
|
||||
StaticMethod = { icon = ' ', hl = 'Function' },
|
||||
}
|
||||
},
|
||||
-- Add this section only if you've configured source selector.
|
||||
source_selector = {
|
||||
sources = {
|
||||
{ source = "filesystem", display_name = " Files " },
|
||||
{ source = "git_status", display_name = " Git " },
|
||||
},
|
||||
},
|
||||
-- Other options ...
|
||||
})
|
||||
```
|
||||
|
||||
## The `:Neotree` Command
|
||||
|
||||
The single `:Neotree` command accepts a range of arguments that give you full
|
||||
control over the details of what and where it will show. For example, the following
|
||||
command will open a file browser on the right hand side, "revealing" the currently
|
||||
active file:
|
||||
|
||||
```
|
||||
:Neotree filesystem reveal right
|
||||
```
|
||||
|
||||
Arguments can be specified as either a key=value pair or just as the value. The
|
||||
key=value form is more verbose but may help with clarity. For example, the command
|
||||
above can also be specified as:
|
||||
|
||||
```
|
||||
:Neotree source=filesystem reveal=true position=right
|
||||
```
|
||||
|
||||
All arguments are optional and can be specified in any order. If you issue the command
|
||||
without any arguments, it will use default values for everything. For example:
|
||||
|
||||
```
|
||||
:Neotree
|
||||
```
|
||||
|
||||
will open the filesystem source on the left hand side and focus it, if you are using
|
||||
the default config.
|
||||
|
||||
### Tab Completion
|
||||
|
||||
Neotree supports tab completion for all arguments. Once a given argument has a value,
|
||||
it will stop suggesting those completions. It will also offer completions for paths.
|
||||
The simplest way to disambiguate a path from another type of argument is to start
|
||||
them with `/` or `./`.
|
||||
|
||||
### Arguments
|
||||
|
||||
Here is the full list of arguments you can use:
|
||||
|
||||
#### `action`
|
||||
What to do. Can be one of:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| focus | Show and/or switch focus to the specified Neotree window. DEFAULT |
|
||||
| show | Show the window, but keep focus on your current window. |
|
||||
| close | Close the window(s) specified. Can be combined with "position" and/or "source" to specify which window(s) to close. |
|
||||
|
||||
#### `source`
|
||||
What to show. Can be one of:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| filesystem | Show a file browser. DEFAULT |
|
||||
| buffers | Show a list of currently open buffers. |
|
||||
| git_status | Show the output of `git status` in a tree layout. |
|
||||
|
||||
#### `position`
|
||||
Where to show it, can be one of:
|
||||
|
||||
| Option | Description |
|
||||
|---------|-------------|
|
||||
| left | Open as left hand sidebar. DEFAULT |
|
||||
| right | Open as right hand sidebar. |
|
||||
| top | Open as top window. |
|
||||
| bottom | Open as bottom window. |
|
||||
| float | Open as floating window. |
|
||||
| current | Open within the current window, like netrw or vinegar would. |
|
||||
|
||||
#### `toggle`
|
||||
This is a boolean flag. Adding this means that the window will be closed if it
|
||||
is already open.
|
||||
|
||||
#### `dir`
|
||||
The directory to set as the root/cwd of the specified window. If you include a
|
||||
directory as one of the arguments, it will be assumed to be this option, you
|
||||
don't need the full dir=/path. You may use any value that can be passed to the
|
||||
'expand' function, such as `%:p:h:h` to specify two directories up from the
|
||||
current file. For example:
|
||||
|
||||
```
|
||||
:Neotree ./relative/path
|
||||
:Neotree /home/user/relative/path
|
||||
:Neotree dir=/home/user/relative/path
|
||||
:Neotree position=current dir=relative/path
|
||||
```
|
||||
|
||||
#### `git_base`
|
||||
The base that is used to calculate the git status for each dir/file.
|
||||
By default it uses `HEAD`, so it shows all changes that are not yet committed.
|
||||
You can for example work on a feature branch, and set it to `main`. It will
|
||||
show all changes that happened on the feature branch and main since you
|
||||
branched off.
|
||||
|
||||
Any git ref, commit, tag, or sha will work.
|
||||
|
||||
```
|
||||
:Neotree main
|
||||
:Neotree v1.0
|
||||
:Neotree git_base=8fe34be
|
||||
:Neotree git_base=HEAD
|
||||
```
|
||||
|
||||
#### `reveal`
|
||||
This is a boolean flag. Adding this will make Neotree automatically find and
|
||||
focus the current file when it opens.
|
||||
|
||||
#### `reveal_file`
|
||||
A path to a file to reveal. This supersedes the "reveal" flag so there is no
|
||||
need to specify both. Use this if you want to reveal something other than the
|
||||
current file. If you include a path to a file as one of the arguments, it will
|
||||
be assumed to be this option. Like "dir", you can pass any value that can be
|
||||
passed to the 'expand' function. For example:
|
||||
|
||||
```
|
||||
:Neotree reveal_file=/home/user/my/file.text
|
||||
:Neotree position=current dir=%:p:h:h reveal_file=%:p
|
||||
:Neotree current %:p:h:h %:p
|
||||
```
|
||||
|
||||
One neat trick you can do with this is to open a Neotree window which is
|
||||
focused on the file under the cursor using the `<cfile>` keyword:
|
||||
|
||||
```
|
||||
nnoremap gd :Neotree float reveal_file=<cfile> reveal_force_cwd<cr>
|
||||
```
|
||||
|
||||
#### `reveal_force_cwd`
|
||||
This is a boolean flag. Normally, if you use one of the reveal options and the
|
||||
given file is not within the current working directory, you will be asked if you
|
||||
want to change the current working directory. If you include this flag, it will
|
||||
automatically change the directory without prompting. This option implies
|
||||
"reveal", so you do not need to specify both.
|
||||
|
||||
See `:h neo-tree-commands` for details and a full listing of available arguments.
|
||||
|
||||
### File Nesting
|
||||
|
||||
See `:h neo-tree-file-nesting` for more details about file nesting.
|
||||
|
||||
|
||||
### Netrw Hijack
|
||||
|
||||
```
|
||||
:edit .
|
||||
:[v]split .
|
||||
```
|
||||
|
||||
If `"filesystem.window.position"` is set to `"current"`, or if you have specified
|
||||
`filesystem.hijack_netrw_behavior = "open_current"`, then any command
|
||||
that would open a directory will open neo-tree in the specified window.
|
||||
|
||||
|
||||
## Sources
|
||||
|
||||
Neo-tree is built on the idea of supporting various sources. Sources are
|
||||
basically interface implementations whose job it is to provide a list of
|
||||
hierarchical items to be rendered, along with commands that are appropriate to
|
||||
those items.
|
||||
|
||||
### filesystem
|
||||
The default source is `filesystem`, which displays your files and folders. This
|
||||
is the default source in commands when none is specified.
|
||||
|
||||
This source can be used to:
|
||||
- Browse the filesystem
|
||||
- Control the current working directory of nvim
|
||||
- Add/Copy/Delete/Move/Rename files and directories
|
||||
- Search the filesystem
|
||||
- Monitor git status and lsp diagnostics for the current working directory
|
||||
|
||||
### buffers
|
||||

|
||||
|
||||
Another available source is `buffers`, which displays your open buffers. This is
|
||||
the same list you would see from `:ls`. To show with the `buffers` list, use:
|
||||
|
||||
```
|
||||
:Neotree buffers
|
||||
```
|
||||
|
||||
### git_status
|
||||
This view take the results of the `git status` command and display them in a
|
||||
tree. It includes commands for adding, unstaging, reverting, and committing.
|
||||
|
||||
The screenshot below shows the result of `:Neotree float git_status` while the
|
||||
filesystem is open in a sidebar:
|
||||
|
||||

|
||||
|
||||
You can specify a different git base here as well. But be aware that it is not
|
||||
possible to unstage / revert a file that is already committed.
|
||||
|
||||
```
|
||||
:Neotree float git_status git_base=main
|
||||
```
|
||||
|
||||
### document_symbols
|
||||
|
||||

|
||||
The document_symbols source lists the symbols in the current document obtained
|
||||
by the LSP request "textDocument/documentSymbols". It currently supports the
|
||||
following features:
|
||||
- [x] UI:
|
||||
- [x] Display all symbols in the current file with symbol kinds
|
||||
- [x] Symbols nesting
|
||||
- [x] Configurable kinds' name and icon
|
||||
- [x] Auto-refresh symbol list
|
||||
- [x] Follow cursor
|
||||
- [ ] Commands
|
||||
- [x] Jump to symbols, open symbol in split,... (`open_split` and friends)
|
||||
- [x] Rename symbols (`rename`)
|
||||
- [x] Preview symbol (`preview` and friends)
|
||||
- [ ] Hover docs
|
||||
- [ ] Call hierarchy
|
||||
- [x] LSP
|
||||
- [x] LSP Support
|
||||
- [x] LSP server selection (ignore, allow_only, use first, use all, etc.)
|
||||
- [ ] CoC Support
|
||||
|
||||
See #879 for the tracking issue of these features.
|
||||
|
||||
This source is currently experimental, so in order to use it, you need to first
|
||||
add `"document_symbols"` to `config.sources` and open it with the command
|
||||
```
|
||||
:Neotree document_symbols
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Source Selector
|
||||

|
||||
|
||||
You can enable a clickable source selector in either the winbar (requires neovim 0.8+) or the statusline.
|
||||
To do so, set one of these options to `true`:
|
||||
|
||||
```lua
|
||||
require("neo-tree").setup({
|
||||
source_selector = {
|
||||
winbar = false,
|
||||
statusline = false
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
There are many configuration options to change the style of these tabs.
|
||||
See [lua/neo-tree/defaults.lua](lua/neo-tree/defaults.lua) for details.
|
||||
|
||||
|
||||
## Configuration and Customization
|
||||
|
||||
This is designed to be flexible. The way that is achieved is by making
|
||||
everything a function, or a string that identifies a built-in function. All of the
|
||||
built-in functions can be replaced with your own implementation, or you can
|
||||
add new ones.
|
||||
|
||||
Each node in the tree is created from the renderer specified for the given node
|
||||
type, and each renderer is a list of component configs to be rendered in order.
|
||||
Each component is a function, either built-in or specified in your config. Those
|
||||
functions simply return the text and highlight group for the component.
|
||||
|
||||
Additionally, there is an events system that you can hook into. If you want to
|
||||
show some new data point related to your files, gather it in the
|
||||
`before_render` event, create a component to display it, and reference that
|
||||
component in the renderer for the `file` and/or `directory` type.
|
||||
|
||||
Details on how to configure everything is in the help file at `:h
|
||||
neo-tree-configuration` or online at
|
||||
[neo-tree.txt](https://github.com/nvim-neo-tree/neo-tree.nvim/blob/main/doc/neo-tree.txt)
|
||||
|
||||
Recipes for customizations can be found on the [wiki](https://github.com/nvim-neo-tree/neo-tree.nvim/wiki/Recipes). Recipes include
|
||||
things like adding a component to show the
|
||||
[Harpoon](https://github.com/ThePrimeagen/harpoon) index for files, or
|
||||
responding to the `"file_opened"` event to auto clear the search when you open a
|
||||
file.
|
||||
|
||||
|
||||
## Why?
|
||||
|
||||
There are many tree plugins for (neo)vim, so why make another one? Well, I
|
||||
wanted something that was:
|
||||
|
||||
1. Easy to maintain and enhance.
|
||||
2. Stable.
|
||||
3. Easy to customize.
|
||||
|
||||
### Easy to maintain and enhance
|
||||
|
||||
This plugin is designed to grow and be flexible. This is accomplished by making
|
||||
the code as decoupled and functional as possible. Hopefully new contributors
|
||||
will find it easy to work with.
|
||||
|
||||
One big difference between this plugin and the ones that came before it, which
|
||||
is also what finally pushed me over the edge into making a new plugin, is that
|
||||
we now have libraries to build upon that did not exist when other tree plugins
|
||||
were created. Most notably, [nui.nvim](https://github.com/MunifTanjim/nui.nvim)
|
||||
and [plenary.nvm](https://github.com/nvim-lua/plenary.nvim). Building upon
|
||||
shared libraries will go a long way in making neo-tree easy to maintain.
|
||||
|
||||
### Stable
|
||||
|
||||
This project will have releases and release tags that follow a simplified
|
||||
Semantic Versioning scheme. The quickstart instructions will always refer to
|
||||
the latest stable major version. Following the **main** branch is for
|
||||
contributors and those that always want bleeding edge. There will be branches
|
||||
for **v1.x**, **v2.x**, etc which will receive updates after a short testing
|
||||
period in **main**. You should be safe to follow those branches and be sure
|
||||
your tree won't break in an update. There will also be tags for each release
|
||||
pushed to those branches named **v1.1**, **v1.2**, etc. If stability is
|
||||
critical to you, or a bug accidentally make it into **v1.x**, you can use those
|
||||
tags instead. It's possible we may backport bug fixes to those tags, but no
|
||||
garauntees on that.
|
||||
|
||||
There will never be a breaking change within a major version (1.x, 2.x, etc.) If
|
||||
a breaking change is needed, there will be depracation warnings in the prior
|
||||
major version, and the breaking change will happen in the next major version.
|
||||
|
||||
### Easy to Customize
|
||||
|
||||
Neo-tree follows in the spirit of plugins like
|
||||
[lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) and
|
||||
[nvim-cokeline](https://github.com/noib3/nvim-cokeline). Everything will be
|
||||
configurable and take either strings, tables, or functions. You can take sane
|
||||
defaults or build your tree items from scratch. There should be the ability to
|
||||
add any features you can think of through existing hooks in the setup function.
|
||||
|
||||
## What is a Breaking Change?
|
||||
|
||||
As of v1.30, a breaking change is defined as anything that _changes_ existing:
|
||||
|
||||
- vim commands (`:NeoTreeShow`, `:NeoTreeReveal`, etc)
|
||||
- configuration options that are passed into the `setup()` function
|
||||
- `NeoTree*` highlight groups
|
||||
- lua functions exported in the following modules that are not prefixed with `_`:
|
||||
* `neo-tree`
|
||||
* `neo-tree.events`
|
||||
* `neo-tree.sources.manager`
|
||||
* `neo-tree.sources.*` (init.lua files)
|
||||
* `neo-tree.sources.*.commands`
|
||||
* `neo-tree.ui.renderer`
|
||||
* `neo-tree.utils`
|
||||
|
||||
If there are other functions you would like to use that are not yet considered
|
||||
part of the public API, please open an issue so we can discuss it.
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are encouraged. Please see [CONTRIBUTING](CONTRIBUTING.md) for more details.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This project relies upon these two excellent libraries:
|
||||
- [nui.nvim](https://github.com/MunifTanjim/nui.nvim) for all UI components, including the tree!
|
||||
- [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) for backend utilities, such as scanning the filesystem.
|
||||
|
||||
The design is heavily inspired by these excellent plugins:
|
||||
- [lualine.nvim](https://github.com/nvim-lualine/lualine.nvim)
|
||||
- [nvim-cokeline](https://github.com/noib3/nvim-cokeline)
|
||||
|
||||
Everything I know about writing a tree control in lua, I learned from:
|
||||
- [nvim-tree.lua](https://github.com/nvim-tree/nvim-tree.lua)
|
1747
bundle/neo-tree.nvim/doc/neo-tree.txt
vendored
Normal file
1747
bundle/neo-tree.nvim/doc/neo-tree.txt
vendored
Normal file
File diff suppressed because it is too large
Load Diff
257
bundle/neo-tree.nvim/lua/neo-tree.lua
vendored
Normal file
257
bundle/neo-tree.nvim/lua/neo-tree.lua
vendored
Normal file
@ -0,0 +1,257 @@
|
||||
local vim = vim
|
||||
local utils = require("neo-tree.utils")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local setup = require("neo-tree.setup")
|
||||
|
||||
-- If you add a new source, you need to add it to the sources table.
|
||||
-- Each source should have a defaults module that contains the default values
|
||||
-- for the source config, and a setup function that takes that config.
|
||||
local sources = {
|
||||
"filesystem",
|
||||
"buffers",
|
||||
"git_status",
|
||||
}
|
||||
|
||||
local M = {}
|
||||
|
||||
local check_source = function(source_name)
|
||||
if not utils.truthy(source_name) then
|
||||
source_name = M.config.default_source
|
||||
end
|
||||
local success, result = pcall(require, "neo-tree.sources." .. source_name)
|
||||
if not success then
|
||||
error("Source " .. source_name .. " could not be loaded: ", result)
|
||||
end
|
||||
return source_name
|
||||
end
|
||||
|
||||
local get_position = function(source_name)
|
||||
local pos = utils.get_value(M, "config." .. source_name .. ".window.position", "left", false)
|
||||
return pos
|
||||
end
|
||||
|
||||
M.ensure_config = function()
|
||||
if not M.config then
|
||||
M.setup({ log_to_file = false }, true)
|
||||
end
|
||||
end
|
||||
|
||||
--DEPRECATED in v2.x
|
||||
M.close_all_except = function(source_name)
|
||||
-- this entire function is faulty now that position can be overriden at runtime
|
||||
source_name = check_source(source_name)
|
||||
local target_pos = get_position(source_name)
|
||||
for _, name in ipairs(sources) do
|
||||
if name ~= source_name then
|
||||
local pos = utils.get_value(M, "config." .. name .. ".window.position", "left", false)
|
||||
if pos == target_pos then
|
||||
manager.close(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
renderer.close_all_floating_windows()
|
||||
end
|
||||
|
||||
--DEPRECATED in v2.x
|
||||
M.close = manager.close
|
||||
|
||||
--DEPRECATED in v2.x, use manager.close_all()
|
||||
M.close_all = function(at_position)
|
||||
renderer.close_all_floating_windows()
|
||||
if type(at_position) == "string" and at_position > "" then
|
||||
for _, name in ipairs(sources) do
|
||||
local pos = get_position(name)
|
||||
if pos == at_position then
|
||||
manager.close(name)
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, name in ipairs(sources) do
|
||||
manager.close(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--DEPRECATED in v2.x, use commands.execute()
|
||||
M.float = function(source_name, toggle_if_open)
|
||||
M.ensure_config()
|
||||
source_name = check_source(source_name)
|
||||
if toggle_if_open then
|
||||
if renderer.close_floating_window(source_name) then
|
||||
-- It was open, and now it's not.
|
||||
return
|
||||
end
|
||||
end
|
||||
renderer.close_all_floating_windows()
|
||||
manager.close(source_name) -- in case this source is open in a sidebar
|
||||
manager.float(source_name)
|
||||
end
|
||||
|
||||
--DEPRECATED in v2.x, use commands.execute()
|
||||
M.focus = function(source_name, close_others, toggle_if_open)
|
||||
M.ensure_config()
|
||||
source_name = check_source(source_name)
|
||||
if get_position(source_name) == "current" then
|
||||
M.show_in_split(source_name, toggle_if_open)
|
||||
return
|
||||
end
|
||||
|
||||
if toggle_if_open then
|
||||
if manager.close(source_name) then
|
||||
-- It was open, and now it's not.
|
||||
return
|
||||
end
|
||||
end
|
||||
if close_others == nil then
|
||||
close_others = true
|
||||
end
|
||||
if close_others then
|
||||
M.close_all_except(source_name)
|
||||
end
|
||||
manager.focus(source_name)
|
||||
end
|
||||
|
||||
--DEPRECATED in v2.x, use commands.execute()
|
||||
M.reveal_current_file = function(source_name, toggle_if_open, force_cwd)
|
||||
M.ensure_config()
|
||||
source_name = check_source(source_name)
|
||||
if get_position(source_name) == "current" then
|
||||
M.reveal_in_split(source_name, toggle_if_open)
|
||||
return
|
||||
end
|
||||
if toggle_if_open then
|
||||
if manager.close(source_name) then
|
||||
-- It was open, and now it's not.
|
||||
return
|
||||
end
|
||||
end
|
||||
manager.reveal_current_file(source_name, nil, force_cwd)
|
||||
end
|
||||
|
||||
--DEPRECATED in v2.x, use commands.execute()
|
||||
M.reveal_in_split = function(source_name, toggle_if_open)
|
||||
M.ensure_config()
|
||||
source_name = check_source(source_name)
|
||||
if toggle_if_open then
|
||||
local state = manager.get_state(source_name, nil, vim.api.nvim_get_current_win())
|
||||
if renderer.close(state) then
|
||||
-- It was open, and now it's not.
|
||||
return
|
||||
end
|
||||
end
|
||||
--TODO: if we are currently in a sidebar, don't replace it with a split style
|
||||
manager.reveal_in_split(source_name)
|
||||
end
|
||||
|
||||
--DEPRECATED in v2.x, use commands.execute()
|
||||
M.show_in_split = function(source_name, toggle_if_open)
|
||||
M.ensure_config()
|
||||
source_name = check_source(source_name)
|
||||
if toggle_if_open then
|
||||
local state = manager.get_state(source_name, nil, vim.api.nvim_get_current_win())
|
||||
if renderer.close(state) then
|
||||
-- It was open, and now it's not.
|
||||
return
|
||||
end
|
||||
end
|
||||
--TODO: if we are currently in a sidebar, don't replace it with a split style
|
||||
manager.show_in_split(source_name)
|
||||
end
|
||||
|
||||
M.get_prior_window = function(ignore_filetypes)
|
||||
ignore_filetypes = ignore_filetypes or {}
|
||||
local ignore = utils.list_to_dict(ignore_filetypes)
|
||||
ignore["neo-tree"] = true
|
||||
|
||||
local tabid = vim.api.nvim_get_current_tabpage()
|
||||
local wins = utils.get_value(M, "config.prior_windows", {}, true)[tabid]
|
||||
if wins == nil then
|
||||
return -1
|
||||
end
|
||||
local win_index = #wins
|
||||
while win_index > 0 do
|
||||
local last_win = wins[win_index]
|
||||
if type(last_win) == "number" then
|
||||
local success, is_valid = pcall(vim.api.nvim_win_is_valid, last_win)
|
||||
if success and is_valid then
|
||||
local buf = vim.api.nvim_win_get_buf(last_win)
|
||||
local ft = vim.api.nvim_buf_get_option(buf, "filetype")
|
||||
local bt = vim.api.nvim_buf_get_option(buf, "buftype") or "normal"
|
||||
if ignore[ft] ~= true and ignore[bt] ~= true then
|
||||
return last_win
|
||||
end
|
||||
end
|
||||
end
|
||||
win_index = win_index - 1
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
M.paste_default_config = function()
|
||||
local base_path = debug.getinfo(utils.truthy).source:match("@(.*)/utils.lua$")
|
||||
local config_path = base_path .. utils.path_separator .. "defaults.lua"
|
||||
local lines = vim.fn.readfile(config_path)
|
||||
if lines == nil then
|
||||
error("Could not read neo-tree.defaults")
|
||||
end
|
||||
|
||||
-- read up to the end of the config, jut to omit the final return
|
||||
local config = {}
|
||||
for _, line in ipairs(lines) do
|
||||
table.insert(config, line)
|
||||
if line == "}" then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_put(config, "l", true, false)
|
||||
vim.schedule(function()
|
||||
vim.cmd("normal! `[v`]=")
|
||||
end)
|
||||
end
|
||||
|
||||
M.buffer_enter_event = setup.buffer_enter_event
|
||||
M.win_enter_event = setup.win_enter_event
|
||||
|
||||
--DEPRECATED in v2.x
|
||||
--BREAKING CHANGE: Removed the do_not_focus and close_others options in 2.0
|
||||
--M.show = function(source_name, do_not_focus, close_others, toggle_if_open)
|
||||
M.show = function(source_name, toggle_if_open)
|
||||
M.ensure_config()
|
||||
source_name = check_source(source_name)
|
||||
if get_position(source_name) == "current" then
|
||||
M.show_in_split(source_name, toggle_if_open)
|
||||
return
|
||||
end
|
||||
|
||||
if toggle_if_open then
|
||||
if manager.close(source_name) then
|
||||
-- It was open, and now it's not.
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
M.close_all_except(source_name)
|
||||
manager.show(source_name)
|
||||
end
|
||||
|
||||
M.set_log_level = function(level)
|
||||
log.set_level(level)
|
||||
end
|
||||
|
||||
M.setup = function(config, is_auto_config)
|
||||
M.config = require("neo-tree.setup").merge_config(config, is_auto_config)
|
||||
local netrw = require("neo-tree.setup.netrw")
|
||||
if not is_auto_config and netrw.get_hijack_netrw_behavior() ~= "disabled" then
|
||||
vim.cmd("silent! autocmd! FileExplorer *")
|
||||
netrw.hijack()
|
||||
end
|
||||
end
|
||||
|
||||
M.show_logs = function()
|
||||
vim.cmd("tabnew " .. log.outfile)
|
||||
end
|
||||
|
||||
return M
|
127
bundle/neo-tree.nvim/lua/neo-tree/collections.lua
vendored
Normal file
127
bundle/neo-tree.nvim/lua/neo-tree/collections.lua
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
local log = require("neo-tree.log")
|
||||
|
||||
Node = {}
|
||||
function Node:new(value)
|
||||
local props = { prev = nil, next = nil, value = value }
|
||||
setmetatable(props, self)
|
||||
self.__index = self
|
||||
return props
|
||||
end
|
||||
|
||||
LinkedList = {}
|
||||
function LinkedList:new()
|
||||
local props = { head = nil, tail = nil, size = 0 }
|
||||
setmetatable(props, self)
|
||||
self.__index = self
|
||||
return props
|
||||
end
|
||||
|
||||
function LinkedList:add_node(node)
|
||||
if self.head == nil then
|
||||
self.head = node
|
||||
self.tail = node
|
||||
else
|
||||
self.tail.next = node
|
||||
node.prev = self.tail
|
||||
self.tail = node
|
||||
end
|
||||
self.size = self.size + 1
|
||||
return node
|
||||
end
|
||||
|
||||
function LinkedList:remove_node(node)
|
||||
if node.prev ~= nil then
|
||||
node.prev.next = node.next
|
||||
end
|
||||
if node.next ~= nil then
|
||||
node.next.prev = node.prev
|
||||
end
|
||||
if self.head == node then
|
||||
self.head = node.next
|
||||
end
|
||||
if self.tail == node then
|
||||
self.tail = node.prev
|
||||
end
|
||||
self.size = self.size - 1
|
||||
node.prev = nil
|
||||
node.next = nil
|
||||
node.value = nil
|
||||
end
|
||||
|
||||
-- First in Last Out
|
||||
Queue = {}
|
||||
function Queue:new()
|
||||
local props = { _list = LinkedList:new() }
|
||||
setmetatable(props, self)
|
||||
self.__index = self
|
||||
return props
|
||||
end
|
||||
|
||||
---Add an element to the end of the queue.
|
||||
---@param value any The value to add.
|
||||
function Queue:add(value)
|
||||
self._list:add_node(Node:new(value))
|
||||
end
|
||||
|
||||
---Iterates over the entire list, running func(value) on each element.
|
||||
---If func returns true, the element is removed from the list.
|
||||
---@param func function The function to run on each element.
|
||||
function Queue:for_each(func)
|
||||
local node = self._list.head
|
||||
while node ~= nil do
|
||||
local result = func(node.value)
|
||||
local node_is_next = false
|
||||
if result then
|
||||
if type(result) == "boolean" then
|
||||
local node_to_remove = node
|
||||
node = node.next
|
||||
node_is_next = true
|
||||
self._list:remove_node(node_to_remove)
|
||||
elseif type(result) == "table" then
|
||||
if type(result.handled) == "boolean" and result.handled == true then
|
||||
log.trace(
|
||||
"Handler ",
|
||||
node.value.id,
|
||||
" for "
|
||||
.. node.value.event
|
||||
.. " returned handled = true, skipping the rest of the queue."
|
||||
)
|
||||
return result
|
||||
end
|
||||
end
|
||||
end
|
||||
if not node_is_next then
|
||||
node = node.next
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Queue:is_empty()
|
||||
return self._list.size == 0
|
||||
end
|
||||
|
||||
function Queue:remove_by_id(id)
|
||||
local current = self._list.head
|
||||
while current ~= nil do
|
||||
local is_match = false
|
||||
local item = current.value
|
||||
if item ~= nil then
|
||||
local item_id = item.id or item
|
||||
if item_id == id then
|
||||
is_match = true
|
||||
end
|
||||
end
|
||||
if is_match then
|
||||
local next = current.next
|
||||
self._list:remove_node(current)
|
||||
current = next
|
||||
else
|
||||
current = current.next
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
Queue = Queue,
|
||||
LinkedList = LinkedList,
|
||||
}
|
122
bundle/neo-tree.nvim/lua/neo-tree/command/completion.lua
vendored
Normal file
122
bundle/neo-tree.nvim/lua/neo-tree/command/completion.lua
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
local parser = require("neo-tree.command.parser")
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {
|
||||
show_key_value_completions = true,
|
||||
}
|
||||
|
||||
local get_path_completions = function(key_prefix, base_path)
|
||||
key_prefix = key_prefix or ""
|
||||
local completions = {}
|
||||
local expanded = parser.resolve_path(base_path)
|
||||
local path_completions = vim.fn.glob(expanded .. "*", false, true)
|
||||
for _, completion in ipairs(path_completions) do
|
||||
if expanded ~= base_path then
|
||||
-- we need to recreate the relative path from the aboluste path
|
||||
-- first strip trailing slashes to normalize
|
||||
if expanded:sub(-1) == utils.path_separator then
|
||||
expanded = expanded:sub(1, -2)
|
||||
end
|
||||
if base_path:sub(-1) == utils.path_separator then
|
||||
base_path = base_path:sub(1, -2)
|
||||
end
|
||||
-- now put just the current completion onto the base_path being used
|
||||
completion = base_path .. string.sub(completion, #expanded + 1)
|
||||
end
|
||||
table.insert(completions, key_prefix .. completion)
|
||||
end
|
||||
|
||||
return table.concat(completions, "\n")
|
||||
end
|
||||
|
||||
local get_ref_completions = function(key_prefix)
|
||||
key_prefix = key_prefix or ""
|
||||
local completions = { key_prefix .. "HEAD" }
|
||||
local ok, refs = utils.execute_command("git show-ref")
|
||||
if not ok then
|
||||
return ""
|
||||
end
|
||||
for _, ref in ipairs(refs) do
|
||||
local _, i = ref:find("refs%/%a+%/")
|
||||
if i then
|
||||
table.insert(completions, key_prefix .. ref:sub(i + 1))
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(completions, "\n")
|
||||
end
|
||||
|
||||
M.complete_args = function(argLead, cmdLine)
|
||||
local candidates = {}
|
||||
local existing = utils.split(cmdLine, " ")
|
||||
local parsed = parser.parse(existing, false)
|
||||
|
||||
local eq = string.find(argLead, "=")
|
||||
if eq == nil then
|
||||
if M.show_key_value_completions then
|
||||
-- may be the start of a new key=value pair
|
||||
for _, key in ipairs(parser.list_args) do
|
||||
key = tostring(key)
|
||||
if key:find(argLead, 1, true) and not parsed[key] then
|
||||
table.insert(candidates, key .. "=")
|
||||
end
|
||||
end
|
||||
|
||||
for _, key in ipairs(parser.path_args) do
|
||||
key = tostring(key)
|
||||
if key:find(argLead, 1, true) and not parsed[key] then
|
||||
table.insert(candidates, key .. "=./")
|
||||
end
|
||||
end
|
||||
|
||||
for _, key in ipairs(parser.ref_args) do
|
||||
key = tostring(key)
|
||||
if key:find(argLead, 1, true) and not parsed[key] then
|
||||
table.insert(candidates, key .. "=")
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- continuation of a key=value pair
|
||||
local key = string.sub(argLead, 1, eq - 1)
|
||||
local value = string.sub(argLead, eq + 1)
|
||||
local arg_type = parser.arg_type_lookup[key]
|
||||
if arg_type == parser.PATH then
|
||||
return get_path_completions(key .. "=", value)
|
||||
elseif arg_type == parser.REF then
|
||||
return get_ref_completions(key .. "=")
|
||||
elseif arg_type == parser.LIST then
|
||||
local valid_values = parser.arguments[key].values
|
||||
if valid_values and not parsed[key] then
|
||||
for _, vv in ipairs(valid_values) do
|
||||
if vv:find(value) then
|
||||
table.insert(candidates, key .. "=" .. vv)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- may be a value without a key
|
||||
for value, key in pairs(parser.reverse_lookup) do
|
||||
value = tostring(value)
|
||||
local key_already_used = false
|
||||
if parser.arg_type_lookup[key] == parser.LIST then
|
||||
key_already_used = type(parsed[key]) ~= "nil"
|
||||
else
|
||||
key_already_used = type(parsed[value]) ~= "nil"
|
||||
end
|
||||
|
||||
if not key_already_used and value:find(argLead, 1, true) then
|
||||
table.insert(candidates, value)
|
||||
end
|
||||
end
|
||||
|
||||
if #candidates == 0 then
|
||||
-- default to path completion
|
||||
return get_path_completions(nil, argLead) .. "\n" .. get_ref_completions(nil)
|
||||
end
|
||||
return table.concat(candidates, "\n")
|
||||
end
|
||||
|
||||
return M
|
207
bundle/neo-tree.nvim/lua/neo-tree/command/init.lua
vendored
Normal file
207
bundle/neo-tree.nvim/lua/neo-tree/command/init.lua
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
local parser = require("neo-tree.command.parser")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local utils = require("neo-tree.utils")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local inputs = require("neo-tree.ui.inputs")
|
||||
local completion = require("neo-tree.command.completion")
|
||||
local do_show_or_focus, handle_reveal
|
||||
|
||||
local M = {
|
||||
complete_args = completion.complete_args,
|
||||
}
|
||||
|
||||
---Executes a Neo-tree action from outside of a Neo-tree window,
|
||||
---such as show, hide, navigate, etc.
|
||||
---@param args table The action to execute. The table can have the following keys:
|
||||
--- action = string The action to execute, can be one of:
|
||||
--- "close",
|
||||
--- "focus", <-- default value
|
||||
--- "show",
|
||||
--- source = string The source to use for this action. This will default
|
||||
--- to the default_source specified in the user's config.
|
||||
--- Can be one of:
|
||||
--- "filesystem",
|
||||
--- "buffers",
|
||||
--- "git_status",
|
||||
-- "migrations"
|
||||
--- position = string The position this action will affect. This will default
|
||||
--- to the the last used position or the position specified
|
||||
--- in the user's config for the given source. Can be one of:
|
||||
--- "left",
|
||||
--- "right",
|
||||
--- "float",
|
||||
--- "current"
|
||||
--- toggle = boolean Whether to toggle the visibility of the Neo-tree window.
|
||||
--- reveal = boolean Whether to reveal the current file in the Neo-tree window.
|
||||
--- reveal_file = string The specific file to reveal.
|
||||
--- dir = string The root directory to set.
|
||||
--- git_base = string The git base used for diff
|
||||
M.execute = function(args)
|
||||
local nt = require("neo-tree")
|
||||
nt.ensure_config()
|
||||
|
||||
if args.source == "migrations" then
|
||||
require("neo-tree.setup.deprecations").show_migrations()
|
||||
return
|
||||
end
|
||||
|
||||
args.action = args.action or "focus"
|
||||
|
||||
-- handle close action, which can specify a source and/or position
|
||||
if args.action == "close" then
|
||||
if args.source then
|
||||
manager.close(args.source, args.position)
|
||||
else
|
||||
manager.close_all(args.position)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- The rest of the actions require a source
|
||||
args.source = args.source or nt.config.default_source
|
||||
|
||||
-- If position=current was requested, but we are currently in a neo-tree window,
|
||||
-- then we need to override that.
|
||||
if args.position == "current" and vim.bo.filetype == "neo-tree" then
|
||||
local position = vim.api.nvim_buf_get_var(0, "neo_tree_position")
|
||||
if position then
|
||||
args.position = position
|
||||
end
|
||||
end
|
||||
|
||||
-- Now get the correct state
|
||||
local state
|
||||
local requested_position = args.position or nt.config[args.source].window.position
|
||||
if requested_position == "current" then
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
state = manager.get_state(args.source, nil, winid)
|
||||
else
|
||||
state = manager.get_state(args.source, nil, nil)
|
||||
end
|
||||
|
||||
-- Next handle toggle, the rest is irrelevant if there is a window to toggle
|
||||
if args.toggle then
|
||||
if renderer.close(state) then
|
||||
-- It was open, and now it's not.
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle position override
|
||||
local default_position = nt.config[args.source].window.position
|
||||
local current_position = state.current_position or default_position
|
||||
local position_changed = false
|
||||
if args.position then
|
||||
state.current_position = args.position
|
||||
position_changed = args.position ~= current_position
|
||||
end
|
||||
|
||||
-- Handle setting directory if requested
|
||||
local path_changed = false
|
||||
if utils.truthy(args.dir) then
|
||||
if #args.dir > 1 and args.dir:sub(-1) == utils.path_separator then
|
||||
args.dir = args.dir:sub(1, -2)
|
||||
end
|
||||
path_changed = state.path ~= args.dir
|
||||
else
|
||||
args.dir = state.path
|
||||
end
|
||||
|
||||
-- Handle setting git ref
|
||||
local git_base_changed = state.git_base ~= args.git_base
|
||||
if utils.truthy(args.git_base) then
|
||||
state.git_base = args.git_base
|
||||
end
|
||||
|
||||
-- Handle reveal logic
|
||||
args.reveal = args.reveal or args.reveal_force_cwd
|
||||
local do_reveal = utils.truthy(args.reveal_file)
|
||||
if args.reveal and not do_reveal then
|
||||
args.reveal_file = manager.get_path_to_reveal()
|
||||
do_reveal = utils.truthy(args.reveal_file)
|
||||
end
|
||||
|
||||
-- All set, now show or focus the window
|
||||
local force_navigate = path_changed or do_reveal or git_base_changed or state.dirty
|
||||
if position_changed and args.position ~= "current" and current_position ~= "current" then
|
||||
manager.close(args.source)
|
||||
end
|
||||
if do_reveal then
|
||||
handle_reveal(args, state)
|
||||
else
|
||||
do_show_or_focus(args, state, force_navigate)
|
||||
end
|
||||
end
|
||||
|
||||
---Parses and executes the command line. Use execute(args) instead.
|
||||
---@param ... string Argument as strings.
|
||||
M._command = function(...)
|
||||
local args = parser.parse({ ... }, true)
|
||||
M.execute(args)
|
||||
end
|
||||
|
||||
do_show_or_focus = function(args, state, force_navigate)
|
||||
local window_exists = renderer.window_exists(state)
|
||||
local function close_other_sources()
|
||||
if not window_exists then
|
||||
-- Clear the space in case another source is already open
|
||||
local target_position = args.position or state.current_position or state.window.position
|
||||
if target_position ~= "current" then
|
||||
manager.close_all(target_position)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if args.action == "show" then
|
||||
-- "show" means show the window without focusing it
|
||||
if window_exists and not force_navigate then
|
||||
-- There's nothing to do here, we are already at the target state
|
||||
return
|
||||
end
|
||||
close_other_sources()
|
||||
local current_win = vim.api.nvim_get_current_win()
|
||||
manager.navigate(state, args.dir, args.reveal_file, function()
|
||||
-- navigate changes the window to neo-tree, so just quickly hop back to the original window
|
||||
vim.api.nvim_set_current_win(current_win)
|
||||
end, false)
|
||||
elseif args.action == "focus" then
|
||||
-- "focus" mean open and jump to the window if closed, and just focus it if already opened
|
||||
if window_exists then
|
||||
vim.api.nvim_set_current_win(state.winid)
|
||||
end
|
||||
if force_navigate or not window_exists then
|
||||
close_other_sources()
|
||||
manager.navigate(state, args.dir, args.reveal_file, nil, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
handle_reveal = function(args, state)
|
||||
-- Deal with cwd if we need to
|
||||
local cwd = state.path
|
||||
local path = args.reveal_file
|
||||
if cwd == nil then
|
||||
cwd = manager.get_cwd(state)
|
||||
end
|
||||
if args.reveal_force_cwd and not utils.is_subpath(cwd, path) then
|
||||
args.dir, _ = utils.split_path(path)
|
||||
do_show_or_focus(args, state, true)
|
||||
return
|
||||
elseif not utils.is_subpath(cwd, path) then
|
||||
-- force was not specified, so we need to ask the user
|
||||
cwd, _ = utils.split_path(path)
|
||||
inputs.confirm("File not in cwd. Change cwd to " .. cwd .. "?", function(response)
|
||||
if response == true then
|
||||
args.dir = cwd
|
||||
else
|
||||
args.reveal_file = nil
|
||||
end
|
||||
do_show_or_focus(args, state, true)
|
||||
end)
|
||||
return
|
||||
else
|
||||
do_show_or_focus(args, state, true)
|
||||
end
|
||||
end
|
||||
return M
|
175
bundle/neo-tree.nvim/lua/neo-tree/command/parser.lua
vendored
Normal file
175
bundle/neo-tree.nvim/lua/neo-tree/command/parser.lua
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {
|
||||
FLAG = "<FLAG>",
|
||||
LIST = "<LIST>",
|
||||
PATH = "<PATH>",
|
||||
REF = "<REF>",
|
||||
}
|
||||
|
||||
M.setup = function(all_source_names)
|
||||
local source_names = utils.table_copy(all_source_names)
|
||||
table.insert(source_names, "migrations")
|
||||
|
||||
-- For lists, the first value is the default value.
|
||||
local arguments = {
|
||||
action = {
|
||||
type = M.LIST,
|
||||
values = {
|
||||
"close",
|
||||
"focus",
|
||||
"show",
|
||||
},
|
||||
},
|
||||
position = {
|
||||
type = M.LIST,
|
||||
values = {
|
||||
"left",
|
||||
"right",
|
||||
"top",
|
||||
"bottom",
|
||||
"float",
|
||||
"current",
|
||||
},
|
||||
},
|
||||
source = {
|
||||
type = M.LIST,
|
||||
values = source_names,
|
||||
},
|
||||
dir = { type = M.PATH, stat_type = "directory" },
|
||||
reveal_file = { type = M.PATH, stat_type = "file" },
|
||||
git_base = { type = M.REF },
|
||||
toggle = { type = M.FLAG },
|
||||
reveal = { type = M.FLAG },
|
||||
reveal_force_cwd = { type = M.FLAG },
|
||||
}
|
||||
|
||||
local arg_type_lookup = {}
|
||||
local list_args = {}
|
||||
local path_args = {}
|
||||
local ref_args = {}
|
||||
local flag_args = {}
|
||||
local reverse_lookup = {}
|
||||
for name, def in pairs(arguments) do
|
||||
arg_type_lookup[name] = def.type
|
||||
if def.type == M.LIST then
|
||||
table.insert(list_args, name)
|
||||
for _, vv in ipairs(def.values) do
|
||||
reverse_lookup[tostring(vv)] = name
|
||||
end
|
||||
elseif def.type == M.PATH then
|
||||
table.insert(path_args, name)
|
||||
elseif def.type == M.FLAG then
|
||||
table.insert(flag_args, name)
|
||||
reverse_lookup[name] = M.FLAG
|
||||
elseif def.type == M.REF then
|
||||
table.insert(ref_args, name)
|
||||
else
|
||||
error("Unknown type: " .. def.type)
|
||||
end
|
||||
end
|
||||
|
||||
M.arguments = arguments
|
||||
M.list_args = list_args
|
||||
M.path_args = path_args
|
||||
M.ref_args = ref_args
|
||||
M.flag_args = flag_args
|
||||
M.arg_type_lookup = arg_type_lookup
|
||||
M.reverse_lookup = reverse_lookup
|
||||
end
|
||||
|
||||
M.resolve_path = function(path, validate_type)
|
||||
local expanded = vim.fn.expand(path)
|
||||
local abs_path = vim.fn.fnamemodify(expanded, ":p")
|
||||
if validate_type then
|
||||
local stat = vim.loop.fs_stat(abs_path)
|
||||
if stat.type ~= validate_type then
|
||||
error("Invalid path: " .. path .. " is not a " .. validate_type)
|
||||
end
|
||||
end
|
||||
return abs_path
|
||||
end
|
||||
|
||||
M.verify_git_ref = function(ref)
|
||||
local ok, _ = utils.execute_command("git rev-parse --verify " .. ref)
|
||||
return ok
|
||||
end
|
||||
|
||||
local parse_arg = function(result, arg)
|
||||
if type(arg) == "string" then
|
||||
local eq = arg:find("=")
|
||||
if eq then
|
||||
local key = arg:sub(1, eq - 1)
|
||||
local value = arg:sub(eq + 1)
|
||||
local def = M.arguments[key]
|
||||
if not def.type then
|
||||
error("Invalid argument: " .. arg)
|
||||
end
|
||||
|
||||
if def.type == M.PATH then
|
||||
result[key] = M.resolve_path(value, def.stat_type)
|
||||
elseif def.type == M.FLAG then
|
||||
if value == "true" then
|
||||
result[key] = true
|
||||
elseif value == "false" then
|
||||
result[key] = false
|
||||
else
|
||||
error("Invalid value for " .. key .. ": " .. value)
|
||||
end
|
||||
elseif def.type == M.REF then
|
||||
if not M.verify_git_ref(value) then
|
||||
error("Invalid value for " .. key .. ": " .. value)
|
||||
end
|
||||
result[key] = value
|
||||
else
|
||||
result[key] = value
|
||||
end
|
||||
else
|
||||
local value = arg
|
||||
local key = M.reverse_lookup[value]
|
||||
if key == nil then
|
||||
-- maybe it's a git ref
|
||||
if M.verify_git_ref(value) then
|
||||
result["git_base"] = value
|
||||
return
|
||||
end
|
||||
-- maybe it's a path
|
||||
local path = M.resolve_path(value)
|
||||
local stat = vim.loop.fs_stat(path)
|
||||
if stat then
|
||||
if stat.type == "directory" then
|
||||
result["dir"] = path
|
||||
elseif stat.type == "file" then
|
||||
result["reveal_file"] = path
|
||||
end
|
||||
else
|
||||
error("Invalid argument: " .. arg)
|
||||
end
|
||||
elseif key == M.FLAG then
|
||||
result[value] = true
|
||||
else
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.parse = function(args, strict_checking)
|
||||
require("neo-tree").ensure_config()
|
||||
local result = {}
|
||||
|
||||
if type(args) == "string" then
|
||||
args = utils.split(args, " ")
|
||||
end
|
||||
-- read args from user
|
||||
for _, arg in ipairs(args) do
|
||||
local success, err = pcall(parse_arg, result, arg)
|
||||
if strict_checking and not success then
|
||||
error(err)
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return M
|
615
bundle/neo-tree.nvim/lua/neo-tree/defaults.lua
vendored
Normal file
615
bundle/neo-tree.nvim/lua/neo-tree/defaults.lua
vendored
Normal file
@ -0,0 +1,615 @@
|
||||
local config = {
|
||||
-- If a user has a sources list it will replace this one.
|
||||
-- Only sources listed here will be loaded.
|
||||
-- You can also add an external source by adding it's name to this list.
|
||||
-- The name used here must be the same name you would use in a require() call.
|
||||
sources = {
|
||||
"filesystem",
|
||||
"buffers",
|
||||
"git_status",
|
||||
-- "document_symbols",
|
||||
},
|
||||
add_blank_line_at_top = false, -- Add a blank line at the top of the tree.
|
||||
auto_clean_after_session_restore = false, -- Automatically clean up broken neo-tree buffers saved in sessions
|
||||
close_if_last_window = false, -- Close Neo-tree if it is the last window left in the tab
|
||||
-- popup_border_style is for input and confirmation dialogs.
|
||||
-- Configurtaion of floating window is done in the individual source sections.
|
||||
-- "NC" is a special style that works well with NormalNC set
|
||||
close_floats_on_escape_key = true,
|
||||
default_source = "filesystem",
|
||||
enable_diagnostics = true,
|
||||
enable_git_status = true,
|
||||
enable_modified_markers = true, -- Show markers for files with unsaved changes.
|
||||
enable_opened_markers = true, -- Enable tracking of opened files. Required for `components.name.highlight_opened_files`
|
||||
enable_refresh_on_write = true, -- Refresh the tree when a file is written. Only used if `use_libuv_file_watcher` is false.
|
||||
git_status_async = true,
|
||||
-- These options are for people with VERY large git repos
|
||||
git_status_async_options = {
|
||||
batch_size = 1000, -- how many lines of git status results to process at a time
|
||||
batch_delay = 10, -- delay in ms between batches. Spreads out the workload to let other processes run.
|
||||
max_lines = 10000, -- How many lines of git status results to process. Anything after this will be dropped.
|
||||
-- Anything before this will be used. The last items to be processed are the untracked files.
|
||||
},
|
||||
hide_root_node = false, -- Hide the root node.
|
||||
retain_hidden_root_indent = false, -- IF the root node is hidden, keep the indentation anyhow.
|
||||
-- This is needed if you use expanders because they render in the indent.
|
||||
log_level = "info", -- "trace", "debug", "info", "warn", "error", "fatal"
|
||||
log_to_file = false, -- true, false, "/path/to/file.log", use :NeoTreeLogs to show the file
|
||||
open_files_in_last_window = true, -- false = open files in top left window
|
||||
open_files_do_not_replace_types = { "terminal", "trouble", "qf" }, -- when opening files, do not use windows containing these filetypes or buftypes
|
||||
popup_border_style = "NC", -- "double", "none", "rounded", "shadow", "single" or "solid"
|
||||
resize_timer_interval = 500, -- in ms, needed for containers to redraw right aligned and faded content
|
||||
-- set to -1 to disable the resize timer entirely
|
||||
-- -- NOTE: this will speed up to 50 ms for 1 second following a resize
|
||||
sort_case_insensitive = false, -- used when sorting files and directories in the tree
|
||||
sort_function = nil , -- uses a custom function for sorting files and directories in the tree
|
||||
use_popups_for_input = true, -- If false, inputs will use vim.ui.input() instead of custom floats.
|
||||
use_default_mappings = true,
|
||||
-- source_selector provides clickable tabs to switch between sources.
|
||||
source_selector = {
|
||||
winbar = false, -- toggle to show selector on winbar
|
||||
statusline = false, -- toggle to show selector on statusline
|
||||
show_scrolled_off_parent_node = false, -- this will replace the tabs with the parent path
|
||||
-- of the top visible node when scrolled down.
|
||||
sources = {
|
||||
{ source = "filesystem" },
|
||||
{ source = "buffers" },
|
||||
{ source = "git_status" },
|
||||
},
|
||||
content_layout = "start", -- only with `tabs_layout` = "equal", "focus"
|
||||
-- start : |/ 裡 bufname \/...
|
||||
-- end : |/ 裡 bufname \/...
|
||||
-- center : |/ 裡 bufname \/...
|
||||
tabs_layout = "equal", -- start, end, center, equal, focus
|
||||
-- start : |/ a \/ b \/ c \ |
|
||||
-- end : | / a \/ b \/ c \|
|
||||
-- center : | / a \/ b \/ c \ |
|
||||
-- equal : |/ a \/ b \/ c \|
|
||||
-- active : |/ focused tab \/ b \/ c \|
|
||||
truncation_character = "…", -- character to use when truncating the tab label
|
||||
tabs_min_width = nil, -- nil | int: if int padding is added based on `content_layout`
|
||||
tabs_max_width = nil, -- this will truncate text even if `text_trunc_to_fit = false`
|
||||
padding = 0, -- can be int or table
|
||||
-- padding = { left = 2, right = 0 },
|
||||
-- separator = "▕", -- can be string or table, see below
|
||||
separator = { left = "▏", right= "▕" },
|
||||
-- separator = { left = "/", right = "\\", override = nil }, -- |/ a \/ b \/ c \...
|
||||
-- separator = { left = "/", right = "\\", override = "right" }, -- |/ a \ b \ c \...
|
||||
-- separator = { left = "/", right = "\\", override = "left" }, -- |/ a / b / c /...
|
||||
-- separator = { left = "/", right = "\\", override = "active" },-- |/ a / b:active \ c \...
|
||||
-- separator = "|", -- || a | b | c |...
|
||||
separator_active = nil, -- set separators around the active tab. nil falls back to `source_selector.separator`
|
||||
show_separator_on_edge = false,
|
||||
-- true : |/ a \/ b \/ c \|
|
||||
-- false : | a \/ b \/ c |
|
||||
highlight_tab = "NeoTreeTabInactive",
|
||||
highlight_tab_active = "NeoTreeTabActive",
|
||||
highlight_background = "NeoTreeTabInactive",
|
||||
highlight_separator = "NeoTreeTabSeparatorInactive",
|
||||
highlight_separator_active = "NeoTreeTabSeparatorActive",
|
||||
},
|
||||
--
|
||||
--event_handlers = {
|
||||
-- {
|
||||
-- event = "before_render",
|
||||
-- handler = function (state)
|
||||
-- -- add something to the state that can be used by custom components
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "file_opened",
|
||||
-- handler = function(file_path)
|
||||
-- --auto close
|
||||
-- require("neo-tree").close_all()
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "file_opened",
|
||||
-- handler = function(file_path)
|
||||
-- --clear search after opening a file
|
||||
-- require("neo-tree.sources.filesystem").reset_search()
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "file_renamed",
|
||||
-- handler = function(args)
|
||||
-- -- fix references to file
|
||||
-- print(args.source, " renamed to ", args.destination)
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "file_moved",
|
||||
-- handler = function(args)
|
||||
-- -- fix references to file
|
||||
-- print(args.source, " moved to ", args.destination)
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "neo_tree_buffer_enter",
|
||||
-- handler = function()
|
||||
-- vim.cmd 'highlight! Cursor blend=100'
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "neo_tree_buffer_leave",
|
||||
-- handler = function()
|
||||
-- vim.cmd 'highlight! Cursor guibg=#5f87af blend=0'
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "neo_tree_window_before_open",
|
||||
-- handler = function(args)
|
||||
-- print("neo_tree_window_before_open", vim.inspect(args))
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "neo_tree_window_after_open",
|
||||
-- handler = function(args)
|
||||
-- vim.cmd("wincmd =")
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "neo_tree_window_before_close",
|
||||
-- handler = function(args)
|
||||
-- print("neo_tree_window_before_close", vim.inspect(args))
|
||||
-- end
|
||||
-- },
|
||||
-- {
|
||||
-- event = "neo_tree_window_after_close",
|
||||
-- handler = function(args)
|
||||
-- vim.cmd("wincmd =")
|
||||
-- end
|
||||
-- }
|
||||
--},
|
||||
default_component_configs = {
|
||||
container = {
|
||||
enable_character_fade = true,
|
||||
width = "100%",
|
||||
right_padding = 0,
|
||||
},
|
||||
--diagnostics = {
|
||||
-- symbols = {
|
||||
-- hint = "H",
|
||||
-- info = "I",
|
||||
-- warn = "!",
|
||||
-- error = "X",
|
||||
-- },
|
||||
-- highlights = {
|
||||
-- hint = "DiagnosticSignHint",
|
||||
-- info = "DiagnosticSignInfo",
|
||||
-- warn = "DiagnosticSignWarn",
|
||||
-- error = "DiagnosticSignError",
|
||||
-- },
|
||||
--},
|
||||
indent = {
|
||||
indent_size = 2,
|
||||
padding = 1,
|
||||
-- indent guides
|
||||
with_markers = true,
|
||||
indent_marker = "│",
|
||||
last_indent_marker = "└",
|
||||
highlight = "NeoTreeIndentMarker",
|
||||
-- expander config, needed for nesting files
|
||||
with_expanders = nil, -- if nil and file nesting is enabled, will enable expanders
|
||||
expander_collapsed = "",
|
||||
expander_expanded = "",
|
||||
expander_highlight = "NeoTreeExpander",
|
||||
},
|
||||
icon = {
|
||||
folder_closed = "",
|
||||
folder_open = "",
|
||||
folder_empty = "ﰊ",
|
||||
folder_empty_open = "ﰊ",
|
||||
-- The next two settings are only a fallback, if you use nvim-web-devicons and configure default icons there
|
||||
-- then these will never be used.
|
||||
default = "*",
|
||||
highlight = "NeoTreeFileIcon"
|
||||
},
|
||||
modified = {
|
||||
symbol = "[+] ",
|
||||
highlight = "NeoTreeModified",
|
||||
},
|
||||
name = {
|
||||
trailing_slash = false,
|
||||
highlight_opened_files = false, -- Requires `enable_opened_markers = true`.
|
||||
-- Take values in { false (no highlight), true (only loaded),
|
||||
-- "all" (both loaded and unloaded)}. For more information,
|
||||
-- see the `show_unloaded` config of the `buffers` source.
|
||||
use_git_status_colors = true,
|
||||
highlight = "NeoTreeFileName",
|
||||
},
|
||||
git_status = {
|
||||
symbols = {
|
||||
-- Change type
|
||||
added = "✚", -- NOTE: you can set any of these to an empty string to not show them
|
||||
deleted = "✖",
|
||||
modified = "",
|
||||
renamed = "",
|
||||
-- Status type
|
||||
untracked = "",
|
||||
ignored = "",
|
||||
unstaged = "",
|
||||
staged = "",
|
||||
conflict = "",
|
||||
},
|
||||
align = "right",
|
||||
},
|
||||
},
|
||||
renderers = {
|
||||
directory = {
|
||||
{ "indent" },
|
||||
{ "icon" },
|
||||
{ "current_filter" },
|
||||
{
|
||||
"container",
|
||||
content = {
|
||||
{ "name", zindex = 10 },
|
||||
-- {
|
||||
-- "symlink_target",
|
||||
-- zindex = 10,
|
||||
-- highlight = "NeoTreeSymbolicLinkTarget",
|
||||
-- },
|
||||
{ "clipboard", zindex = 10 },
|
||||
{ "diagnostics", errors_only = true, zindex = 20, align = "right", hide_when_expanded = true },
|
||||
{ "git_status", zindex = 20, align = "right", hide_when_expanded = true },
|
||||
},
|
||||
},
|
||||
},
|
||||
file = {
|
||||
{ "indent" },
|
||||
{ "icon" },
|
||||
{
|
||||
"container",
|
||||
content = {
|
||||
{
|
||||
"name",
|
||||
zindex = 10
|
||||
},
|
||||
-- {
|
||||
-- "symlink_target",
|
||||
-- zindex = 10,
|
||||
-- highlight = "NeoTreeSymbolicLinkTarget",
|
||||
-- },
|
||||
{ "clipboard", zindex = 10 },
|
||||
{ "bufnr", zindex = 10 },
|
||||
{ "modified", zindex = 20, align = "right" },
|
||||
{ "diagnostics", zindex = 20, align = "right" },
|
||||
{ "git_status", zindex = 20, align = "right" },
|
||||
},
|
||||
},
|
||||
},
|
||||
message = {
|
||||
{ "indent", with_markers = false },
|
||||
{ "name", highlight = "NeoTreeMessage" },
|
||||
},
|
||||
terminal = {
|
||||
{ "indent" },
|
||||
{ "icon" },
|
||||
{ "name" },
|
||||
{ "bufnr" }
|
||||
}
|
||||
},
|
||||
nesting_rules = {},
|
||||
-- Global custom commands that will be available in all sources (if not overridden in `opts[source_name].commands`)
|
||||
--
|
||||
-- You can then reference the custom command by adding a mapping to it:
|
||||
-- globally -> `opts.window.mappings`
|
||||
-- locally -> `opt[source_name].window.mappings` to make it source specific.
|
||||
--
|
||||
-- commands = { | window { | filesystem {
|
||||
-- hello = function() | mappings = { | commands = {
|
||||
-- print("Hello world") | ["<C-c>"] = "hello" | hello = function()
|
||||
-- end | } | print("Hello world in filesystem")
|
||||
-- } | } | end
|
||||
--
|
||||
-- see `:h neo-tree-global-custom-commands`
|
||||
commands = {}, -- A list of functions
|
||||
|
||||
window = { -- see https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/popup for
|
||||
-- possible options. These can also be functions that return these options.
|
||||
position = "left", -- left, right, top, bottom, float, current
|
||||
width = 40, -- applies to left and right positions
|
||||
height = 15, -- applies to top and bottom positions
|
||||
auto_expand_width = false, -- expand the window when file exceeds the window width. does not work with position = "float"
|
||||
popup = { -- settings that apply to float position only
|
||||
size = {
|
||||
height = "80%",
|
||||
width = "50%",
|
||||
},
|
||||
position = "50%", -- 50% means center it
|
||||
-- you can also specify border here, if you want a different setting from
|
||||
-- the global popup_border_style.
|
||||
},
|
||||
same_level = false, -- Create and paste/move files/directories on the same level as the directory under cursor (as opposed to within the directory under cursor).
|
||||
insert_as = "child", -- Affects how nodes get inserted into the tree during creation/pasting/moving of files if the node under the cursor is a directory:
|
||||
-- "child": Insert nodes as children of the directory under cursor.
|
||||
-- "sibling": Insert nodes as siblings of the directory under cursor.
|
||||
-- Mappings for tree window. See `:h neo-tree-mappings` for a list of built-in commands.
|
||||
-- You can also create your own commands by providing a function instead of a string.
|
||||
mapping_options = {
|
||||
noremap = true,
|
||||
nowait = true,
|
||||
},
|
||||
mappings = {
|
||||
["<space>"] = {
|
||||
"toggle_node",
|
||||
nowait = false, -- disable `nowait` if you have existing combos starting with this char that you want to use
|
||||
},
|
||||
["<2-LeftMouse>"] = "open",
|
||||
["<cr>"] = "open",
|
||||
["<esc>"] = "revert_preview",
|
||||
["P"] = { "toggle_preview", config = { use_float = true } },
|
||||
["l"] = "focus_preview",
|
||||
["S"] = "open_split",
|
||||
-- ["S"] = "split_with_window_picker",
|
||||
["s"] = "open_vsplit",
|
||||
-- ["s"] = "vsplit_with_window_picker",
|
||||
["t"] = "open_tabnew",
|
||||
-- ["<cr>"] = "open_drop",
|
||||
-- ["t"] = "open_tab_drop",
|
||||
["w"] = "open_with_window_picker",
|
||||
["C"] = "close_node",
|
||||
["z"] = "close_all_nodes",
|
||||
--["Z"] = "expand_all_nodes",
|
||||
["R"] = "refresh",
|
||||
["a"] = {
|
||||
"add",
|
||||
-- some commands may take optional config options, see `:h neo-tree-mappings` for details
|
||||
config = {
|
||||
show_path = "none", -- "none", "relative", "absolute"
|
||||
}
|
||||
},
|
||||
["A"] = "add_directory", -- also accepts the config.show_path and config.insert_as options.
|
||||
["d"] = "delete",
|
||||
["r"] = "rename",
|
||||
["y"] = "copy_to_clipboard",
|
||||
["x"] = "cut_to_clipboard",
|
||||
["p"] = "paste_from_clipboard",
|
||||
["c"] = "copy", -- takes text input for destination, also accepts the config.show_path and config.insert_as options
|
||||
["m"] = "move", -- takes text input for destination, also accepts the config.show_path and config.insert_as options
|
||||
["e"] = "toggle_auto_expand_width",
|
||||
["q"] = "close_window",
|
||||
["?"] = "show_help",
|
||||
["<"] = "prev_source",
|
||||
[">"] = "next_source",
|
||||
},
|
||||
},
|
||||
filesystem = {
|
||||
window = {
|
||||
mappings = {
|
||||
["H"] = "toggle_hidden",
|
||||
["/"] = "fuzzy_finder",
|
||||
["D"] = "fuzzy_finder_directory",
|
||||
--["/"] = "filter_as_you_type", -- this was the default until v1.28
|
||||
["#"] = "fuzzy_sorter", -- fuzzy sorting using the fzy algorithm
|
||||
-- ["D"] = "fuzzy_sorter_directory",
|
||||
["f"] = "filter_on_submit",
|
||||
["<C-x>"] = "clear_filter",
|
||||
["<bs>"] = "navigate_up",
|
||||
["."] = "set_root",
|
||||
["[g"] = "prev_git_modified",
|
||||
["]g"] = "next_git_modified",
|
||||
},
|
||||
fuzzy_finder_mappings = { -- define keymaps for filter popup window in fuzzy_finder_mode
|
||||
["<down>"] = "move_cursor_down",
|
||||
["<C-n>"] = "move_cursor_down",
|
||||
["<up>"] = "move_cursor_up",
|
||||
["<C-p>"] = "move_cursor_up",
|
||||
},
|
||||
},
|
||||
async_directory_scan = "auto", -- "auto" means refreshes are async, but it's synchronous when called from the Neotree commands.
|
||||
-- "always" means directory scans are always async.
|
||||
-- "never" means directory scans are never async.
|
||||
scan_mode = "shallow", -- "shallow": Don't scan into directories to detect possible empty directory a priori
|
||||
-- "deep": Scan into directories to detect empty or grouped empty directories a priori.
|
||||
bind_to_cwd = true, -- true creates a 2-way binding between vim's cwd and neo-tree's root
|
||||
cwd_target = {
|
||||
sidebar = "tab", -- sidebar is when position = left or right
|
||||
current = "window" -- current is when position = current
|
||||
},
|
||||
-- The renderer section provides the renderers that will be used to render the tree.
|
||||
-- The first level is the node type.
|
||||
-- For each node type, you can specify a list of components to render.
|
||||
-- Components are rendered in the order they are specified.
|
||||
-- The first field in each component is the name of the function to call.
|
||||
-- The rest of the fields are passed to the function as the "config" argument.
|
||||
filtered_items = {
|
||||
visible = false, -- when true, they will just be displayed differently than normal items
|
||||
force_visible_in_empty_folder = false, -- when true, hidden files will be shown if the root folder is otherwise empty
|
||||
show_hidden_count = true, -- when true, the number of hidden items in each folder will be shown as the last entry
|
||||
hide_dotfiles = true,
|
||||
hide_gitignored = true,
|
||||
hide_hidden = true, -- only works on Windows for hidden files/directories
|
||||
hide_by_name = {
|
||||
".DS_Store",
|
||||
"thumbs.db"
|
||||
--"node_modules",
|
||||
},
|
||||
hide_by_pattern = { -- uses glob style patterns
|
||||
--"*.meta",
|
||||
--"*/src/*/tsconfig.json"
|
||||
},
|
||||
always_show = { -- remains visible even if other settings would normally hide it
|
||||
--".gitignored",
|
||||
},
|
||||
never_show = { -- remains hidden even if visible is toggled to true, this overrides always_show
|
||||
--".DS_Store",
|
||||
--"thumbs.db"
|
||||
},
|
||||
never_show_by_pattern = { -- uses glob style patterns
|
||||
--".null-ls_*",
|
||||
},
|
||||
},
|
||||
find_by_full_path_words = false, -- `false` means it only searches the tail of a path.
|
||||
-- `true` will change the filter into a full path
|
||||
-- search with space as an implicit ".*", so
|
||||
-- `fi init`
|
||||
-- will match: `./sources/filesystem/init.lua
|
||||
--find_command = "fd", -- this is determined automatically, you probably don't need to set it
|
||||
--find_args = { -- you can specify extra args to pass to the find command.
|
||||
-- fd = {
|
||||
-- "--exclude", ".git",
|
||||
-- "--exclude", "node_modules"
|
||||
-- }
|
||||
--},
|
||||
---- or use a function instead of list of strings
|
||||
--find_args = function(cmd, path, search_term, args)
|
||||
-- if cmd ~= "fd" then
|
||||
-- return args
|
||||
-- end
|
||||
-- --maybe you want to force the filter to always include hidden files:
|
||||
-- table.insert(args, "--hidden")
|
||||
-- -- but no one ever wants to see .git files
|
||||
-- table.insert(args, "--exclude")
|
||||
-- table.insert(args, ".git")
|
||||
-- -- or node_modules
|
||||
-- table.insert(args, "--exclude")
|
||||
-- table.insert(args, "node_modules")
|
||||
-- --here is where it pays to use the function, you can exclude more for
|
||||
-- --short search terms, or vary based on the directory
|
||||
-- if string.len(search_term) < 4 and path == "/home/cseickel" then
|
||||
-- table.insert(args, "--exclude")
|
||||
-- table.insert(args, "Library")
|
||||
-- end
|
||||
-- return args
|
||||
--end,
|
||||
group_empty_dirs = false, -- when true, empty folders will be grouped together
|
||||
search_limit = 50, -- max number of search results when using filters
|
||||
follow_current_file = false, -- This will find and focus the file in the active buffer every time
|
||||
-- the current file is changed while the tree is open.
|
||||
hijack_netrw_behavior = "open_default", -- netrw disabled, opening a directory opens neo-tree
|
||||
-- in whatever position is specified in window.position
|
||||
-- "open_current",-- netrw disabled, opening a directory opens within the
|
||||
-- window like netrw would, regardless of window.position
|
||||
-- "disabled", -- netrw left alone, neo-tree does not handle opening dirs
|
||||
use_libuv_file_watcher = false, -- This will use the OS level file watchers to detect changes
|
||||
-- instead of relying on nvim autocmd events.
|
||||
},
|
||||
buffers = {
|
||||
bind_to_cwd = true,
|
||||
follow_current_file = true, -- This will find and focus the file in the active buffer every time
|
||||
-- the current file is changed while the tree is open.
|
||||
group_empty_dirs = true, -- when true, empty directories will be grouped together
|
||||
show_unloaded = false, -- When working with sessions, for example, restored but unfocused buffers
|
||||
-- are mark as "unloaded". Turn this on to view these unloaded buffer.
|
||||
window = {
|
||||
mappings = {
|
||||
["<bs>"] = "navigate_up",
|
||||
["."] = "set_root",
|
||||
["bd"] = "buffer_delete",
|
||||
},
|
||||
},
|
||||
},
|
||||
git_status = {
|
||||
window = {
|
||||
mappings = {
|
||||
["A"] = "git_add_all",
|
||||
["gu"] = "git_unstage_file",
|
||||
["ga"] = "git_add_file",
|
||||
["gr"] = "git_revert_file",
|
||||
["gc"] = "git_commit",
|
||||
["gp"] = "git_push",
|
||||
["gg"] = "git_commit_and_push",
|
||||
},
|
||||
},
|
||||
},
|
||||
document_symbols = {
|
||||
follow_cursor = false,
|
||||
client_filters = "first",
|
||||
renderers = {
|
||||
root = {
|
||||
{"indent"},
|
||||
{"icon", default="C" },
|
||||
{"name", zindex = 10},
|
||||
},
|
||||
symbol = {
|
||||
{"indent", with_expanders = true},
|
||||
{"kind_icon", default="?" },
|
||||
{"container",
|
||||
content = {
|
||||
{"name", zindex = 10},
|
||||
{"kind_name", zindex = 20, align = "right"},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
window = {
|
||||
mappings = {
|
||||
["<cr>"] = "jump_to_symbol",
|
||||
["o"] = "jump_to_symbol",
|
||||
["A"] = "noop", -- also accepts the config.show_path and config.insert_as options.
|
||||
["d"] = "noop",
|
||||
["y"] = "noop",
|
||||
["x"] = "noop",
|
||||
["p"] = "noop",
|
||||
["c"] = "noop",
|
||||
["m"] = "noop",
|
||||
["a"] = "noop",
|
||||
["/"] = "filter",
|
||||
["f"] = "filter_on_submit",
|
||||
},
|
||||
},
|
||||
custom_kinds = {
|
||||
-- define custom kinds here (also remember to add icon and hl group to kinds)
|
||||
-- ccls
|
||||
-- [252] = 'TypeAlias',
|
||||
-- [253] = 'Parameter',
|
||||
-- [254] = 'StaticMethod',
|
||||
-- [255] = 'Macro',
|
||||
},
|
||||
kinds = {
|
||||
Unknown = { icon = "?", hl = "" },
|
||||
Root = { icon = "", hl = "NeoTreeRootName" },
|
||||
File = { icon = "", hl = "Tag" },
|
||||
Module = { icon = "", hl = "Exception" },
|
||||
Namespace = { icon = "", hl = "Include" },
|
||||
Package = { icon = "", hl = "Label" },
|
||||
Class = { icon = "", hl = "Include" },
|
||||
Method = { icon = "", hl = "Function" },
|
||||
Property = { icon = "", hl = "@property" },
|
||||
Field = { icon = "", hl = "@field" },
|
||||
Constructor = { icon = "", hl = "@constructor" },
|
||||
Enum = { icon = "了", hl = "@number" },
|
||||
Interface = { icon = "", hl = "Type" },
|
||||
Function = { icon = "", hl = "Function" },
|
||||
Variable = { icon = "", hl = "@variable" },
|
||||
Constant = { icon = "", hl = "Constant" },
|
||||
String = { icon = "", hl = "String" },
|
||||
Number = { icon = "", hl = "Number" },
|
||||
Boolean = { icon = "", hl = "Boolean" },
|
||||
Array = { icon = "", hl = "Type" },
|
||||
Object = { icon = "", hl = "Type" },
|
||||
Key = { icon = "", hl = "" },
|
||||
Null = { icon = "", hl = "Constant" },
|
||||
EnumMember = { icon = "", hl = "Number" },
|
||||
Struct = { icon = "", hl = "Type" },
|
||||
Event = { icon = "", hl = "Constant" },
|
||||
Operator = { icon = "", hl = "Operator" },
|
||||
TypeParameter = { icon = "", hl = "Type" },
|
||||
|
||||
-- ccls
|
||||
-- TypeAlias = { icon = ' ', hl = 'Type' },
|
||||
-- Parameter = { icon = ' ', hl = '@parameter' },
|
||||
-- StaticMethod = { icon = 'ﴂ ', hl = 'Function' },
|
||||
-- Macro = { icon = ' ', hl = 'Macro' },
|
||||
}
|
||||
},
|
||||
example = {
|
||||
renderers = {
|
||||
custom = {
|
||||
{"indent"},
|
||||
{"icon", default="C" },
|
||||
{"custom"},
|
||||
{"name"}
|
||||
}
|
||||
},
|
||||
window = {
|
||||
mappings = {
|
||||
["<cr>"] = "toggle_node",
|
||||
["<C-e>"] = "example_command",
|
||||
["d"] = "show_debug_info",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return config
|
93
bundle/neo-tree.nvim/lua/neo-tree/events/init.lua
vendored
Normal file
93
bundle/neo-tree.nvim/lua/neo-tree/events/init.lua
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
local vim = vim
|
||||
local q = require("neo-tree.events.queue")
|
||||
local log = require("neo-tree.log")
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {
|
||||
-- Well known event names, you can make up your own
|
||||
BEFORE_RENDER = "before_render",
|
||||
AFTER_RENDER = "after_render",
|
||||
FILE_ADDED = "file_added",
|
||||
FILE_DELETED = "file_deleted",
|
||||
BEFORE_FILE_MOVE = "before_file_move",
|
||||
FILE_MOVED = "file_moved",
|
||||
FILE_OPEN_REQUESTED = "file_open_requested",
|
||||
FILE_OPENED = "file_opened",
|
||||
BEFORE_FILE_RENAME = "before_file_rename",
|
||||
FILE_RENAMED = "file_renamed",
|
||||
FS_EVENT = "fs_event",
|
||||
GIT_EVENT = "git_event",
|
||||
GIT_STATUS_CHANGED = "git_status_changed",
|
||||
NEO_TREE_BUFFER_ENTER = "neo_tree_buffer_enter",
|
||||
NEO_TREE_BUFFER_LEAVE = "neo_tree_buffer_leave",
|
||||
NEO_TREE_LSP_UPDATE = "neo_tree_lsp_update",
|
||||
NEO_TREE_POPUP_BUFFER_ENTER = "neo_tree_popup_buffer_enter",
|
||||
NEO_TREE_POPUP_BUFFER_LEAVE = "neo_tree_popup_buffer_leave",
|
||||
NEO_TREE_WINDOW_AFTER_CLOSE = "neo_tree_window_after_close",
|
||||
NEO_TREE_WINDOW_AFTER_OPEN = "neo_tree_window_after_open",
|
||||
NEO_TREE_WINDOW_BEFORE_CLOSE = "neo_tree_window_before_close",
|
||||
NEO_TREE_WINDOW_BEFORE_OPEN = "neo_tree_window_before_open",
|
||||
VIM_AFTER_SESSION_LOAD = "vim_after_session_load",
|
||||
VIM_BUFFER_ADDED = "vim_buffer_added",
|
||||
VIM_BUFFER_CHANGED = "vim_buffer_changed",
|
||||
VIM_BUFFER_DELETED = "vim_buffer_deleted",
|
||||
VIM_BUFFER_ENTER = "vim_buffer_enter",
|
||||
VIM_BUFFER_MODIFIED_SET = "vim_buffer_modified_set",
|
||||
VIM_COLORSCHEME = "vim_colorscheme",
|
||||
VIM_CURSOR_MOVED = "vim_cursor_moved",
|
||||
VIM_DIAGNOSTIC_CHANGED = "vim_diagnostic_changed",
|
||||
VIM_DIR_CHANGED = "vim_dir_changed",
|
||||
VIM_INSERT_LEAVE = "vim_insert_leave",
|
||||
VIM_LEAVE = "vim_leave",
|
||||
VIM_LSP_REQUEST = "vim_lsp_request",
|
||||
VIM_RESIZED = "vim_resized",
|
||||
VIM_TAB_CLOSED = "vim_tab_closed",
|
||||
VIM_TERMINAL_ENTER = "vim_terminal_enter",
|
||||
VIM_TEXT_CHANGED_NORMAL = "vim_text_changed_normal",
|
||||
VIM_WIN_CLOSED = "vim_win_closed",
|
||||
VIM_WIN_ENTER = "vim_win_enter",
|
||||
}
|
||||
|
||||
M.define_autocmd_event = function(event_name, autocmds, debounce_frequency, seed_fn, nested)
|
||||
local opts = {
|
||||
setup = function()
|
||||
local tpl =
|
||||
":lua require('neo-tree.events').fire_event('%s', { afile = vim.fn.expand('<afile>') })"
|
||||
local callback = string.format(tpl, event_name)
|
||||
if nested then
|
||||
callback = "++nested " .. callback
|
||||
end
|
||||
|
||||
local autocmd = table.concat(autocmds, ",")
|
||||
if not vim.startswith(autocmd, "User") then
|
||||
autocmd = autocmd .. " *"
|
||||
end
|
||||
local cmds = {
|
||||
"augroup NeoTreeEvent_" .. event_name,
|
||||
"autocmd " .. autocmd .. " " .. callback,
|
||||
"augroup END",
|
||||
}
|
||||
log.trace("Registering autocmds: %s", table.concat(cmds, "\n"))
|
||||
vim.cmd(table.concat(cmds, "\n"))
|
||||
end,
|
||||
seed = seed_fn,
|
||||
teardown = function()
|
||||
log.trace("Teardown autocmds for ", event_name)
|
||||
vim.cmd(string.format("autocmd! NeoTreeEvent_%s", event_name))
|
||||
end,
|
||||
debounce_frequency = debounce_frequency,
|
||||
debounce_strategy = utils.debounce_strategy.CALL_LAST_ONLY,
|
||||
}
|
||||
log.debug("Defining autocmd event: %s", event_name)
|
||||
q.define_event(event_name, opts)
|
||||
end
|
||||
|
||||
M.clear_all_events = q.clear_all_events
|
||||
M.define_event = q.define_event
|
||||
M.destroy_event = q.destroy_event
|
||||
M.fire_event = q.fire_event
|
||||
|
||||
M.subscribe = q.subscribe
|
||||
M.unsubscribe = q.unsubscribe
|
||||
|
||||
return M
|
144
bundle/neo-tree.nvim/lua/neo-tree/events/queue.lua
vendored
Normal file
144
bundle/neo-tree.nvim/lua/neo-tree/events/queue.lua
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
local Queue = require("neo-tree.collections").Queue
|
||||
|
||||
local event_queues = {}
|
||||
local event_definitions = {}
|
||||
local M = {}
|
||||
|
||||
local validate_event_handler = function(event_handler)
|
||||
if type(event_handler) ~= "table" then
|
||||
error("Event handler must be a table")
|
||||
end
|
||||
if type(event_handler.event) ~= "string" then
|
||||
error("Event handler must have an event")
|
||||
end
|
||||
if type(event_handler.handler) ~= "function" then
|
||||
error("Event handler must have a handler")
|
||||
end
|
||||
end
|
||||
|
||||
M.clear_all_events = function()
|
||||
for event_name, queue in pairs(event_queues) do
|
||||
M.destroy_event(event_name)
|
||||
end
|
||||
event_queues = {}
|
||||
end
|
||||
|
||||
M.define_event = function(event_name, opts)
|
||||
local existing = event_definitions[event_name]
|
||||
if existing ~= nil then
|
||||
error("Event already defined: " .. event_name)
|
||||
end
|
||||
event_definitions[event_name] = opts
|
||||
end
|
||||
|
||||
M.destroy_event = function(event_name)
|
||||
local existing = event_definitions[event_name]
|
||||
if existing == nil then
|
||||
return false
|
||||
end
|
||||
if existing.setup_was_run and type(existing.teardown) == "function" then
|
||||
local success, result = pcall(existing.teardown)
|
||||
if not success then
|
||||
error("Error in teardown for " .. event_name .. ": " .. result)
|
||||
end
|
||||
existing.setup_was_run = false
|
||||
end
|
||||
event_queues[event_name] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
local fire_event_internal = function(event, args)
|
||||
local queue = event_queues[event]
|
||||
if queue == nil then
|
||||
return nil
|
||||
end
|
||||
--log.trace("Firing event: ", event, " with args: ", args)
|
||||
|
||||
if queue:is_empty() then
|
||||
--log.trace("Event queue is empty")
|
||||
return nil
|
||||
end
|
||||
local seed = utils.get_value(event_definitions, event .. ".seed")
|
||||
if seed ~= nil then
|
||||
local success, result = pcall(seed, args)
|
||||
if success and result then
|
||||
log.trace("Seed for " .. event .. " returned: " .. tostring(result))
|
||||
elseif success then
|
||||
log.trace("Seed for " .. event .. " returned falsy, cancelling event")
|
||||
else
|
||||
log.error("Error in seed function for " .. event .. ": " .. result)
|
||||
end
|
||||
end
|
||||
|
||||
return queue:for_each(function(event_handler)
|
||||
local remove_node = event_handler == nil or event_handler.cancelled
|
||||
if not remove_node then
|
||||
local success, result = pcall(event_handler.handler, args)
|
||||
local id = event_handler.id or event_handler
|
||||
if success then
|
||||
log.trace("Handler ", id, " for " .. event .. " called successfully.")
|
||||
else
|
||||
log.error(string.format("Error in event handler for event %s[%s]: %s", event, id, result))
|
||||
end
|
||||
if event_handler.once then
|
||||
event_handler.cancelled = true
|
||||
return true
|
||||
end
|
||||
return result
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
M.fire_event = function(event, args)
|
||||
local freq = utils.get_value(event_definitions, event .. ".debounce_frequency", 0, true)
|
||||
local strategy = utils.get_value(event_definitions, event .. ".debounce_strategy", 0, true)
|
||||
log.trace("Firing event: ", event, " with args: ", args)
|
||||
if freq > 0 then
|
||||
utils.debounce("EVENT_FIRED: " .. event, function()
|
||||
fire_event_internal(event, args or {})
|
||||
end, freq, strategy)
|
||||
else
|
||||
return fire_event_internal(event, args or {})
|
||||
end
|
||||
end
|
||||
|
||||
M.subscribe = function(event_handler)
|
||||
validate_event_handler(event_handler)
|
||||
|
||||
local queue = event_queues[event_handler.event]
|
||||
if queue == nil then
|
||||
log.debug("Creating queue for event: " .. event_handler.event)
|
||||
queue = Queue:new()
|
||||
local def = event_definitions[event_handler.event]
|
||||
if def and type(def.setup) == "function" then
|
||||
local success, result = pcall(def.setup)
|
||||
if success then
|
||||
def.setup_was_run = true
|
||||
log.debug("Setup for event " .. event_handler.event .. " was run")
|
||||
else
|
||||
log.error("Error in setup for " .. event_handler.event .. ": " .. result)
|
||||
end
|
||||
end
|
||||
event_queues[event_handler.event] = queue
|
||||
end
|
||||
log.debug("Adding event handler [", event_handler.id, "] for event: ", event_handler.event)
|
||||
queue:add(event_handler)
|
||||
end
|
||||
|
||||
M.unsubscribe = function(event_handler)
|
||||
local queue = event_queues[event_handler.event]
|
||||
if queue == nil then
|
||||
return nil
|
||||
end
|
||||
queue:remove_by_id(event_handler.id or event_handler)
|
||||
if queue:is_empty() then
|
||||
M.destroy_event(event_handler.event)
|
||||
event_queues[event_handler.event] = nil
|
||||
else
|
||||
event_queues[event_handler.event] = queue
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
156
bundle/neo-tree.nvim/lua/neo-tree/git/ignored.lua
vendored
Normal file
156
bundle/neo-tree.nvim/lua/neo-tree/git/ignored.lua
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
local Job = require("plenary.job")
|
||||
|
||||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
local git_utils = require("neo-tree.git.utils")
|
||||
|
||||
local M = {}
|
||||
local sep = utils.path_separator
|
||||
|
||||
M.is_ignored = function(ignored, path, _type)
|
||||
if _type == "directory" and not utils.is_windows then
|
||||
path = path .. sep
|
||||
end
|
||||
|
||||
return vim.tbl_contains(ignored, path)
|
||||
end
|
||||
|
||||
local git_root_cache = {
|
||||
known_roots = {},
|
||||
dir_lookup = {},
|
||||
}
|
||||
local get_root_for_item = function(item)
|
||||
local dir = item.type == "directory" and item.path or item.parent_path
|
||||
if type(git_root_cache.dir_lookup[dir]) ~= "nil" then
|
||||
return git_root_cache.dir_lookup[dir]
|
||||
end
|
||||
--for _, root in ipairs(git_root_cache.known_roots) do
|
||||
-- if vim.startswith(dir, root) then
|
||||
-- git_root_cache.dir_lookup[dir] = root
|
||||
-- return root
|
||||
-- end
|
||||
--end
|
||||
local root = git_utils.get_repository_root(dir)
|
||||
if root then
|
||||
git_root_cache.dir_lookup[dir] = root
|
||||
table.insert(git_root_cache.known_roots, root)
|
||||
else
|
||||
git_root_cache.dir_lookup[dir] = false
|
||||
end
|
||||
return root
|
||||
end
|
||||
|
||||
M.mark_ignored = function(state, items, callback)
|
||||
local folders = {}
|
||||
log.trace("================================================================================")
|
||||
log.trace("IGNORED: mark_ignore BEGIN...")
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
local folder = utils.split_path(item.path)
|
||||
if folder then
|
||||
if not folders[folder] then
|
||||
folders[folder] = {}
|
||||
end
|
||||
table.insert(folders[folder], item.path)
|
||||
end
|
||||
end
|
||||
|
||||
local function process_result(result)
|
||||
if utils.is_windows then
|
||||
--on Windows, git seems to return quotes and double backslash "path\\directory"
|
||||
result = vim.tbl_map(function(item)
|
||||
item = item:gsub("\\\\", "\\")
|
||||
return item
|
||||
end, result)
|
||||
else
|
||||
--check-ignore does not indicate directories the same as 'status' so we need to
|
||||
--add the trailing slash to the path manually if not on Windows.
|
||||
log.trace("IGNORED: Checking types of", #result, "items to see which ones are directories")
|
||||
for i, item in ipairs(result) do
|
||||
local stat = vim.loop.fs_stat(item)
|
||||
if stat and stat.type == "directory" then
|
||||
result[i] = item .. sep
|
||||
end
|
||||
end
|
||||
end
|
||||
result = vim.tbl_map(function(item)
|
||||
-- remove leading and trailing " from git output
|
||||
item = item:gsub('^"', ""):gsub('"$', "")
|
||||
-- convert octal encoded lines to utf-8
|
||||
item = git_utils.octal_to_utf8(item)
|
||||
return item
|
||||
end, result)
|
||||
return result
|
||||
end
|
||||
|
||||
local function finalize(all_results)
|
||||
local show_anyway = state.filtered_items and state.filtered_items.hide_gitignored == false
|
||||
log.trace("IGNORED: Comparing results to mark items as ignored, show_anyway:", show_anyway)
|
||||
local ignored, not_ignored = 0, 0
|
||||
for _, item in ipairs(items) do
|
||||
if M.is_ignored(all_results, item.path, item.type) then
|
||||
item.filtered_by = item.filtered_by or {}
|
||||
item.filtered_by.gitignored = true
|
||||
item.filtered_by.show_anyway = show_anyway
|
||||
ignored = ignored + 1
|
||||
else
|
||||
not_ignored = not_ignored + 1
|
||||
end
|
||||
end
|
||||
log.trace("IGNORED: mark_ignored is complete, ignored:", ignored, ", not ignored:", not_ignored)
|
||||
log.trace("================================================================================")
|
||||
end
|
||||
|
||||
local all_results = {}
|
||||
if type(callback) == "function" then
|
||||
local jobs = {}
|
||||
local progress = 0
|
||||
for folder, folder_items in pairs(folders) do
|
||||
local args = { "-C", folder, "check-ignore", "--stdin" }
|
||||
local job = Job:new({
|
||||
command = "git",
|
||||
args = args,
|
||||
enabled_recording = true,
|
||||
writer = folder_items,
|
||||
on_start = function()
|
||||
log.trace("IGNORED: Running async git with args: ", args)
|
||||
end,
|
||||
on_exit = function(self, code, _)
|
||||
local result
|
||||
if code ~= 0 then
|
||||
log.debug("Failed to load ignored files for", state.path, ":", self:stderr_result())
|
||||
result = {}
|
||||
else
|
||||
result = self:result()
|
||||
end
|
||||
vim.list_extend(all_results, process_result(result))
|
||||
progress = progress + 1
|
||||
if progress == #jobs then
|
||||
finalize(all_results)
|
||||
callback(all_results)
|
||||
end
|
||||
end,
|
||||
})
|
||||
table.insert(jobs, job)
|
||||
end
|
||||
|
||||
for _, job in ipairs(jobs) do
|
||||
job:start()
|
||||
end
|
||||
else
|
||||
for folder, folder_items in pairs(folders) do
|
||||
local cmd = { "git", "-C", folder, "check-ignore", unpack(folder_items) }
|
||||
log.trace("IGNORED: Running cmd: ", cmd)
|
||||
local result = vim.fn.systemlist(cmd)
|
||||
if vim.v.shell_error == 128 then
|
||||
log.debug("Failed to load ignored files for", state.path, ":", result)
|
||||
result = {}
|
||||
end
|
||||
vim.list_extend(all_results, process_result(result))
|
||||
end
|
||||
finalize(all_results)
|
||||
return all_results
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
13
bundle/neo-tree.nvim/lua/neo-tree/git/init.lua
vendored
Normal file
13
bundle/neo-tree.nvim/lua/neo-tree/git/init.lua
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
local status = require("neo-tree.git.status")
|
||||
local ignored = require("neo-tree.git.ignored")
|
||||
local git_utils = require("neo-tree.git.utils")
|
||||
|
||||
local M = {
|
||||
get_repository_root = git_utils.get_repository_root,
|
||||
is_ignored = ignored.is_ignored,
|
||||
mark_ignored = ignored.mark_ignored,
|
||||
status = status.status,
|
||||
status_async = status.status_async,
|
||||
}
|
||||
|
||||
return M
|
339
bundle/neo-tree.nvim/lua/neo-tree/git/status.lua
vendored
Normal file
339
bundle/neo-tree.nvim/lua/neo-tree/git/status.lua
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
local utils = require("neo-tree.utils")
|
||||
local events = require("neo-tree.events")
|
||||
local Job = require("plenary.job")
|
||||
local log = require("neo-tree.log")
|
||||
local git_utils = require("neo-tree.git.utils")
|
||||
|
||||
local M = {}
|
||||
|
||||
local function get_simple_git_status_code(status)
|
||||
-- Prioritze M then A over all others
|
||||
if status:match("U") or status == "AA" or status == "DD" then
|
||||
return "U"
|
||||
elseif status:match("M") then
|
||||
return "M"
|
||||
elseif status:match("[ACR]") then
|
||||
return "A"
|
||||
elseif status:match("!$") then
|
||||
return "!"
|
||||
elseif status:match("?$") then
|
||||
return "?"
|
||||
else
|
||||
local len = #status
|
||||
while len > 0 do
|
||||
local char = status:sub(len, len)
|
||||
if char ~= " " then
|
||||
return char
|
||||
end
|
||||
len = len - 1
|
||||
end
|
||||
return status
|
||||
end
|
||||
end
|
||||
|
||||
local function get_priority_git_status_code(status, other_status)
|
||||
if not status then
|
||||
return other_status
|
||||
elseif not other_status then
|
||||
return status
|
||||
elseif status == "U" or other_status == "U" then
|
||||
return "U"
|
||||
elseif status == "?" or other_status == "?" then
|
||||
return "?"
|
||||
elseif status == "M" or other_status == "M" then
|
||||
return "M"
|
||||
elseif status == "A" or other_status == "A" then
|
||||
return "A"
|
||||
else
|
||||
return status
|
||||
end
|
||||
end
|
||||
|
||||
local parse_git_status_line = function(context, line)
|
||||
context.lines_parsed = context.lines_parsed + 1
|
||||
if type(line) ~= "string" then
|
||||
return
|
||||
end
|
||||
if #line < 4 then
|
||||
return
|
||||
end
|
||||
local git_root = context.git_root
|
||||
local git_status = context.git_status
|
||||
local exclude_directories = context.exclude_directories
|
||||
|
||||
local line_parts = vim.split(line, " ")
|
||||
if #line_parts < 2 then
|
||||
return
|
||||
end
|
||||
local status = line_parts[1]
|
||||
local relative_path = line_parts[2]
|
||||
|
||||
-- rename output is `R000 from/filename to/filename`
|
||||
if status:match("^R") then
|
||||
relative_path = line_parts[3]
|
||||
end
|
||||
|
||||
-- remove any " due to whitespace or utf-8 in the path
|
||||
relative_path = relative_path:gsub('^"', ""):gsub('"$', "")
|
||||
-- convert octal encoded lines to utf-8
|
||||
relative_path = git_utils.octal_to_utf8(relative_path)
|
||||
|
||||
if utils.is_windows == true then
|
||||
relative_path = utils.windowize_path(relative_path)
|
||||
end
|
||||
local absolute_path = utils.path_join(git_root, relative_path)
|
||||
-- merge status result if there are results from multiple passes
|
||||
local existing_status = git_status[absolute_path]
|
||||
if existing_status then
|
||||
local merged = ""
|
||||
local i = 0
|
||||
while i < 2 do
|
||||
i = i + 1
|
||||
local existing_char = #existing_status >= i and existing_status:sub(i, i) or ""
|
||||
local new_char = #status >= i and status:sub(i, i) or ""
|
||||
local merged_char = get_priority_git_status_code(existing_char, new_char)
|
||||
merged = merged .. merged_char
|
||||
end
|
||||
status = merged
|
||||
end
|
||||
git_status[absolute_path] = status
|
||||
|
||||
if not exclude_directories then
|
||||
-- Now bubble this status up to the parent directories
|
||||
local parts = utils.split(absolute_path, utils.path_separator)
|
||||
table.remove(parts) -- pop the last part so we don't override the file's status
|
||||
utils.reduce(parts, "", function(acc, part)
|
||||
local path = acc .. utils.path_separator .. part
|
||||
if utils.is_windows == true then
|
||||
path = path:gsub("^" .. utils.path_separator, "")
|
||||
end
|
||||
local path_status = git_status[path]
|
||||
local file_status = get_simple_git_status_code(status)
|
||||
git_status[path] = get_priority_git_status_code(path_status, file_status)
|
||||
return path
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---Parse "git status" output for the current working directory.
|
||||
---@base git ref base
|
||||
---@exclude_directories boolean Whether to skip bubling up status to directories
|
||||
---@path string Path to run the git status command in, defaults to cwd.
|
||||
---@return table table Table with the path as key and the status as value.
|
||||
---@return table, string|nil The git root for the specified path.
|
||||
M.status = function(base, exclude_directories, path)
|
||||
local git_root = git_utils.get_repository_root(path)
|
||||
if not utils.truthy(git_root) then
|
||||
return {}
|
||||
end
|
||||
|
||||
local C = git_root
|
||||
local staged_cmd = { "git", "-C", C, "diff", "--staged", "--name-status", base, "--" }
|
||||
local staged_ok, staged_result = utils.execute_command(staged_cmd)
|
||||
if not staged_ok then
|
||||
return {}
|
||||
end
|
||||
local unstaged_cmd = { "git", "-C", C, "diff", "--name-status" }
|
||||
local unstaged_ok, unstaged_result = utils.execute_command(unstaged_cmd)
|
||||
if not unstaged_ok then
|
||||
return {}
|
||||
end
|
||||
local untracked_cmd = { "git", "-C", C, "ls-files", "--exclude-standard", "--others" }
|
||||
local untracked_ok, untracked_result = utils.execute_command(untracked_cmd)
|
||||
if not untracked_ok then
|
||||
return {}
|
||||
end
|
||||
|
||||
local context = {
|
||||
git_root = git_root,
|
||||
git_status = {},
|
||||
exclude_directories = exclude_directories,
|
||||
lines_parsed = 0,
|
||||
}
|
||||
|
||||
for _, line in ipairs(staged_result) do
|
||||
parse_git_status_line(context, line)
|
||||
end
|
||||
for _, line in ipairs(unstaged_result) do
|
||||
if line then
|
||||
line = " " .. line
|
||||
end
|
||||
parse_git_status_line(context, line)
|
||||
end
|
||||
for _, line in ipairs(untracked_result) do
|
||||
if line then
|
||||
line = "? " .. line
|
||||
end
|
||||
parse_git_status_line(context, line)
|
||||
end
|
||||
|
||||
return context.git_status, git_root
|
||||
end
|
||||
|
||||
local function parse_lines_batch(context, job_complete_callback)
|
||||
local i, batch_size = 0, context.batch_size
|
||||
|
||||
if context.lines_total == nil then
|
||||
-- first time through, get the total number of lines
|
||||
context.lines_total = math.min(context.max_lines, #context.lines)
|
||||
context.lines_parsed = 0
|
||||
if context.lines_total == 0 then
|
||||
if type(job_complete_callback) == "function" then
|
||||
job_complete_callback()
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
batch_size = math.min(context.batch_size, context.lines_total - context.lines_parsed)
|
||||
|
||||
while i < batch_size do
|
||||
i = i + 1
|
||||
parse_git_status_line(context, context.lines[context.lines_parsed + 1])
|
||||
end
|
||||
|
||||
if context.lines_parsed >= context.lines_total then
|
||||
if type(job_complete_callback) == "function" then
|
||||
job_complete_callback()
|
||||
end
|
||||
else
|
||||
-- add small delay so other work can happen
|
||||
vim.defer_fn(function()
|
||||
parse_lines_batch(context, job_complete_callback)
|
||||
end, context.batch_delay)
|
||||
end
|
||||
end
|
||||
|
||||
M.status_async = function(path, base, opts)
|
||||
git_utils.get_repository_root(path, function(git_root)
|
||||
if utils.truthy(git_root) then
|
||||
log.trace("git.status.status_async called")
|
||||
else
|
||||
log.trace("status_async: not a git folder: ", path)
|
||||
return false
|
||||
end
|
||||
|
||||
local event_id = "git_status_" .. git_root
|
||||
local context = {
|
||||
git_root = git_root,
|
||||
git_status = {},
|
||||
exclude_directories = false,
|
||||
lines = {},
|
||||
lines_parsed = 0,
|
||||
batch_size = opts.batch_size or 1000,
|
||||
batch_delay = opts.batch_delay or 10,
|
||||
max_lines = opts.max_lines or 100000,
|
||||
}
|
||||
|
||||
local should_process = function(err, line, job, err_msg)
|
||||
if vim.v.dying > 0 or vim.v.exiting ~= vim.NIL then
|
||||
job:shutdown()
|
||||
return false
|
||||
end
|
||||
if err and err > 0 then
|
||||
log.error(err_msg, err, line)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local job_complete_callback = function()
|
||||
utils.debounce(event_id, nil, nil, nil, utils.debounce_action.COMPLETE_ASYNC_JOB)
|
||||
vim.schedule(function()
|
||||
events.fire_event(events.GIT_STATUS_CHANGED, {
|
||||
git_root = context.git_root,
|
||||
git_status = context.git_status,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local parse_lines = vim.schedule_wrap(function()
|
||||
parse_lines_batch(context, job_complete_callback)
|
||||
end)
|
||||
|
||||
utils.debounce(event_id, function()
|
||||
local staged_job = Job:new({
|
||||
command = "git",
|
||||
args = { "-C", git_root, "diff", "--staged", "--name-status", base, "--" },
|
||||
enable_recording = false,
|
||||
maximium_results = context.max_lines,
|
||||
on_stdout = vim.schedule_wrap(function(err, line, job)
|
||||
if should_process(err, line, job, "status_async staged error:") then
|
||||
table.insert(context.lines, line)
|
||||
end
|
||||
end),
|
||||
on_stderr = function(err, line)
|
||||
if err and err > 0 then
|
||||
log.error("status_async staged error: ", err, line)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local unstaged_job = Job:new({
|
||||
command = "git",
|
||||
args = { "-C", git_root, "diff", "--name-status" },
|
||||
enable_recording = false,
|
||||
maximium_results = context.max_lines,
|
||||
on_stdout = vim.schedule_wrap(function(err, line, job)
|
||||
if should_process(err, line, job, "status_async unstaged error:") then
|
||||
if line then
|
||||
line = " " .. line
|
||||
end
|
||||
table.insert(context.lines, line)
|
||||
end
|
||||
end),
|
||||
on_stderr = function(err, line)
|
||||
if err and err > 0 then
|
||||
log.error("status_async unstaged error: ", err, line)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local untracked_job = Job:new({
|
||||
command = "git",
|
||||
args = { "-C", git_root, "ls-files", "--exclude-standard", "--others" },
|
||||
enable_recording = false,
|
||||
maximium_results = context.max_lines,
|
||||
on_stdout = vim.schedule_wrap(function(err, line, job)
|
||||
if should_process(err, line, job, "status_async untracked error:") then
|
||||
if line then
|
||||
line = "? " .. line
|
||||
end
|
||||
table.insert(context.lines, line)
|
||||
end
|
||||
end),
|
||||
on_stderr = function(err, line)
|
||||
if err and err > 0 then
|
||||
log.error("status_async untracked error: ", err, line)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
Job:new({
|
||||
command = "git",
|
||||
args = {
|
||||
"-C",
|
||||
git_root,
|
||||
"config",
|
||||
"--get",
|
||||
"status.showUntrackedFiles",
|
||||
},
|
||||
enabled_recording = true,
|
||||
on_exit = function(self, _, _)
|
||||
local result = self:result()
|
||||
log.debug("git status.showUntrackedFiles =", result[1])
|
||||
if result[1] == "no" then
|
||||
unstaged_job:after(parse_lines)
|
||||
Job.chain(staged_job, unstaged_job)
|
||||
else
|
||||
untracked_job:after(parse_lines)
|
||||
Job.chain(staged_job, unstaged_job, untracked_job)
|
||||
end
|
||||
end,
|
||||
}):start()
|
||||
end, 1000, utils.debounce_strategy.CALL_FIRST_AND_LAST, utils.debounce_action.START_ASYNC_JOB)
|
||||
|
||||
return true
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
65
bundle/neo-tree.nvim/lua/neo-tree/git/utils.lua
vendored
Normal file
65
bundle/neo-tree.nvim/lua/neo-tree/git/utils.lua
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
local Job = require("plenary.job")
|
||||
|
||||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.get_repository_root = function(path, callback)
|
||||
local args = { "rev-parse", "--show-toplevel" }
|
||||
if utils.truthy(path) then
|
||||
args = { "-C", path, "rev-parse", "--show-toplevel" }
|
||||
end
|
||||
if type(callback) == "function" then
|
||||
Job:new({
|
||||
command = "git",
|
||||
args = args,
|
||||
enabled_recording = true,
|
||||
on_exit = function(self, code, _)
|
||||
if code ~= 0 then
|
||||
log.trace("GIT ROOT ERROR ", self:stderr_result())
|
||||
callback(nil)
|
||||
return
|
||||
end
|
||||
local git_root = self:result()[1]
|
||||
|
||||
if utils.is_windows then
|
||||
git_root = utils.windowize_path(git_root)
|
||||
end
|
||||
|
||||
log.trace("GIT ROOT for '", path, "' is '", git_root, "'")
|
||||
callback(git_root)
|
||||
end,
|
||||
}):start()
|
||||
else
|
||||
local ok, git_root = utils.execute_command({ "git", unpack(args) })
|
||||
if not ok then
|
||||
log.trace("GIT ROOT ERROR ", git_root)
|
||||
return nil
|
||||
end
|
||||
git_root = git_root[1]
|
||||
|
||||
if utils.is_windows then
|
||||
git_root = utils.windowize_path(git_root)
|
||||
end
|
||||
|
||||
log.trace("GIT ROOT for '", path, "' is '", git_root, "'")
|
||||
return git_root
|
||||
end
|
||||
end
|
||||
|
||||
local convert_octal_char = function(octal)
|
||||
return string.char(tonumber(octal, 8))
|
||||
end
|
||||
|
||||
M.octal_to_utf8 = function(text)
|
||||
-- git uses octal encoding for utf-8 filepaths, convert octal back to utf-8
|
||||
local success, converted = pcall(string.gsub, text, "\\([0-7][0-7][0-7])", convert_octal_char)
|
||||
if success then
|
||||
return converted
|
||||
else
|
||||
return text
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
189
bundle/neo-tree.nvim/lua/neo-tree/log.lua
vendored
Normal file
189
bundle/neo-tree.nvim/lua/neo-tree/log.lua
vendored
Normal file
@ -0,0 +1,189 @@
|
||||
-- log.lua
|
||||
--
|
||||
-- Inspired by rxi/log.lua
|
||||
-- Modified by tjdevries and can be found at github.com/tjdevries/vlog.nvim
|
||||
--
|
||||
-- This library is free software; you can redistribute it and/or modify it
|
||||
-- under the terms of the MIT license. See LICENSE for details.
|
||||
|
||||
local vim = vim
|
||||
-- User configuration section
|
||||
local default_config = {
|
||||
-- Name of the plugin. Prepended to log messages
|
||||
plugin = "neo-tree.nvim",
|
||||
|
||||
-- Should print the output to neovim while running
|
||||
use_console = true,
|
||||
|
||||
-- Should highlighting be used in console (using echohl)
|
||||
highlights = true,
|
||||
|
||||
-- Should write to a file
|
||||
use_file = false,
|
||||
|
||||
-- Any messages above this level will be logged.
|
||||
level = "info",
|
||||
|
||||
-- Level configuration
|
||||
modes = {
|
||||
{ name = "trace", hl = "None", level = vim.log.levels.TRACE },
|
||||
{ name = "debug", hl = "None", level = vim.log.levels.DEBGUG },
|
||||
{ name = "info", hl = "None", level = vim.log.levels.INFO },
|
||||
{ name = "warn", hl = "WarningMsg", level = vim.log.levels.WARN },
|
||||
{ name = "error", hl = "ErrorMsg", level = vim.log.levels.ERROR },
|
||||
{ name = "fatal", hl = "ErrorMsg", level = vim.log.levels.ERROR },
|
||||
},
|
||||
|
||||
-- Can limit the number of decimals displayed for floats
|
||||
float_precision = 0.01,
|
||||
}
|
||||
|
||||
-- {{{ NO NEED TO CHANGE
|
||||
local log = {}
|
||||
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local notify = function(message, level_config)
|
||||
if type(vim.notify) == "table" then
|
||||
-- probably using nvim-notify
|
||||
vim.notify(message, level_config.level, { title = "Neo-tree" })
|
||||
else
|
||||
local nameupper = level_config.name:upper()
|
||||
local console_string = string.format("[Neo-tree %s] %s", nameupper, message)
|
||||
vim.notify(console_string, level_config.level)
|
||||
end
|
||||
end
|
||||
|
||||
log.new = function(config, standalone)
|
||||
config = vim.tbl_deep_extend("force", default_config, config)
|
||||
|
||||
local outfile =
|
||||
string.format("%s/%s.log", vim.api.nvim_call_function("stdpath", { "data" }), config.plugin)
|
||||
|
||||
local obj
|
||||
if standalone then
|
||||
obj = log
|
||||
else
|
||||
obj = {}
|
||||
end
|
||||
obj.outfile = outfile
|
||||
|
||||
obj.use_file = function(file, quiet)
|
||||
if file == false then
|
||||
if not quiet then
|
||||
obj.info("[neo-tree] Logging to file disabled")
|
||||
end
|
||||
config.use_file = false
|
||||
else
|
||||
if type(file) == "string" then
|
||||
obj.outfile = file
|
||||
else
|
||||
obj.outfile = outfile
|
||||
end
|
||||
config.use_file = true
|
||||
if not quiet then
|
||||
obj.info("[neo-tree] Logging to file: " .. obj.outfile)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local levels = {}
|
||||
for i, v in ipairs(config.modes) do
|
||||
levels[v.name] = i
|
||||
end
|
||||
|
||||
obj.set_level = function(level)
|
||||
if levels[level] then
|
||||
if config.level ~= level then
|
||||
config.level = level
|
||||
end
|
||||
else
|
||||
notify("Invalid log level: " .. level, config.modes[5])
|
||||
end
|
||||
end
|
||||
|
||||
local round = function(x, increment)
|
||||
increment = increment or 1
|
||||
x = x / increment
|
||||
return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment
|
||||
end
|
||||
|
||||
local make_string = function(...)
|
||||
local t = {}
|
||||
for i = 1, select("#", ...) do
|
||||
local x = select(i, ...)
|
||||
|
||||
if type(x) == "number" and config.float_precision then
|
||||
x = tostring(round(x, config.float_precision))
|
||||
elseif type(x) == "table" then
|
||||
x = vim.inspect(x)
|
||||
if #x > 300 then
|
||||
x = x:sub(1, 300) .. "..."
|
||||
end
|
||||
else
|
||||
x = tostring(x)
|
||||
end
|
||||
|
||||
t[#t + 1] = x
|
||||
end
|
||||
return table.concat(t, " ")
|
||||
end
|
||||
|
||||
local log_at_level = function(level, level_config, message_maker, ...)
|
||||
-- Return early if we're below the config.level
|
||||
if level < levels[config.level] then
|
||||
return
|
||||
end
|
||||
-- Ignnore this if vim is exiting
|
||||
if vim.v.dying > 0 or vim.v.exiting ~= vim.NIL then
|
||||
return
|
||||
end
|
||||
local nameupper = level_config.name:upper()
|
||||
|
||||
local msg = message_maker(...)
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
local lineinfo = info.short_src .. ":" .. info.currentline
|
||||
|
||||
-- Output to log file
|
||||
if config.use_file then
|
||||
local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg)
|
||||
local fp = io.open(obj.outfile, "a")
|
||||
if fp then
|
||||
fp:write(str)
|
||||
fp:close()
|
||||
else
|
||||
print("[neo-tree] Could not open log file: " .. obj.outfile)
|
||||
end
|
||||
end
|
||||
|
||||
-- Output to console
|
||||
if config.use_console and level > 2 then
|
||||
vim.schedule(function()
|
||||
notify(msg, level_config)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
for i, x in ipairs(config.modes) do
|
||||
obj[x.name] = function(...)
|
||||
return log_at_level(i, x, make_string, ...)
|
||||
end
|
||||
|
||||
obj[("fmt_%s"):format(x.name)] = function()
|
||||
return log_at_level(i, x, function(...)
|
||||
local passed = { ... }
|
||||
local fmt = table.remove(passed, 1)
|
||||
local inspected = {}
|
||||
for _, v in ipairs(passed) do
|
||||
table.insert(inspected, vim.inspect(v))
|
||||
end
|
||||
return string.format(fmt, unpack(inspected))
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
log.new(default_config, true)
|
||||
-- }}}
|
||||
|
||||
return log
|
93
bundle/neo-tree.nvim/lua/neo-tree/setup/deprecations.lua
vendored
Normal file
93
bundle/neo-tree.nvim/lua/neo-tree/setup/deprecations.lua
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {}
|
||||
|
||||
local migrations = {}
|
||||
|
||||
M.show_migrations = function()
|
||||
if #migrations > 0 then
|
||||
for i, message in ipairs(migrations) do
|
||||
migrations[i] = " * " .. message
|
||||
end
|
||||
table.insert(
|
||||
migrations,
|
||||
1,
|
||||
"# Neo-tree configuration has been updated. Please review the changes below."
|
||||
)
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, migrations)
|
||||
vim.api.nvim_buf_set_option(buf, "buftype", "nofile")
|
||||
vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe")
|
||||
vim.api.nvim_buf_set_option(buf, "buflisted", false)
|
||||
vim.api.nvim_buf_set_option(buf, "swapfile", false)
|
||||
vim.api.nvim_buf_set_option(buf, "modifiable", false)
|
||||
vim.api.nvim_buf_set_option(buf, "filetype", "markdown")
|
||||
vim.api.nvim_buf_set_name(buf, "Neo-tree migrations")
|
||||
vim.defer_fn(function()
|
||||
vim.cmd(string.format("%ssplit", #migrations))
|
||||
vim.api.nvim_win_set_buf(0, buf)
|
||||
end, 100)
|
||||
end
|
||||
end
|
||||
|
||||
M.migrate = function(config)
|
||||
migrations = {}
|
||||
|
||||
local moved = function(old, new, converter)
|
||||
local existing = utils.get_value(config, old)
|
||||
if type(existing) ~= "nil" then
|
||||
if type(converter) == "function" then
|
||||
existing = converter(existing)
|
||||
end
|
||||
utils.set_value(config, new, existing)
|
||||
config[old] = nil
|
||||
migrations[#migrations + 1] =
|
||||
string.format("The `%s` option has been deprecated, please use `%s` instead.", old, new)
|
||||
end
|
||||
end
|
||||
|
||||
local removed = function(key)
|
||||
local value = utils.get_value(config, key)
|
||||
if type(value) ~= "nil" then
|
||||
utils.set_value(config, key, nil)
|
||||
migrations[#migrations + 1] = string.format("The `%s` option has been removed.", key)
|
||||
end
|
||||
end
|
||||
|
||||
local renamed_value = function(key, old_value, new_value)
|
||||
local value = utils.get_value(config, key)
|
||||
if value == old_value then
|
||||
utils.set_value(config, key, new_value)
|
||||
migrations[#migrations + 1] =
|
||||
string.format("The `%s=%s` option has been renamed to `%s`.", key, old_value, new_value)
|
||||
end
|
||||
end
|
||||
|
||||
local opposite = function(value)
|
||||
return not value
|
||||
end
|
||||
|
||||
local tab_to_source_migrator = function(labels)
|
||||
local converted_sources = {}
|
||||
for entry, label in pairs(labels) do
|
||||
table.insert(converted_sources, { source = entry, display_name = label })
|
||||
end
|
||||
return converted_sources
|
||||
end
|
||||
|
||||
moved("filesystem.filters", "filesystem.filtered_items")
|
||||
moved("filesystem.filters.show_hidden", "filesystem.filtered_items.hide_dotfiles", opposite)
|
||||
moved("filesystem.filters.respect_gitignore", "filesystem.filtered_items.hide_gitignored")
|
||||
moved("open_files_do_not_replace_filetypes", "open_files_do_not_replace_types")
|
||||
moved("source_selector.tab_labels", "source_selector.sources", tab_to_source_migrator)
|
||||
removed("filesystem.filters.gitignore_source")
|
||||
removed("filesystem.filter_items.gitignore_source")
|
||||
renamed_value("filesystem.hijack_netrw_behavior", "open_split", "open_current")
|
||||
for _, source in ipairs({ "filesystem", "buffers", "git_status" }) do
|
||||
renamed_value(source .. "window.position", "split", "current")
|
||||
end
|
||||
|
||||
return migrations
|
||||
end
|
||||
|
||||
return M
|
739
bundle/neo-tree.nvim/lua/neo-tree/setup/init.lua
vendored
Normal file
739
bundle/neo-tree.nvim/lua/neo-tree/setup/init.lua
vendored
Normal file
@ -0,0 +1,739 @@
|
||||
local utils = require("neo-tree.utils")
|
||||
local defaults = require("neo-tree.defaults")
|
||||
local mapping_helper = require("neo-tree.setup.mapping-helper")
|
||||
local events = require("neo-tree.events")
|
||||
local log = require("neo-tree.log")
|
||||
local file_nesting = require("neo-tree.sources.common.file-nesting")
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local netrw = require("neo-tree.setup.netrw")
|
||||
|
||||
local M = {}
|
||||
|
||||
local normalize_mappings = function(config)
|
||||
if config == nil then
|
||||
return false
|
||||
end
|
||||
local mappings = utils.get_value(config, "window.mappings", nil)
|
||||
if mappings then
|
||||
local fixed = mapping_helper.normalize_map(mappings)
|
||||
config.window.mappings = fixed
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local events_setup = false
|
||||
local define_events = function()
|
||||
if events_setup then
|
||||
return
|
||||
end
|
||||
|
||||
events.define_event(events.FS_EVENT, {
|
||||
debounce_frequency = 100,
|
||||
debounce_strategy = utils.debounce_strategy.CALL_LAST_ONLY,
|
||||
})
|
||||
|
||||
local v = vim.version()
|
||||
local diag_autocmd = "DiagnosticChanged"
|
||||
if v.major < 1 and v.minor < 6 then
|
||||
diag_autocmd = "User LspDiagnosticsChanged"
|
||||
end
|
||||
events.define_autocmd_event(events.VIM_DIAGNOSTIC_CHANGED, { diag_autocmd }, 500, function(args)
|
||||
args.diagnostics_lookup = utils.get_diagnostic_counts()
|
||||
return args
|
||||
end)
|
||||
|
||||
|
||||
|
||||
local update_opened_buffers = function(args)
|
||||
args.opened_buffers = utils.get_opened_buffers()
|
||||
return args
|
||||
end
|
||||
|
||||
events.define_autocmd_event(events.VIM_AFTER_SESSION_LOAD, { "SessionLoadPost" }, 200)
|
||||
events.define_autocmd_event(events.VIM_BUFFER_ADDED, { "BufAdd" }, 200, update_opened_buffers)
|
||||
events.define_autocmd_event(
|
||||
events.VIM_BUFFER_DELETED,
|
||||
{ "BufDelete" },
|
||||
200,
|
||||
update_opened_buffers
|
||||
)
|
||||
events.define_autocmd_event(events.VIM_BUFFER_ENTER, { "BufEnter", "BufWinEnter" }, 0)
|
||||
events.define_autocmd_event(
|
||||
events.VIM_BUFFER_MODIFIED_SET,
|
||||
{ "BufModifiedSet" },
|
||||
0,
|
||||
update_opened_buffers
|
||||
)
|
||||
events.define_autocmd_event(events.VIM_COLORSCHEME, { "ColorScheme" }, 0)
|
||||
events.define_autocmd_event(events.VIM_CURSOR_MOVED, { "CursorMoved" }, 100)
|
||||
events.define_autocmd_event(events.VIM_DIR_CHANGED, { "DirChanged" }, 200, nil, true)
|
||||
events.define_autocmd_event(events.VIM_INSERT_LEAVE, { "InsertLeave" }, 200)
|
||||
events.define_autocmd_event(events.VIM_LEAVE, { "VimLeavePre" })
|
||||
events.define_autocmd_event(events.VIM_RESIZED, { "VimResized" }, 100)
|
||||
events.define_autocmd_event(events.VIM_TAB_CLOSED, { "TabClosed" })
|
||||
events.define_autocmd_event(events.VIM_TERMINAL_ENTER, { "TermEnter" }, 0)
|
||||
events.define_autocmd_event(events.VIM_TEXT_CHANGED_NORMAL, { "TextChanged" }, 200)
|
||||
events.define_autocmd_event(events.VIM_WIN_CLOSED, { "WinClosed" })
|
||||
events.define_autocmd_event(events.VIM_WIN_ENTER, { "WinEnter" }, 0, nil, true)
|
||||
|
||||
events.define_autocmd_event(events.GIT_EVENT, { "User FugitiveChanged" }, 100)
|
||||
events.define_event(events.GIT_STATUS_CHANGED, { debounce_frequency = 0 })
|
||||
events_setup = true
|
||||
|
||||
events.subscribe({
|
||||
event = events.VIM_LEAVE,
|
||||
handler = function()
|
||||
events.clear_all_events()
|
||||
end,
|
||||
})
|
||||
|
||||
events.subscribe({
|
||||
event = events.VIM_RESIZED,
|
||||
handler = function()
|
||||
require("neo-tree.ui.renderer").update_floating_window_layouts()
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local prior_window_options = {}
|
||||
|
||||
--- Store the current window options so we can restore them when we close the tree.
|
||||
--- @param winid number | nil The window id to store the options for, defaults to current window
|
||||
local store_local_window_settings = function(winid)
|
||||
winid = winid or vim.api.nvim_get_current_win()
|
||||
local neo_tree_settings_applied, _ =
|
||||
pcall(vim.api.nvim_win_get_var, winid, "neo_tree_settings_applied")
|
||||
if neo_tree_settings_applied then
|
||||
-- don't store our own window settings
|
||||
return
|
||||
end
|
||||
prior_window_options[tostring(winid)] = {
|
||||
cursorline = vim.wo.cursorline,
|
||||
cursorlineopt = vim.wo.cursorlineopt,
|
||||
foldcolumn = vim.wo.foldcolumn,
|
||||
wrap = vim.wo.wrap,
|
||||
list = vim.wo.list,
|
||||
spell = vim.wo.spell,
|
||||
number = vim.wo.number,
|
||||
relativenumber = vim.wo.relativenumber,
|
||||
winhighlight = vim.wo.winhighlight,
|
||||
}
|
||||
end
|
||||
|
||||
--- Restore the window options for the current window
|
||||
--- @param winid number | nil The window id to restore the options for, defaults to current window
|
||||
local restore_local_window_settings = function(winid)
|
||||
winid = winid or vim.api.nvim_get_current_win()
|
||||
-- return local window settings to their prior values
|
||||
local wo = prior_window_options[tostring(winid)]
|
||||
if wo then
|
||||
vim.wo.cursorline = wo.cursorline
|
||||
vim.wo.cursorlineopt = wo.cursorlineopt
|
||||
vim.wo.foldcolumn = wo.foldcolumn
|
||||
vim.wo.wrap = wo.wrap
|
||||
vim.wo.list = wo.list
|
||||
vim.wo.spell = wo.spell
|
||||
vim.wo.number = wo.number
|
||||
vim.wo.relativenumber = wo.relativenumber
|
||||
vim.wo.winhighlight = wo.winhighlight
|
||||
log.debug("Window settings restored")
|
||||
vim.api.nvim_win_set_var(0, "neo_tree_settings_applied", false)
|
||||
else
|
||||
log.debug("No window settings to restore")
|
||||
end
|
||||
end
|
||||
|
||||
local last_buffer_enter_filetype = nil
|
||||
M.buffer_enter_event = function()
|
||||
-- if it is a neo-tree window, just set local options
|
||||
if vim.bo.filetype == "neo-tree" then
|
||||
if last_buffer_enter_filetype == "neo-tree" then
|
||||
-- we've switched to another neo-tree window
|
||||
events.fire_event(events.NEO_TREE_BUFFER_LEAVE)
|
||||
else
|
||||
store_local_window_settings()
|
||||
end
|
||||
vim.cmd([[
|
||||
setlocal cursorline
|
||||
setlocal cursorlineopt=line
|
||||
setlocal nowrap
|
||||
setlocal nolist nospell nonumber norelativenumber
|
||||
]])
|
||||
|
||||
local winhighlight =
|
||||
"Normal:NeoTreeNormal,NormalNC:NeoTreeNormalNC,SignColumn:NeoTreeSignColumn,CursorLine:NeoTreeCursorLine,FloatBorder:NeoTreeFloatBorder,StatusLine:NeoTreeStatusLine,StatusLineNC:NeoTreeStatusLineNC,VertSplit:NeoTreeVertSplit,EndOfBuffer:NeoTreeEndOfBuffer"
|
||||
if vim.version().minor >= 7 then
|
||||
vim.cmd("setlocal winhighlight=" .. winhighlight .. ",WinSeparator:NeoTreeWinSeparator")
|
||||
else
|
||||
vim.cmd("setlocal winhighlight=" .. winhighlight)
|
||||
end
|
||||
|
||||
events.fire_event(events.NEO_TREE_BUFFER_ENTER)
|
||||
last_buffer_enter_filetype = vim.bo.filetype
|
||||
vim.api.nvim_win_set_var(0, "neo_tree_settings_applied", true)
|
||||
return
|
||||
end
|
||||
|
||||
if vim.bo.filetype == "neo-tree-popup" then
|
||||
vim.cmd([[
|
||||
setlocal winhighlight=Normal:NeoTreeFloatNormal,FloatBorder:NeoTreeFloatBorder
|
||||
setlocal nolist nospell nonumber norelativenumber
|
||||
]])
|
||||
events.fire_event(events.NEO_TREE_POPUP_BUFFER_ENTER)
|
||||
last_buffer_enter_filetype = vim.bo.filetype
|
||||
return
|
||||
end
|
||||
|
||||
if last_buffer_enter_filetype == "neo-tree" then
|
||||
events.fire_event(events.NEO_TREE_BUFFER_LEAVE)
|
||||
end
|
||||
if last_buffer_enter_filetype == "neo-tree-popup" then
|
||||
events.fire_event(events.NEO_TREE_POPUP_BUFFER_LEAVE)
|
||||
end
|
||||
last_buffer_enter_filetype = vim.bo.filetype
|
||||
|
||||
-- there is nothing more we want to do with floating windows
|
||||
if utils.is_floating() then
|
||||
return
|
||||
end
|
||||
|
||||
-- if vim is trying to open a dir, then we hijack it
|
||||
if netrw.hijack() then
|
||||
return
|
||||
end
|
||||
|
||||
-- For all others, make sure another buffer is not hijacking our window
|
||||
-- ..but not if the position is "current"
|
||||
local prior_buf = vim.fn.bufnr("#")
|
||||
if prior_buf < 1 then
|
||||
return
|
||||
end
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local prior_type = vim.api.nvim_buf_get_option(prior_buf, "filetype")
|
||||
if prior_type == "neo-tree" then
|
||||
local success, position = pcall(vim.api.nvim_buf_get_var, prior_buf, "neo_tree_position")
|
||||
if not success then
|
||||
-- just bail out now, the rest of these lookups will probably fail too.
|
||||
return
|
||||
end
|
||||
|
||||
if position == "current" then
|
||||
-- nothing to do here, files are supposed to open in same window
|
||||
return
|
||||
end
|
||||
|
||||
local current_tabid = vim.api.nvim_get_current_tabpage()
|
||||
local neo_tree_tabid = vim.api.nvim_buf_get_var(prior_buf, "neo_tree_tabid")
|
||||
if neo_tree_tabid ~= current_tabid then
|
||||
-- This a new tab, so the alternate being neo-tree doesn't matter.
|
||||
return
|
||||
end
|
||||
local neo_tree_winid = vim.api.nvim_buf_get_var(prior_buf, "neo_tree_winid")
|
||||
local current_winid = vim.api.nvim_get_current_win()
|
||||
if neo_tree_winid ~= current_winid then
|
||||
-- This is not the neo-tree window, so the alternate being neo-tree doesn't matter.
|
||||
return
|
||||
end
|
||||
|
||||
local bufname = vim.api.nvim_buf_get_name(0)
|
||||
log.debug("redirecting buffer " .. bufname .. " to new split")
|
||||
vim.cmd("b#")
|
||||
-- Using schedule at this point fixes problem with syntax
|
||||
-- highlighting in the buffer. I also prevents errors with diagnostics
|
||||
-- trying to work with the buffer as it's being closed.
|
||||
vim.schedule(function()
|
||||
-- try to delete the buffer, only because if it was new it would take
|
||||
-- on options from the neo-tree window that are undesirable.
|
||||
pcall(vim.cmd, "bdelete " .. bufname)
|
||||
local fake_state = {
|
||||
window = {
|
||||
position = position,
|
||||
},
|
||||
}
|
||||
utils.open_file(fake_state, bufname)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
M.win_enter_event = function()
|
||||
local win_id = vim.api.nvim_get_current_win()
|
||||
if utils.is_floating(win_id) then
|
||||
return
|
||||
end
|
||||
|
||||
-- if the new win is not a floating window, make sure all neo-tree floats are closed
|
||||
manager.close_all("float")
|
||||
|
||||
if M.config.close_if_last_window then
|
||||
local tabid = vim.api.nvim_get_current_tabpage()
|
||||
local wins = utils.get_value(M, "config.prior_windows", {})[tabid]
|
||||
local prior_exists = utils.truthy(wins)
|
||||
local non_floating_wins = vim.tbl_filter(function(win)
|
||||
return not utils.is_floating(win)
|
||||
end, vim.api.nvim_tabpage_list_wins(tabid))
|
||||
local win_count = #non_floating_wins
|
||||
log.trace("checking if last window")
|
||||
log.trace("prior window exists = ", prior_exists)
|
||||
log.trace("win_count: ", win_count)
|
||||
if prior_exists and win_count == 1 and vim.o.filetype == "neo-tree" then
|
||||
local position = vim.api.nvim_buf_get_var(0, "neo_tree_position")
|
||||
local source = vim.api.nvim_buf_get_var(0, "neo_tree_source")
|
||||
if position ~= "current" then
|
||||
-- close_if_last_window just doesn't make sense for a split style
|
||||
log.trace("last window, closing")
|
||||
local state = require("neo-tree.sources.manager").get_state(source)
|
||||
if state == nil then
|
||||
return
|
||||
end
|
||||
local mod = utils.get_opened_buffers()
|
||||
log.debug("close_if_last_window, modified files found: ", vim.inspect(mod))
|
||||
for filename, buf_info in pairs(mod) do
|
||||
if buf_info.modified then
|
||||
local buf_name, message
|
||||
if vim.startswith(filename, "[No Name]#") then
|
||||
buf_name = string.sub(filename, 11)
|
||||
message = "Cannot close because an unnamed buffer is modified. Please save or discard this file."
|
||||
else
|
||||
buf_name = filename
|
||||
message = "Cannot close because one of the files is modified. Please save or discard changes."
|
||||
end
|
||||
log.trace("close_if_last_window, showing unnamed modified buffer: ", filename)
|
||||
vim.schedule(function()
|
||||
log.warn(message)
|
||||
vim.cmd("rightbelow vertical split")
|
||||
vim.api.nvim_win_set_width(win_id, state.window.width or 40)
|
||||
vim.cmd("b" .. buf_name)
|
||||
end)
|
||||
return
|
||||
end
|
||||
end
|
||||
vim.cmd("q!")
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if vim.o.filetype == "neo-tree" then
|
||||
local _, position = pcall(vim.api.nvim_buf_get_var, 0, "neo_tree_position")
|
||||
if position == "current" then
|
||||
-- make sure the buffer wasn't moved to a new window
|
||||
local neo_tree_winid = vim.api.nvim_buf_get_var(0, "neo_tree_winid")
|
||||
local current_winid = vim.api.nvim_get_current_win()
|
||||
local current_bufnr = vim.api.nvim_get_current_buf()
|
||||
if neo_tree_winid ~= current_winid then
|
||||
-- At this point we know that either the neo-tree window was split,
|
||||
-- or the neo-tree buffer is being shown in another window for some other reason.
|
||||
-- Sometime the split is just the first step in the process of opening somethig else,
|
||||
-- so instead of fixing this right away, we add a short delay and check back again to see
|
||||
-- if the buffer is still in this window.
|
||||
local old_state = manager.get_state("filesystem", nil, neo_tree_winid)
|
||||
vim.schedule(function()
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
if bufnr ~= current_bufnr then
|
||||
-- The neo-tree buffer was replaced with something else, so we don't need to do anything.
|
||||
log.trace("neo-tree buffer replaced with something else - no further action required")
|
||||
return
|
||||
end
|
||||
-- create a new tree for this window
|
||||
local state = manager.get_state("filesystem", nil, current_winid)
|
||||
state.path = old_state.path
|
||||
state.current_position = "current"
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
state.force_open_folders = renderer.get_expanded_nodes(old_state.tree)
|
||||
require("neo-tree.sources.filesystem")._navigate_internal(state, nil, nil, nil, false)
|
||||
end)
|
||||
return
|
||||
end
|
||||
end
|
||||
-- it's a neo-tree window, ignore
|
||||
return
|
||||
end
|
||||
|
||||
M.config.prior_windows = M.config.prior_windows or {}
|
||||
|
||||
local tabid = vim.api.nvim_get_current_tabpage()
|
||||
local tab_windows = M.config.prior_windows[tabid]
|
||||
if tab_windows == nil then
|
||||
tab_windows = {}
|
||||
M.config.prior_windows[tabid] = tab_windows
|
||||
end
|
||||
table.insert(tab_windows, win_id)
|
||||
|
||||
-- prune the history when it gets too big
|
||||
if #tab_windows > 100 then
|
||||
local new_array = {}
|
||||
local win_count = #tab_windows
|
||||
for i = 80, win_count do
|
||||
table.insert(new_array, tab_windows[i])
|
||||
end
|
||||
M.config.prior_windows[tabid] = new_array
|
||||
end
|
||||
end
|
||||
|
||||
M.set_log_level = function(level)
|
||||
log.set_level(level)
|
||||
end
|
||||
|
||||
local function merge_global_components_config(components, config)
|
||||
local indent_exists = false
|
||||
local merged_components = {}
|
||||
local do_merge
|
||||
|
||||
do_merge = function(component)
|
||||
local name = component[1]
|
||||
if type(name) == "string" then
|
||||
if name == "indent" then
|
||||
indent_exists = true
|
||||
end
|
||||
local merged = { name }
|
||||
local global_config = config.default_component_configs[name]
|
||||
if global_config then
|
||||
for k, v in pairs(global_config) do
|
||||
merged[k] = v
|
||||
end
|
||||
end
|
||||
for k, v in pairs(component) do
|
||||
merged[k] = v
|
||||
end
|
||||
if name == "container" then
|
||||
for i, child in ipairs(component.content) do
|
||||
merged.content[i] = do_merge(child)
|
||||
end
|
||||
end
|
||||
return merged
|
||||
else
|
||||
log.error("component name is the wrong type", component)
|
||||
end
|
||||
end
|
||||
|
||||
for _, component in ipairs(components) do
|
||||
local merged = do_merge(component)
|
||||
table.insert(merged_components, merged)
|
||||
end
|
||||
|
||||
-- If the indent component is not specified, then add it.
|
||||
-- We do this because it used to be implicitly added, so we don't want to
|
||||
-- break any existing configs.
|
||||
if not indent_exists then
|
||||
local indent = { "indent" }
|
||||
for k, v in pairs(config.default_component_configs.indent or {}) do
|
||||
indent[k] = v
|
||||
end
|
||||
table.insert(merged_components, 1, indent)
|
||||
end
|
||||
return merged_components
|
||||
end
|
||||
|
||||
local merge_renderers = function(default_config, source_default_config, user_config)
|
||||
-- This can't be a deep copy/merge. If a renderer is specified in the target it completely
|
||||
-- replaces the base renderer.
|
||||
|
||||
if source_default_config == nil then
|
||||
-- first override the default config global renderer with the user's global renderers
|
||||
for name, renderer in pairs(user_config.renderers or {}) do
|
||||
log.debug("overriding global renderer for " .. name)
|
||||
default_config.renderers[name] = renderer
|
||||
end
|
||||
else
|
||||
-- then override the global renderers with the source specific renderers
|
||||
source_default_config.renderers = source_default_config.renderers or {}
|
||||
for name, renderer in pairs(default_config.renderers or {}) do
|
||||
if source_default_config.renderers[name] == nil then
|
||||
log.debug("overriding source renderer for " .. name)
|
||||
local r = {}
|
||||
-- Only copy components that exist in the target source.
|
||||
-- This alllows us to specify global renderers that include components from all sources,
|
||||
-- even if some of those components are not universal
|
||||
for _, value in ipairs(renderer) do
|
||||
if value[1] and source_default_config.components[value[1]] ~= nil then
|
||||
table.insert(r, value)
|
||||
end
|
||||
end
|
||||
source_default_config.renderers[name] = r
|
||||
end
|
||||
end
|
||||
|
||||
-- if user sets renderers, completely wipe the default ones
|
||||
local source_name = source_default_config.name
|
||||
for name, _ in pairs(source_default_config.renderers) do
|
||||
local user = utils.get_value(user_config, source_name .. ".renderers." .. name)
|
||||
if user then
|
||||
source_default_config.renderers[name] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.merge_config = function(user_config, is_auto_config)
|
||||
local default_config = vim.deepcopy(defaults)
|
||||
user_config = vim.deepcopy(user_config or {})
|
||||
|
||||
local migrations = require("neo-tree.setup.deprecations").migrate(user_config)
|
||||
if #migrations > 0 then
|
||||
-- defer to make sure it is the last message printed
|
||||
vim.defer_fn(function()
|
||||
vim.cmd(
|
||||
"echohl WarningMsg | echo 'Some options have changed, please run `:Neotree migrations` to see the changes' | echohl NONE"
|
||||
)
|
||||
end, 50)
|
||||
end
|
||||
|
||||
if user_config.log_level ~= nil then
|
||||
M.set_log_level(user_config.log_level)
|
||||
end
|
||||
log.use_file(user_config.log_to_file, true)
|
||||
log.debug("setup")
|
||||
|
||||
events.clear_all_events()
|
||||
define_events()
|
||||
|
||||
-- Prevent accidentally opening another file in the neo-tree window.
|
||||
events.subscribe({
|
||||
event = events.VIM_BUFFER_ENTER,
|
||||
handler = M.buffer_enter_event,
|
||||
})
|
||||
|
||||
-- Setup autocmd for neo-tree BufLeave, to restore window settings.
|
||||
-- This is set to happen just before leaving the window.
|
||||
-- The patterns used should ensure it only runs in neo-tree windows where position = "current"
|
||||
local augroup = vim.api.nvim_create_augroup("NeoTree_BufLeave", { clear = true })
|
||||
local bufleave = function(data)
|
||||
-- Vim patterns in autocmds are not quite precise enough
|
||||
-- so we are doing a second stage filter in lua
|
||||
local pattern = "neo%-tree [^ ]+ %[1%d%d%d%]"
|
||||
if string.match(data.file, pattern) then
|
||||
restore_local_window_settings()
|
||||
end
|
||||
end
|
||||
vim.api.nvim_create_autocmd({ "BufWinLeave" }, {
|
||||
group = augroup,
|
||||
pattern = "neo-tree *",
|
||||
callback = bufleave,
|
||||
})
|
||||
|
||||
if user_config.event_handlers ~= nil then
|
||||
for _, handler in ipairs(user_config.event_handlers) do
|
||||
events.subscribe(handler)
|
||||
end
|
||||
end
|
||||
|
||||
highlights.setup()
|
||||
|
||||
-- used to either limit the sources that or loaded, or add extra external sources
|
||||
local all_sources = {}
|
||||
local all_source_names = {}
|
||||
for _, source in ipairs(user_config.sources or default_config.sources) do
|
||||
local parts = utils.split(source, ".")
|
||||
local name = parts[#parts]
|
||||
local is_internal_ns, is_external_ns = false, false
|
||||
local module
|
||||
|
||||
if #parts == 1 then
|
||||
-- might be a module name in the internal namespace
|
||||
is_internal_ns, module = pcall(require, "neo-tree.sources." .. source)
|
||||
end
|
||||
if is_internal_ns then
|
||||
name = module.name or name
|
||||
all_sources[name] = "neo-tree.sources." .. name
|
||||
else
|
||||
-- fully qualified module name
|
||||
-- or just a root level module name
|
||||
is_external_ns, module = pcall(require, source)
|
||||
if is_external_ns then
|
||||
name = module.name or name
|
||||
all_sources[name] = source
|
||||
else
|
||||
log.error("Source module not found", source)
|
||||
name = nil
|
||||
end
|
||||
end
|
||||
if name then
|
||||
default_config[name] = module.default_config or default_config[name]
|
||||
table.insert(all_source_names, name)
|
||||
end
|
||||
end
|
||||
log.debug("Sources to load: ", vim.inspect(all_sources))
|
||||
require("neo-tree.command.parser").setup(all_source_names)
|
||||
|
||||
-- setup the default values for all sources
|
||||
normalize_mappings(default_config)
|
||||
normalize_mappings(user_config)
|
||||
merge_renderers(default_config, nil, user_config)
|
||||
|
||||
for source_name, mod_root in pairs(all_sources) do
|
||||
local module = require(mod_root)
|
||||
default_config[source_name] = default_config[source_name]
|
||||
or {
|
||||
renderers = {},
|
||||
components = {},
|
||||
}
|
||||
local source_default_config = default_config[source_name]
|
||||
source_default_config.components = module.components or require(mod_root .. ".components")
|
||||
source_default_config.commands = module.commands or require(mod_root .. ".commands")
|
||||
source_default_config.name = source_name
|
||||
source_default_config.display_name = module.display_name or source_default_config.name
|
||||
|
||||
if user_config.use_default_mappings == false then
|
||||
default_config.window.mappings = {}
|
||||
source_default_config.window.mappings = {}
|
||||
end
|
||||
-- Make sure all the mappings are normalized so they will merge properly.
|
||||
normalize_mappings(source_default_config)
|
||||
normalize_mappings(user_config[source_name])
|
||||
-- merge the global config with the source specific config
|
||||
source_default_config.window = vim.tbl_deep_extend(
|
||||
"force",
|
||||
default_config.window or {},
|
||||
source_default_config.window or {},
|
||||
user_config.window or {}
|
||||
)
|
||||
|
||||
merge_renderers(default_config, source_default_config, user_config)
|
||||
|
||||
--validate the window.position
|
||||
local pos_key = source_name .. ".window.position"
|
||||
local position = utils.get_value(user_config, pos_key, "left", true)
|
||||
local valid_positions = {
|
||||
left = true,
|
||||
right = true,
|
||||
top = true,
|
||||
bottom = true,
|
||||
float = true,
|
||||
current = true,
|
||||
}
|
||||
if not valid_positions[position] then
|
||||
log.error("Invalid value for ", pos_key, ": ", position)
|
||||
user_config[source_name].window.position = "left"
|
||||
end
|
||||
end
|
||||
--print(vim.inspect(default_config.filesystem))
|
||||
|
||||
-- Moving user_config.sources to user_config.orig_sources
|
||||
user_config.orig_sources = user_config.sources and user_config.sources or {}
|
||||
|
||||
-- apply the users config
|
||||
M.config = vim.tbl_deep_extend("force", default_config, user_config)
|
||||
|
||||
-- RE: 873, fixes issue with invalid source checking by overriding
|
||||
-- source table with name table
|
||||
-- Setting new "sources" to be the parsed names of the sources
|
||||
M.config.sources = all_source_names
|
||||
|
||||
if ( M.config.source_selector.winbar or M.config.source_selector.statusline )
|
||||
and M.config.source_selector.sources
|
||||
and not user_config.default_source then
|
||||
-- Set the default source to the head of these
|
||||
-- This resolves some weirdness with the source selector having
|
||||
-- a different "head" item than our current default.
|
||||
-- Removing this line makes Neo-tree show the "filesystem"
|
||||
-- source instead of whatever the first item in the config is.
|
||||
-- Probably don't remove this unless you have a better fix for that
|
||||
M.config.default_source = M.config.source_selector.sources[1].source
|
||||
end
|
||||
-- Check if the default source is not included in config.sources
|
||||
-- log a warning and then "pick" the first in the sources list
|
||||
local match = false
|
||||
for _, source in ipairs(M.config.sources) do
|
||||
if source == M.config.default_source then
|
||||
match = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not match then
|
||||
M.config.default_source = M.config.sources[1]
|
||||
log.warn(string.format("Invalid default source found in configuration. Using first available source: %s", M.config.default_source))
|
||||
end
|
||||
|
||||
if not M.config.enable_git_status then
|
||||
M.config.git_status_async = false
|
||||
end
|
||||
|
||||
-- Validate that the source_selector.sources are all available and if any
|
||||
-- aren't, remove them
|
||||
local source_selector_sources = {}
|
||||
for _, ss_source in ipairs(M.config.source_selector.sources or {}) do
|
||||
local source_match = false
|
||||
for _, source in ipairs(M.config.sources) do
|
||||
if ss_source.source == source then
|
||||
source_match = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if source_match then
|
||||
table.insert(source_selector_sources, ss_source)
|
||||
else
|
||||
log.debug(string.format("Unable to locate Neo-tree extension %s", ss_source.source))
|
||||
end
|
||||
end
|
||||
M.config.source_selector.sources = source_selector_sources
|
||||
|
||||
file_nesting.setup(M.config.nesting_rules)
|
||||
|
||||
for source_name, mod_root in pairs(all_sources) do
|
||||
for name, rndr in pairs(M.config[source_name].renderers) do
|
||||
M.config[source_name].renderers[name] = merge_global_components_config(rndr, M.config)
|
||||
end
|
||||
local module = require(mod_root)
|
||||
if M.config.commands then
|
||||
M.config[source_name].commands =
|
||||
vim.tbl_extend("keep", M.config[source_name].commands or {}, M.config.commands)
|
||||
end
|
||||
manager.setup(source_name, M.config[source_name], M.config, module)
|
||||
manager.redraw(source_name)
|
||||
end
|
||||
|
||||
if M.config.auto_clean_after_session_restore then
|
||||
require("neo-tree.ui.renderer").clean_invalid_neotree_buffers(false)
|
||||
events.subscribe({
|
||||
event = events.VIM_AFTER_SESSION_LOAD,
|
||||
handler = function()
|
||||
require("neo-tree.ui.renderer").clean_invalid_neotree_buffers(true)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
events.subscribe({
|
||||
event = events.VIM_COLORSCHEME,
|
||||
handler = highlights.setup,
|
||||
id = "neo-tree-highlight",
|
||||
})
|
||||
|
||||
events.subscribe({
|
||||
event = events.VIM_WIN_ENTER,
|
||||
handler = M.win_enter_event,
|
||||
id = "neo-tree-win-enter",
|
||||
})
|
||||
|
||||
--Dispose ourselves if the tab closes
|
||||
events.subscribe({
|
||||
event = events.VIM_TAB_CLOSED,
|
||||
handler = function(args)
|
||||
local tabnr = tonumber(args.afile)
|
||||
log.debug("VIM_TAB_CLOSED: disposing state for tabnr", tabnr)
|
||||
-- Internally we use tabids to track state but <afile> is tabnr of a tab that has already been
|
||||
-- closed so there is no way to get its tabid. Instead dispose all tabs that are no longer valid.
|
||||
-- Must be scheduled because nvim_tabpage_is_valid does not work inside TabClosed event callback.
|
||||
vim.schedule_wrap(manager.dispose_invalid_tabs)()
|
||||
end,
|
||||
})
|
||||
|
||||
--Dispose ourselves if the tab closes
|
||||
events.subscribe({
|
||||
event = events.VIM_WIN_CLOSED,
|
||||
handler = function(args)
|
||||
local winid = tonumber(args.afile)
|
||||
log.debug("VIM_WIN_CLOSED: disposing state for window", winid)
|
||||
manager.dispose_window(winid)
|
||||
end,
|
||||
})
|
||||
|
||||
local rt = utils.get_value(M.config, "resize_timer_interval", 50, true)
|
||||
require("neo-tree.ui.renderer").resize_timer_interval = rt
|
||||
|
||||
return M.config
|
||||
end
|
||||
|
||||
return M
|
62
bundle/neo-tree.nvim/lua/neo-tree/setup/mapping-helper.lua
vendored
Normal file
62
bundle/neo-tree.nvim/lua/neo-tree/setup/mapping-helper.lua
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.normalize_map_key = function(key)
|
||||
if key == nil then
|
||||
return nil
|
||||
end
|
||||
if key:match("^<[^>]+>$") then
|
||||
local parts = utils.split(key, "-")
|
||||
if #parts == 2 then
|
||||
local mod = parts[1]:lower()
|
||||
if mod == "<a" then
|
||||
mod = "<m"
|
||||
end
|
||||
local alpha = parts[2]
|
||||
if #alpha > 2 then
|
||||
alpha = alpha:lower()
|
||||
end
|
||||
key = string.format("%s-%s", mod, alpha)
|
||||
return key
|
||||
else
|
||||
key = key:lower()
|
||||
if key == "<backspace>" then
|
||||
return "<bs>"
|
||||
elseif key == "<enter>" then
|
||||
return "<cr>"
|
||||
elseif key == "<return>" then
|
||||
return "<cr>"
|
||||
end
|
||||
end
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
M.normalize_map = function(map)
|
||||
local new_map = {}
|
||||
for key, value in pairs(map) do
|
||||
local normalized_key = M.normalize_map_key(key)
|
||||
if normalized_key ~= nil then
|
||||
new_map[normalized_key] = value
|
||||
end
|
||||
end
|
||||
return new_map
|
||||
end
|
||||
|
||||
local tests = {
|
||||
{ "<BS>", "<bs>" },
|
||||
{ "<Backspace>", "<bs>" },
|
||||
{ "<Enter>", "<cr>" },
|
||||
{ "<C-W>", "<c-W>" },
|
||||
{ "<A-q>", "<m-q>" },
|
||||
{ "<C-Left>", "<c-left>" },
|
||||
{ "<C-Right>", "<c-right>" },
|
||||
{ "<C-Up>", "<c-up>" },
|
||||
}
|
||||
for _, test in ipairs(tests) do
|
||||
local key = M.normalize_map_key(test[1])
|
||||
assert(key == test[2], string.format("%s != %s", key, test[2]))
|
||||
end
|
||||
|
||||
return M
|
103
bundle/neo-tree.nvim/lua/neo-tree/setup/netrw.lua
vendored
Normal file
103
bundle/neo-tree.nvim/lua/neo-tree/setup/netrw.lua
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local command = require("neo-tree.command")
|
||||
local M = {}
|
||||
|
||||
local get_position = function(source_name)
|
||||
local nt = require("neo-tree")
|
||||
local pos = utils.get_value(nt.config, source_name .. ".window.position", "left", true)
|
||||
return pos
|
||||
end
|
||||
|
||||
M.get_hijack_netrw_behavior = function()
|
||||
local nt = require("neo-tree")
|
||||
local option = "filesystem.hijack_netrw_behavior"
|
||||
local hijack_behavior = utils.get_value(nt.config, option, "open_default", true)
|
||||
if hijack_behavior == "disabled" then
|
||||
return hijack_behavior
|
||||
elseif hijack_behavior == "open_default" then
|
||||
return hijack_behavior
|
||||
elseif hijack_behavior == "open_current" then
|
||||
return hijack_behavior
|
||||
else
|
||||
log.error("Invalid value for " .. option .. ": " .. hijack_behavior)
|
||||
return "disabled"
|
||||
end
|
||||
end
|
||||
|
||||
M.hijack = function()
|
||||
local hijack_behavior = M.get_hijack_netrw_behavior()
|
||||
if hijack_behavior == "disabled" then
|
||||
return false
|
||||
end
|
||||
|
||||
-- ensure this is a directory
|
||||
local bufname = vim.api.nvim_buf_get_name(0)
|
||||
local stats = vim.loop.fs_stat(bufname)
|
||||
if not stats then
|
||||
return false
|
||||
end
|
||||
if stats.type ~= "directory" then
|
||||
return false
|
||||
end
|
||||
|
||||
-- record where we are now
|
||||
local pos = get_position("filesystem")
|
||||
local should_open_current = hijack_behavior == "open_current" or pos == "current"
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local dir_bufnr = vim.api.nvim_get_current_buf()
|
||||
|
||||
-- Now actually open the tree, with a very quick debounce because this may be
|
||||
-- called multiple times in quick succession.
|
||||
utils.debounce("hijack_netrw_" .. winid, function()
|
||||
-- We will want to replace the "directory" buffer with either the "alternate"
|
||||
-- buffer or a new blank one.
|
||||
local replace_with_bufnr = vim.fn.bufnr("#")
|
||||
local is_currently_neo_tree = false
|
||||
if replace_with_bufnr > 0 then
|
||||
if vim.api.nvim_buf_get_option(replace_with_bufnr, "filetype") == "neo-tree" then
|
||||
-- don't hijack the current window if it's already a Neo-tree sidebar
|
||||
local _, position = pcall(vim.api.nvim_buf_get_var, replace_with_bufnr, "neo_tree_position")
|
||||
if position ~= "current" then
|
||||
is_currently_neo_tree = true
|
||||
else
|
||||
replace_with_bufnr = -1
|
||||
end
|
||||
end
|
||||
end
|
||||
if not should_open_current then
|
||||
if replace_with_bufnr == dir_bufnr or replace_with_bufnr < 1 then
|
||||
replace_with_bufnr = vim.api.nvim_create_buf(true, false)
|
||||
log.trace("Created new buffer for netrw hijack", replace_with_bufnr)
|
||||
end
|
||||
end
|
||||
if replace_with_bufnr > 0 then
|
||||
log.trace("Replacing buffer in netrw hijack", replace_with_bufnr)
|
||||
pcall(vim.api.nvim_win_set_buf, winid, replace_with_bufnr)
|
||||
end
|
||||
local remove_dir_buf = vim.schedule_wrap(function()
|
||||
log.trace("Deleting buffer in netrw hijack", dir_bufnr)
|
||||
pcall(vim.api.nvim_buf_delete, dir_bufnr, { force = true })
|
||||
end)
|
||||
|
||||
local state
|
||||
if should_open_current and not is_currently_neo_tree then
|
||||
log.debug("hijack_netrw: opening current")
|
||||
state = manager.get_state("filesystem", nil, winid)
|
||||
state.current_position = "current"
|
||||
elseif is_currently_neo_tree then
|
||||
log.debug("hijack_netrw: opening in existing Neo-tree")
|
||||
state = manager.get_state("filesystem")
|
||||
else
|
||||
log.debug("hijack_netrw: opening default")
|
||||
manager.close_all_except("filesystem")
|
||||
state = manager.get_state("filesystem")
|
||||
end
|
||||
require("neo-tree.sources.filesystem")._navigate_internal(state, bufname, nil, remove_dir_buf)
|
||||
end, 10, utils.debounce_strategy.CALL_LAST_ONLY)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return M
|
92
bundle/neo-tree.nvim/lua/neo-tree/sources/buffers/commands.lua
vendored
Normal file
92
bundle/neo-tree.nvim/lua/neo-tree/sources/buffers/commands.lua
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
--This file should contain all commands meant to be used by mappings.
|
||||
|
||||
local vim = vim
|
||||
local cc = require("neo-tree.sources.common.commands")
|
||||
local buffers = require("neo-tree.sources.buffers")
|
||||
local utils = require("neo-tree.utils")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
|
||||
local M = {}
|
||||
|
||||
local refresh = utils.wrap(manager.refresh, "buffers")
|
||||
local redraw = utils.wrap(manager.redraw, "buffers")
|
||||
|
||||
M.add = function(state)
|
||||
cc.add(state, refresh)
|
||||
end
|
||||
|
||||
M.add_directory = function(state)
|
||||
cc.add_directory(state, refresh)
|
||||
end
|
||||
|
||||
M.buffer_delete = function(state)
|
||||
local node = state.tree:get_node()
|
||||
if node then
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
vim.api.nvim_buf_delete(node.extra.bufnr, { force = false, unload = false })
|
||||
refresh()
|
||||
end
|
||||
end
|
||||
|
||||
---Marks node as copied, so that it can be pasted somewhere else.
|
||||
M.copy_to_clipboard = function(state)
|
||||
cc.copy_to_clipboard(state, redraw)
|
||||
end
|
||||
|
||||
M.copy_to_clipboard_visual = function(state, selected_nodes)
|
||||
cc.copy_to_clipboard_visual(state, selected_nodes, redraw)
|
||||
end
|
||||
|
||||
---Marks node as cut, so that it can be pasted (moved) somewhere else.
|
||||
M.cut_to_clipboard = function(state)
|
||||
cc.cut_to_clipboard(state, redraw)
|
||||
end
|
||||
|
||||
M.cut_to_clipboard_visual = function(state, selected_nodes)
|
||||
cc.cut_to_clipboard_visual(state, selected_nodes, redraw)
|
||||
end
|
||||
|
||||
M.copy = function(state)
|
||||
cc.copy(state, redraw)
|
||||
end
|
||||
|
||||
M.move = function(state)
|
||||
cc.move(state, redraw)
|
||||
end
|
||||
|
||||
M.show_debug_info = cc.show_debug_info
|
||||
|
||||
---Pastes all items from the clipboard to the current directory.
|
||||
M.paste_from_clipboard = function(state)
|
||||
cc.paste_from_clipboard(state, refresh)
|
||||
end
|
||||
|
||||
M.delete = function(state)
|
||||
cc.delete(state, refresh)
|
||||
end
|
||||
|
||||
---Navigate up one level.
|
||||
M.navigate_up = function(state)
|
||||
local parent_path, _ = utils.split_path(state.path)
|
||||
buffers.navigate(state, parent_path)
|
||||
end
|
||||
|
||||
M.refresh = refresh
|
||||
|
||||
M.rename = function(state)
|
||||
cc.rename(state, refresh)
|
||||
end
|
||||
|
||||
M.set_root = function(state)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
if node.type == "directory" then
|
||||
buffers.navigate(state, node.id)
|
||||
end
|
||||
end
|
||||
|
||||
cc._add_common_commands(M)
|
||||
|
||||
return M
|
48
bundle/neo-tree.nvim/lua/neo-tree/sources/buffers/components.lua
vendored
Normal file
48
bundle/neo-tree.nvim/lua/neo-tree/sources/buffers/components.lua
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
-- This file contains the built-in components. Each componment is a function
|
||||
-- that takes the following arguments:
|
||||
-- config: A table containing the configuration provided by the user
|
||||
-- when declaring this component in their renderer config.
|
||||
-- node: A NuiNode object for the currently focused node.
|
||||
-- state: The current state of the source providing the items.
|
||||
--
|
||||
-- The function should return either a table, or a list of tables, each of which
|
||||
-- contains the following keys:
|
||||
-- text: The text to display for this item.
|
||||
-- highlight: The highlight group to apply to this text.
|
||||
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local common = require("neo-tree.sources.common.components")
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.name = function(config, node, state)
|
||||
local highlight = config.highlight or highlights.FILE_NAME_OPENED
|
||||
local name = node.name
|
||||
if node.type == "directory" then
|
||||
if node:get_depth() == 1 then
|
||||
highlight = highlights.ROOT_NAME
|
||||
name = "OPEN BUFFERS in " .. name
|
||||
else
|
||||
highlight = highlights.DIRECTORY_NAME
|
||||
end
|
||||
elseif node.type == "terminal" then
|
||||
if node:get_depth() == 1 then
|
||||
highlight = highlights.ROOT_NAME
|
||||
name = "TERMINALS"
|
||||
else
|
||||
highlight = highlights.FILE_NAME
|
||||
end
|
||||
elseif config.use_git_status_colors then
|
||||
local git_status = state.components.git_status({}, node, state)
|
||||
if git_status and git_status.highlight then
|
||||
highlight = git_status.highlight
|
||||
end
|
||||
end
|
||||
return {
|
||||
text = name,
|
||||
highlight = highlight,
|
||||
}
|
||||
end
|
||||
|
||||
return vim.tbl_deep_extend("force", common, M)
|
191
bundle/neo-tree.nvim/lua/neo-tree/sources/buffers/init.lua
vendored
Normal file
191
bundle/neo-tree.nvim/lua/neo-tree/sources/buffers/init.lua
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
--This file should have all functions that are in the public api and either set
|
||||
--or read the state of this source.
|
||||
|
||||
local vim = vim
|
||||
local utils = require("neo-tree.utils")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local items = require("neo-tree.sources.buffers.lib.items")
|
||||
local events = require("neo-tree.events")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local git = require("neo-tree.git")
|
||||
|
||||
local M = {
|
||||
name = "buffers",
|
||||
display_name = " Buffers "
|
||||
}
|
||||
|
||||
local wrap = function(func)
|
||||
return utils.wrap(func, M.name)
|
||||
end
|
||||
|
||||
local get_state = function()
|
||||
return manager.get_state(M.name)
|
||||
end
|
||||
|
||||
local follow_internal = function()
|
||||
if vim.bo.filetype == "neo-tree" or vim.bo.filetype == "neo-tree-popup" then
|
||||
return
|
||||
end
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local path_to_reveal = manager.get_path_to_reveal(true) or tostring(bufnr)
|
||||
|
||||
local state = get_state()
|
||||
if state.current_position == "float" then
|
||||
return false
|
||||
end
|
||||
if not state.path then
|
||||
return false
|
||||
end
|
||||
local window_exists = renderer.window_exists(state)
|
||||
if window_exists then
|
||||
local node = state.tree and state.tree:get_node()
|
||||
if node then
|
||||
if node:get_id() == path_to_reveal then
|
||||
-- already focused
|
||||
return false
|
||||
end
|
||||
end
|
||||
renderer.focus_node(state, path_to_reveal, true)
|
||||
end
|
||||
end
|
||||
|
||||
M.follow = function()
|
||||
if vim.fn.bufname(0) == "COMMIT_EDITMSG" then
|
||||
return false
|
||||
end
|
||||
utils.debounce("neo-tree-buffer-follow", function()
|
||||
return follow_internal()
|
||||
end, 100, utils.debounce_strategy.CALL_LAST_ONLY)
|
||||
end
|
||||
|
||||
local buffers_changed_internal = function()
|
||||
for _, tabid in ipairs(vim.api.nvim_list_tabpages()) do
|
||||
local state = manager.get_state(M.name, tabid)
|
||||
if state.path and renderer.window_exists(state) then
|
||||
items.get_opened_buffers(state)
|
||||
if state.follow_current_file then
|
||||
follow_internal()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Calld by autocmd when any buffer is open, closed, renamed, etc.
|
||||
M.buffers_changed = function()
|
||||
utils.debounce(
|
||||
"buffers_changed",
|
||||
buffers_changed_internal,
|
||||
100,
|
||||
utils.debounce_strategy.CALL_LAST_ONLY
|
||||
)
|
||||
end
|
||||
|
||||
---Navigate to the given path.
|
||||
---@param path string Path to navigate to. If empty, will navigate to the cwd.
|
||||
M.navigate = function(state, path, path_to_reveal)
|
||||
state.dirty = false
|
||||
local path_changed = false
|
||||
if path == nil then
|
||||
path = vim.fn.getcwd()
|
||||
end
|
||||
if path ~= state.path then
|
||||
state.path = path
|
||||
path_changed = true
|
||||
end
|
||||
if path_to_reveal then
|
||||
renderer.position.set(state, path_to_reveal)
|
||||
end
|
||||
|
||||
items.get_opened_buffers(state)
|
||||
|
||||
if path_changed and state.bind_to_cwd then
|
||||
vim.api.nvim_command("tcd " .. path)
|
||||
end
|
||||
end
|
||||
|
||||
---Configures the plugin, should be called before the plugin is used.
|
||||
---@param config table Configuration table containing any keys that the user
|
||||
--wants to change from the defaults. May be empty to accept default values.
|
||||
M.setup = function(config, global_config)
|
||||
--Configure events for before_render
|
||||
if config.before_render then
|
||||
--convert to new event system
|
||||
manager.subscribe(M.name, {
|
||||
event = events.BEFORE_RENDER,
|
||||
handler = function(state)
|
||||
local this_state = get_state()
|
||||
if state == this_state then
|
||||
config.before_render(this_state)
|
||||
end
|
||||
end,
|
||||
})
|
||||
elseif global_config.enable_git_status then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.BEFORE_RENDER,
|
||||
handler = function(state)
|
||||
local this_state = get_state()
|
||||
if state == this_state then
|
||||
state.git_status_lookup = git.status(state.git_base)
|
||||
end
|
||||
end,
|
||||
})
|
||||
manager.subscribe(M.name, {
|
||||
event = events.GIT_EVENT,
|
||||
handler = M.buffers_changed,
|
||||
})
|
||||
end
|
||||
|
||||
local refresh_events = {
|
||||
events.VIM_BUFFER_ADDED,
|
||||
events.VIM_BUFFER_DELETED,
|
||||
}
|
||||
if global_config.enable_refresh_on_write then
|
||||
table.insert(refresh_events, events.VIM_BUFFER_CHANGED)
|
||||
end
|
||||
for _, e in ipairs(refresh_events) do
|
||||
manager.subscribe(M.name, {
|
||||
event = e,
|
||||
handler = function(args)
|
||||
if args.afile == "" or utils.is_real_file(args.afile) then
|
||||
M.buffers_changed()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
if config.bind_to_cwd then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_DIR_CHANGED,
|
||||
handler = wrap(manager.dir_changed),
|
||||
})
|
||||
end
|
||||
|
||||
if global_config.enable_diagnostics then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_DIAGNOSTIC_CHANGED,
|
||||
handler = wrap(manager.diagnostics_changed),
|
||||
})
|
||||
end
|
||||
|
||||
--Configure event handlers for modified files
|
||||
if global_config.enable_modified_markers then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_BUFFER_MODIFIED_SET,
|
||||
handler = wrap(manager.opened_buffers_changed),
|
||||
})
|
||||
end
|
||||
|
||||
-- Configure event handler for follow_current_file option
|
||||
if config.follow_current_file then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_BUFFER_ENTER,
|
||||
handler = M.follow,
|
||||
})
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_TERMINAL_ENTER,
|
||||
handler = M.follow,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
108
bundle/neo-tree.nvim/lua/neo-tree/sources/buffers/lib/items.lua
vendored
Normal file
108
bundle/neo-tree.nvim/lua/neo-tree/sources/buffers/lib/items.lua
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
local vim = vim
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local utils = require("neo-tree.utils")
|
||||
local file_items = require("neo-tree.sources.common.file-items")
|
||||
local log = require("neo-tree.log")
|
||||
|
||||
local M = {}
|
||||
|
||||
---Get a table of all open buffers, along with all parent paths of those buffers.
|
||||
---The paths are the keys of the table, and all the values are 'true'.
|
||||
M.get_opened_buffers = function(state)
|
||||
if state.loading then
|
||||
return
|
||||
end
|
||||
state.loading = true
|
||||
local context = file_items.create_context()
|
||||
context.state = state
|
||||
-- Create root folder
|
||||
local root = file_items.create_item(context, state.path, "directory")
|
||||
root.name = vim.fn.fnamemodify(root.path, ":~")
|
||||
root.loaded = true
|
||||
root.search_pattern = state.search_pattern
|
||||
context.folders[root.path] = root
|
||||
local terminals = {}
|
||||
|
||||
local function add_buffer(bufnr, path)
|
||||
local is_loaded = vim.api.nvim_buf_is_loaded(bufnr)
|
||||
if is_loaded or state.show_unloaded then
|
||||
local is_listed = vim.fn.buflisted(bufnr)
|
||||
if is_listed == 1 then
|
||||
if path == "" then
|
||||
path = "[No Name]"
|
||||
end
|
||||
local success, item = pcall(file_items.create_item, context, path, "file", bufnr)
|
||||
if success then
|
||||
item.extra = {
|
||||
bufnr = bufnr,
|
||||
is_listed = is_listed,
|
||||
}
|
||||
else
|
||||
log.error("Error creating item for " .. path .. ": " .. item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local bufs = vim.api.nvim_list_bufs()
|
||||
for _, b in ipairs(bufs) do
|
||||
local path = vim.api.nvim_buf_get_name(b)
|
||||
if vim.startswith(path, "term://") then
|
||||
local name = path:match("term://(.*)//.*")
|
||||
local abs_path = vim.fn.fnamemodify(name, ":p")
|
||||
local has_title, title = pcall(vim.api.nvim_buf_get_var, b, "term_title")
|
||||
local item = {
|
||||
name = has_title and title or name,
|
||||
ext = "terminal",
|
||||
path = abs_path,
|
||||
id = path,
|
||||
type = "terminal",
|
||||
loaded = true,
|
||||
extra = {
|
||||
bufnr = b,
|
||||
is_listed = true,
|
||||
},
|
||||
}
|
||||
if utils.is_subpath(state.path, abs_path) then
|
||||
table.insert(terminals, item)
|
||||
end
|
||||
elseif path == "" then
|
||||
add_buffer(b, path)
|
||||
else
|
||||
if #state.path > 1 then
|
||||
local rootsub = path:sub(1, #state.path)
|
||||
-- make sure this is within the root path
|
||||
if rootsub == state.path then
|
||||
add_buffer(b, path)
|
||||
end
|
||||
else
|
||||
add_buffer(b, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local root_folders = { root }
|
||||
|
||||
if #terminals > 0 then
|
||||
local terminal_root = {
|
||||
name = "Terminals",
|
||||
id = "Terminals",
|
||||
ext = "terminal",
|
||||
type = "terminal",
|
||||
children = terminals,
|
||||
loaded = true,
|
||||
search_pattern = state.search_pattern,
|
||||
}
|
||||
context.folders["Terminals"] = terminal_root
|
||||
root_folders[2] = terminal_root
|
||||
end
|
||||
state.default_expanded_nodes = {}
|
||||
for id, _ in pairs(context.folders) do
|
||||
table.insert(state.default_expanded_nodes, id)
|
||||
end
|
||||
file_items.deep_sort(root.children)
|
||||
renderer.show_nodes(root_folders, state)
|
||||
state.loading = false
|
||||
end
|
||||
|
||||
return M
|
748
bundle/neo-tree.nvim/lua/neo-tree/sources/common/commands.lua
vendored
Normal file
748
bundle/neo-tree.nvim/lua/neo-tree/sources/common/commands.lua
vendored
Normal file
@ -0,0 +1,748 @@
|
||||
--This file should contain all commands meant to be used by mappings.
|
||||
|
||||
local vim = vim
|
||||
local fs_actions = require("neo-tree.sources.filesystem.lib.fs_actions")
|
||||
local utils = require("neo-tree.utils")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local events = require("neo-tree.events")
|
||||
local inputs = require("neo-tree.ui.inputs")
|
||||
local popups = require("neo-tree.ui.popups")
|
||||
local log = require("neo-tree.log")
|
||||
local help = require("neo-tree.sources.common.help")
|
||||
local Preview = require("neo-tree.sources.common.preview")
|
||||
|
||||
---Gets the node parent folder
|
||||
---@param state table to look for nodes
|
||||
---@return table? node
|
||||
local function get_folder_node(state)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
local last_id = node:get_id()
|
||||
|
||||
while node do
|
||||
local insert_as_local = state.config.insert_as
|
||||
local insert_as_global = require("neo-tree").config.window.insert_as
|
||||
local use_parent
|
||||
if insert_as_local then
|
||||
use_parent = insert_as_local == "sibling"
|
||||
else
|
||||
use_parent = insert_as_global == "sibling"
|
||||
end
|
||||
|
||||
local is_open_dir = node.type == "directory" and (node:is_expanded() or node.empty_expanded)
|
||||
if use_parent and not is_open_dir then
|
||||
return tree:get_node(node:get_parent_id())
|
||||
end
|
||||
|
||||
if node.type == "directory" then
|
||||
return node
|
||||
end
|
||||
|
||||
local parent_id = node:get_parent_id()
|
||||
if not parent_id or parent_id == last_id then
|
||||
return node
|
||||
else
|
||||
last_id = parent_id
|
||||
node = tree:get_node(parent_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---The using_root_directory is used to decide what part of the filename to show
|
||||
-- the user when asking for a new filename to e.g. create, copy to or move to.
|
||||
---@param state table The state of the source
|
||||
---@return string The root path from which the relative source path should be taken
|
||||
local function get_using_root_directory(state)
|
||||
-- default to showing only the basename of the path
|
||||
local using_root_directory = get_folder_node(state):get_id()
|
||||
local show_path = state.config.show_path
|
||||
if show_path == "absolute" then
|
||||
using_root_directory = ""
|
||||
elseif show_path == "relative" then
|
||||
using_root_directory = state.path
|
||||
elseif show_path ~= nil and show_path ~= "none" then
|
||||
log.warn(
|
||||
'A neo-tree mapping was setup with a config.show_path option with invalid value: "'
|
||||
.. show_path
|
||||
.. '", falling back to its default: nil/"none"'
|
||||
)
|
||||
end
|
||||
return using_root_directory
|
||||
end
|
||||
|
||||
local M = {}
|
||||
|
||||
---Adds all missing common commands to the given module
|
||||
---@param to_source_command_module table The commands module for a source
|
||||
---@param pattern string? A pattern specifying which commands to add, nil to add all
|
||||
M._add_common_commands = function(to_source_command_module, pattern)
|
||||
for name, func in pairs(M) do
|
||||
if
|
||||
type(name) == "string"
|
||||
and not to_source_command_module[name]
|
||||
and (not pattern or name:find(pattern))
|
||||
and not name:find("^_")
|
||||
then
|
||||
to_source_command_module[name] = func
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Add a new file or dir at the current node
|
||||
---@param state table The state of the source
|
||||
---@param callback function The callback to call when the command is done. Called with the parent node as the argument.
|
||||
M.add = function(state, callback)
|
||||
local node = get_folder_node(state)
|
||||
local in_directory = node:get_id()
|
||||
local using_root_directory = get_using_root_directory(state)
|
||||
fs_actions.create_node(in_directory, callback, using_root_directory)
|
||||
end
|
||||
|
||||
---Add a new file or dir at the current node
|
||||
---@param state table The state of the source
|
||||
---@param callback function The callback to call when the command is done. Called with the parent node as the argument.
|
||||
M.add_directory = function(state, callback)
|
||||
local node = get_folder_node(state)
|
||||
local in_directory = node:get_id()
|
||||
local using_root_directory = get_using_root_directory(state)
|
||||
fs_actions.create_directory(in_directory, callback, using_root_directory)
|
||||
end
|
||||
|
||||
M.expand_all_nodes = function(state, toggle_directory)
|
||||
if toggle_directory == nil then
|
||||
toggle_directory = function(_, node)
|
||||
node:expand()
|
||||
end
|
||||
end
|
||||
--state.explicitly_opened_directories = state.explicitly_opened_directories or {}
|
||||
|
||||
local expand_node
|
||||
expand_node = function(node)
|
||||
local id = node:get_id()
|
||||
if node.type == "directory" and not node:is_expanded() then
|
||||
toggle_directory(state, node)
|
||||
node = state.tree:get_node(id)
|
||||
end
|
||||
local children = state.tree:get_nodes(id)
|
||||
if children then
|
||||
for _, child in ipairs(children) do
|
||||
if child.type == "directory" then
|
||||
expand_node(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, node in ipairs(state.tree:get_nodes()) do
|
||||
expand_node(node)
|
||||
end
|
||||
renderer.redraw(state)
|
||||
end
|
||||
|
||||
M.close_node = function(state, callback)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
local parent_node = tree:get_node(node:get_parent_id())
|
||||
local target_node
|
||||
|
||||
if node:has_children() and node:is_expanded() then
|
||||
target_node = node
|
||||
else
|
||||
target_node = parent_node
|
||||
end
|
||||
|
||||
local root = tree:get_nodes()[1]
|
||||
local is_root = target_node:get_id() == root:get_id()
|
||||
|
||||
if target_node and target_node:has_children() and not is_root then
|
||||
target_node:collapse()
|
||||
renderer.redraw(state)
|
||||
renderer.focus_node(state, target_node:get_id())
|
||||
end
|
||||
end
|
||||
|
||||
M.close_all_subnodes = function(state)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
local parent_node = tree:get_node(node:get_parent_id())
|
||||
local target_node
|
||||
|
||||
if node:has_children() and node:is_expanded() then
|
||||
target_node = node
|
||||
else
|
||||
target_node = parent_node
|
||||
end
|
||||
|
||||
renderer.collapse_all_nodes(tree, target_node:get_id())
|
||||
renderer.redraw(state)
|
||||
renderer.focus_node(state, target_node:get_id())
|
||||
end
|
||||
|
||||
M.close_all_nodes = function(state)
|
||||
renderer.collapse_all_nodes(state.tree)
|
||||
renderer.redraw(state)
|
||||
end
|
||||
|
||||
M.close_window = function(state)
|
||||
renderer.close(state)
|
||||
end
|
||||
|
||||
M.toggle_auto_expand_width = function(state)
|
||||
if state.window.position == "float" then
|
||||
return
|
||||
end
|
||||
state.window.auto_expand_width = state.window.auto_expand_width == false
|
||||
local width = utils.resolve_width(state.window.width)
|
||||
if not state.window.auto_expand_width then
|
||||
if (state.window.last_user_width or width) >= vim.api.nvim_win_get_width(0) then
|
||||
state.window.last_user_width = width
|
||||
end
|
||||
vim.api.nvim_win_set_width(0, state.window.last_user_width)
|
||||
state.win_width = state.window.last_user_width
|
||||
state.longest_width_exact = 0
|
||||
log.trace(string.format("Collapse auto_expand_width."))
|
||||
end
|
||||
renderer.redraw(state)
|
||||
end
|
||||
|
||||
local copy_node_to_clipboard = function(state, node)
|
||||
state.clipboard = state.clipboard or {}
|
||||
local existing = state.clipboard[node.id]
|
||||
if existing and existing.action == "copy" then
|
||||
state.clipboard[node.id] = nil
|
||||
else
|
||||
state.clipboard[node.id] = { action = "copy", node = node }
|
||||
log.info("Copied " .. node.name .. " to clipboard")
|
||||
end
|
||||
end
|
||||
|
||||
---Marks node as copied, so that it can be pasted somewhere else.
|
||||
M.copy_to_clipboard = function(state, callback)
|
||||
local node = state.tree:get_node()
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
copy_node_to_clipboard(state, node)
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
M.copy_to_clipboard_visual = function(state, selected_nodes, callback)
|
||||
for _, node in ipairs(selected_nodes) do
|
||||
if node.type ~= "message" then
|
||||
copy_node_to_clipboard(state, node)
|
||||
end
|
||||
end
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
local cut_node_to_clipboard = function(state, node)
|
||||
state.clipboard = state.clipboard or {}
|
||||
local existing = state.clipboard[node.id]
|
||||
if existing and existing.action == "cut" then
|
||||
state.clipboard[node.id] = nil
|
||||
else
|
||||
state.clipboard[node.id] = { action = "cut", node = node }
|
||||
log.info("Cut " .. node.name .. " to clipboard")
|
||||
end
|
||||
end
|
||||
|
||||
---Marks node as cut, so that it can be pasted (moved) somewhere else.
|
||||
M.cut_to_clipboard = function(state, callback)
|
||||
local node = state.tree:get_node()
|
||||
cut_node_to_clipboard(state, node)
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
M.cut_to_clipboard_visual = function(state, selected_nodes, callback)
|
||||
for _, node in ipairs(selected_nodes) do
|
||||
if node.type ~= "message" then
|
||||
cut_node_to_clipboard(state, node)
|
||||
end
|
||||
end
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Git commands
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
M.git_add_file = function(state)
|
||||
local node = state.tree:get_node()
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
local path = node:get_id()
|
||||
local cmd = { "git", "add", path }
|
||||
vim.fn.system(cmd)
|
||||
events.fire_event(events.GIT_EVENT)
|
||||
end
|
||||
|
||||
M.git_add_all = function(state)
|
||||
local cmd = { "git", "add", "-A" }
|
||||
vim.fn.system(cmd)
|
||||
events.fire_event(events.GIT_EVENT)
|
||||
end
|
||||
|
||||
M.git_commit = function(state, and_push)
|
||||
local width = vim.fn.winwidth(0) - 2
|
||||
local row = vim.api.nvim_win_get_height(0) - 3
|
||||
local popup_options = {
|
||||
relative = "win",
|
||||
position = {
|
||||
row = row,
|
||||
col = 0,
|
||||
},
|
||||
size = width,
|
||||
}
|
||||
|
||||
inputs.input("Commit message: ", "", function(msg)
|
||||
local cmd = { "git", "commit", "-m", msg }
|
||||
local title = "git commit"
|
||||
local result = vim.fn.systemlist(cmd)
|
||||
if vim.v.shell_error ~= 0 or (#result > 0 and vim.startswith(result[1], "fatal:")) then
|
||||
popups.alert("ERROR: git commit", result)
|
||||
return
|
||||
end
|
||||
if and_push then
|
||||
title = "git commit && git push"
|
||||
cmd = { "git", "push" }
|
||||
local result2 = vim.fn.systemlist(cmd)
|
||||
table.insert(result, "")
|
||||
for i = 1, #result2 do
|
||||
table.insert(result, result2[i])
|
||||
end
|
||||
end
|
||||
events.fire_event(events.GIT_EVENT)
|
||||
popups.alert(title, result)
|
||||
end, popup_options)
|
||||
end
|
||||
|
||||
M.git_commit_and_push = function(state)
|
||||
M.git_commit(state, true)
|
||||
end
|
||||
|
||||
M.git_push = function(state)
|
||||
inputs.confirm("Are you sure you want to push your changes?", function(yes)
|
||||
if yes then
|
||||
local result = vim.fn.systemlist({ "git", "push" })
|
||||
events.fire_event(events.GIT_EVENT)
|
||||
popups.alert("git push", result)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
M.git_unstage_file = function(state)
|
||||
local node = state.tree:get_node()
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
local path = node:get_id()
|
||||
local cmd = { "git", "reset", "--", path }
|
||||
vim.fn.system(cmd)
|
||||
events.fire_event(events.GIT_EVENT)
|
||||
end
|
||||
|
||||
M.git_revert_file = function(state)
|
||||
local node = state.tree:get_node()
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
local path = node:get_id()
|
||||
local cmd = { "git", "checkout", "HEAD", "--", path }
|
||||
local msg = string.format("Are you sure you want to revert %s?", node.name)
|
||||
inputs.confirm(msg, function(yes)
|
||||
if yes then
|
||||
vim.fn.system(cmd)
|
||||
events.fire_event(events.GIT_EVENT)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- END Git commands
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
M.next_source = function(state)
|
||||
local sources = require("neo-tree").config.sources
|
||||
local sources = require("neo-tree").config.source_selector.sources
|
||||
local next_source = sources[1]
|
||||
for i, source_info in ipairs(sources) do
|
||||
if source_info.source == state.name then
|
||||
next_source = sources[i + 1]
|
||||
if not next_source then
|
||||
next_source = sources[1]
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
require("neo-tree.command").execute({
|
||||
source = next_source.source,
|
||||
position = state.current_position,
|
||||
action = "focus",
|
||||
})
|
||||
end
|
||||
|
||||
M.prev_source = function(state)
|
||||
local sources = require("neo-tree").config.sources
|
||||
local sources = require("neo-tree").config.source_selector.sources
|
||||
local next_source = sources[#sources]
|
||||
for i, source_info in ipairs(sources) do
|
||||
if source_info.source == state.name then
|
||||
next_source = sources[i - 1]
|
||||
if not next_source then
|
||||
next_source = sources[#sources]
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
require("neo-tree.command").execute({
|
||||
source = next_source.source,
|
||||
position = state.current_position,
|
||||
action = "focus",
|
||||
})
|
||||
end
|
||||
|
||||
M.show_debug_info = function(state)
|
||||
print(vim.inspect(state))
|
||||
end
|
||||
|
||||
---Pastes all items from the clipboard to the current directory.
|
||||
---@param state table The state of the source
|
||||
---@param callback function The callback to call when the command is done. Called with the parent node as the argument.
|
||||
M.paste_from_clipboard = function(state, callback)
|
||||
if state.clipboard then
|
||||
local folder = get_folder_node(state):get_id()
|
||||
-- Convert to list so to make it easier to pop items from the stack.
|
||||
local clipboard_list = {}
|
||||
for _, item in pairs(state.clipboard) do
|
||||
table.insert(clipboard_list, item)
|
||||
end
|
||||
state.clipboard = nil
|
||||
local handle_next_paste, paste_complete
|
||||
|
||||
paste_complete = function(source, destination)
|
||||
if callback then
|
||||
local insert_as = require("neo-tree").config.window.insert_as
|
||||
-- open the folder so the user can see the new files
|
||||
local node = insert_as == "sibling" and state.tree:get_node() or state.tree:get_node(folder)
|
||||
if not node then
|
||||
log.warn("Could not find node for " .. folder)
|
||||
end
|
||||
callback(node, destination)
|
||||
end
|
||||
local next_item = table.remove(clipboard_list)
|
||||
if next_item then
|
||||
handle_next_paste(next_item)
|
||||
end
|
||||
end
|
||||
|
||||
handle_next_paste = function(item)
|
||||
if item.action == "copy" then
|
||||
fs_actions.copy_node(
|
||||
item.node.path,
|
||||
folder .. utils.path_separator .. item.node.name,
|
||||
paste_complete
|
||||
)
|
||||
elseif item.action == "cut" then
|
||||
fs_actions.move_node(
|
||||
item.node.path,
|
||||
folder .. utils.path_separator .. item.node.name,
|
||||
paste_complete
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local next_item = table.remove(clipboard_list)
|
||||
if next_item then
|
||||
handle_next_paste(next_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Copies a node to a new location, using typed input.
|
||||
---@param state table The state of the source
|
||||
---@param callback function The callback to call when the command is done. Called with the parent node as the argument.
|
||||
M.copy = function(state, callback)
|
||||
local node = state.tree:get_node()
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
local using_root_directory = get_using_root_directory(state)
|
||||
fs_actions.copy_node(node.path, nil, callback, using_root_directory)
|
||||
end
|
||||
|
||||
---Moves a node to a new location, using typed input.
|
||||
---@param state table The state of the source
|
||||
---@param callback function The callback to call when the command is done. Called with the parent node as the argument.
|
||||
M.move = function(state, callback)
|
||||
local node = state.tree:get_node()
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
local using_root_directory = get_using_root_directory(state)
|
||||
fs_actions.move_node(node.path, nil, callback, using_root_directory)
|
||||
end
|
||||
|
||||
M.delete = function(state, callback)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
if node.type == "file" or node.type == "directory" then
|
||||
fs_actions.delete_node(node.path, callback)
|
||||
else
|
||||
log.warn("The `delete` command can only be used on files and directories")
|
||||
end
|
||||
end
|
||||
|
||||
M.delete_visual = function(state, selected_nodes, callback)
|
||||
local paths_to_delete = {}
|
||||
for _, node_to_delete in pairs(selected_nodes) do
|
||||
if node_to_delete.type == "file" or node_to_delete.type == "directory" then
|
||||
table.insert(paths_to_delete, node_to_delete.path)
|
||||
end
|
||||
end
|
||||
fs_actions.delete_nodes(paths_to_delete, callback)
|
||||
end
|
||||
|
||||
M.preview = function(state)
|
||||
Preview.show(state)
|
||||
end
|
||||
|
||||
M.revert_preview = function()
|
||||
Preview.hide()
|
||||
end
|
||||
--
|
||||
-- Multi-purpose function to back out of whatever we are in
|
||||
M.cancel = function(state)
|
||||
if Preview.is_active() then
|
||||
Preview.hide()
|
||||
else
|
||||
if state.current_position == "float" then
|
||||
renderer.close_all_floating_windows()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.toggle_preview = function(state)
|
||||
Preview.toggle(state)
|
||||
end
|
||||
|
||||
M.focus_preview = function()
|
||||
Preview.focus()
|
||||
end
|
||||
|
||||
---Open file or directory
|
||||
---@param state table The state of the source
|
||||
---@param open_cmd string The vim command to use to open the file
|
||||
---@param toggle_directory function The function to call to toggle a directory
|
||||
---open/closed
|
||||
local open_with_cmd = function(state, open_cmd, toggle_directory, open_file)
|
||||
local tree = state.tree
|
||||
local success, node = pcall(tree.get_node, tree)
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
if not (success and node) then
|
||||
log.debug("Could not get node.")
|
||||
return
|
||||
end
|
||||
|
||||
local function open()
|
||||
M.revert_preview()
|
||||
local path = node.path or node:get_id()
|
||||
local bufnr = node.extra and node.extra.bufnr
|
||||
if node.type == "terminal" then
|
||||
path = node:get_id()
|
||||
end
|
||||
if type(open_file) == "function" then
|
||||
open_file(state, path, open_cmd, bufnr)
|
||||
else
|
||||
utils.open_file(state, path, open_cmd, bufnr)
|
||||
end
|
||||
local extra = node.extra or {}
|
||||
local pos = extra.position or extra.end_position
|
||||
if pos ~= nil then
|
||||
vim.api.nvim_win_set_cursor(0, { (pos[1] or 0) + 1, pos[2] or 0 })
|
||||
vim.api.nvim_win_call(0, function()
|
||||
vim.cmd("normal! zvzz") -- expand folds and center cursor
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
if utils.is_expandable(node) then
|
||||
if toggle_directory and node.type == "directory" then
|
||||
toggle_directory(node)
|
||||
elseif node:has_children() then
|
||||
if node:is_expanded() and node.type ~= "directory" then
|
||||
return open()
|
||||
end
|
||||
|
||||
local updated = false
|
||||
if node:is_expanded() then
|
||||
updated = node:collapse()
|
||||
else
|
||||
updated = node:expand()
|
||||
end
|
||||
if updated then
|
||||
renderer.redraw(state)
|
||||
end
|
||||
end
|
||||
else
|
||||
open()
|
||||
end
|
||||
end
|
||||
|
||||
---Open file or directory in the closest window
|
||||
---@param state table The state of the source
|
||||
---@param toggle_directory function The function to call to toggle a directory
|
||||
---open/closed
|
||||
M.open = function(state, toggle_directory)
|
||||
open_with_cmd(state, "e", toggle_directory)
|
||||
end
|
||||
|
||||
---Open file or directory in a split of the closest window
|
||||
---@param state table The state of the source
|
||||
---@param toggle_directory function The function to call to toggle a directory
|
||||
---open/closed
|
||||
M.open_split = function(state, toggle_directory)
|
||||
open_with_cmd(state, "split", toggle_directory)
|
||||
end
|
||||
|
||||
---Open file or directory in a vertical split of the closest window
|
||||
---@param state table The state of the source
|
||||
---@param toggle_directory function The function to call to toggle a directory
|
||||
---open/closed
|
||||
M.open_vsplit = function(state, toggle_directory)
|
||||
open_with_cmd(state, "vsplit", toggle_directory)
|
||||
end
|
||||
|
||||
---Open file or directory in a new tab
|
||||
---@param state table The state of the source
|
||||
---@param toggle_directory function The function to call to toggle a directory
|
||||
---open/closed
|
||||
M.open_tabnew = function(state, toggle_directory)
|
||||
open_with_cmd(state, "tabnew", toggle_directory)
|
||||
end
|
||||
|
||||
---Open file or directory or focus it if a buffer already exists with it
|
||||
---@param state table The state of the source
|
||||
---@param toggle_directory function The function to call to toggle a directory
|
||||
---open/closed
|
||||
M.open_drop = function(state, toggle_directory)
|
||||
open_with_cmd(state, "drop", toggle_directory)
|
||||
end
|
||||
|
||||
---Open file or directory in new tab or focus it if a buffer already exists with it
|
||||
---@param state table The state of the source
|
||||
---@param toggle_directory function The function to call to toggle a directory
|
||||
---open/closed
|
||||
M.open_tab_drop = function(state, toggle_directory)
|
||||
open_with_cmd(state, "tab drop", toggle_directory)
|
||||
end
|
||||
|
||||
M.rename = function(state, callback)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
if node.type == "message" then
|
||||
return
|
||||
end
|
||||
fs_actions.rename_node(node.path, callback)
|
||||
end
|
||||
|
||||
---Expands or collapses the current node.
|
||||
M.toggle_node = function(state, toggle_directory)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
if not utils.is_expandable(node) then
|
||||
return
|
||||
end
|
||||
if node.type == "directory" and toggle_directory then
|
||||
toggle_directory(node)
|
||||
elseif node:has_children() then
|
||||
local updated = false
|
||||
if node:is_expanded() then
|
||||
updated = node:collapse()
|
||||
else
|
||||
updated = node:expand()
|
||||
end
|
||||
if updated then
|
||||
renderer.redraw(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Expands or collapses the current node.
|
||||
M.toggle_directory = function(state, toggle_directory)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
if node.type ~= "directory" then
|
||||
return
|
||||
end
|
||||
M.toggle_node(state, toggle_directory)
|
||||
end
|
||||
|
||||
---Marks potential windows with letters and will open the give node in the picked window.
|
||||
---@param state table The state of the source
|
||||
---@param path string The path to open
|
||||
---@param cmd string Command that is used to perform action on picked window
|
||||
local use_window_picker = function(state, path, cmd)
|
||||
local success, picker = pcall(require, "window-picker")
|
||||
if not success then
|
||||
print(
|
||||
"You'll need to install window-picker to use this command: https://github.com/s1n7ax/nvim-window-picker"
|
||||
)
|
||||
return
|
||||
end
|
||||
local events = require("neo-tree.events")
|
||||
local event_result = events.fire_event(events.FILE_OPEN_REQUESTED, {
|
||||
state = state,
|
||||
path = path,
|
||||
open_cmd = cmd,
|
||||
}) or {}
|
||||
if event_result.handled then
|
||||
events.fire_event(events.FILE_OPENED, path)
|
||||
return
|
||||
end
|
||||
local picked_window_id = picker.pick_window()
|
||||
if picked_window_id then
|
||||
vim.api.nvim_set_current_win(picked_window_id)
|
||||
local result, err = pcall(vim.cmd, cmd .. " " .. vim.fn.fnameescape(path))
|
||||
if result or err == "Vim(edit):E325: ATTENTION" then
|
||||
-- fixes #321
|
||||
vim.api.nvim_buf_set_option(0, "buflisted", true)
|
||||
events.fire_event(events.FILE_OPENED, path)
|
||||
else
|
||||
log.error("Error opening file:", err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Marks potential windows with letters and will open the give node in the picked window.
|
||||
M.open_with_window_picker = function(state, toggle_directory)
|
||||
open_with_cmd(state, "edit", toggle_directory, use_window_picker)
|
||||
end
|
||||
|
||||
---Marks potential windows with letters and will open the give node in a split next to the picked window.
|
||||
M.split_with_window_picker = function(state, toggle_directory)
|
||||
open_with_cmd(state, "split", toggle_directory, use_window_picker)
|
||||
end
|
||||
|
||||
---Marks potential windows with letters and will open the give node in a vertical split next to the picked window.
|
||||
M.vsplit_with_window_picker = function(state, toggle_directory)
|
||||
open_with_cmd(state, "vsplit", toggle_directory, use_window_picker)
|
||||
end
|
||||
|
||||
M.show_help = function(state)
|
||||
help.show(state)
|
||||
end
|
||||
|
||||
return M
|
440
bundle/neo-tree.nvim/lua/neo-tree/sources/common/components.lua
vendored
Normal file
440
bundle/neo-tree.nvim/lua/neo-tree/sources/common/components.lua
vendored
Normal file
@ -0,0 +1,440 @@
|
||||
-- This file contains the built-in components. Each componment is a function
|
||||
-- that takes the following arguments:
|
||||
-- config: A table containing the configuration provided by the user
|
||||
-- when declaring this component in their renderer config.
|
||||
-- node: A NuiNode object for the currently focused node.
|
||||
-- state: The current state of the source providing the items.
|
||||
--
|
||||
-- The function should return either a table, or a list of tables, each of which
|
||||
-- contains the following keys:
|
||||
-- text: The text to display for this item.
|
||||
-- highlight: The highlight group to apply to this text.
|
||||
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local utils = require("neo-tree.utils")
|
||||
local file_nesting = require("neo-tree.sources.common.file-nesting")
|
||||
local container = require("neo-tree.sources.common.container")
|
||||
local log = require("neo-tree.log")
|
||||
|
||||
local M = {}
|
||||
|
||||
local make_two_char = function(symbol)
|
||||
if vim.fn.strchars(symbol) == 1 then
|
||||
return symbol .. " "
|
||||
else
|
||||
return symbol
|
||||
end
|
||||
end
|
||||
-- only works in the buffers component, but it's here so we don't have to defined
|
||||
-- multple renderers.
|
||||
M.bufnr = function(config, node, state)
|
||||
local highlight = config.highlight or highlights.BUFFER_NUMBER
|
||||
local bufnr = node.extra and node.extra.bufnr
|
||||
if not bufnr then
|
||||
return {}
|
||||
end
|
||||
return {
|
||||
text = string.format("#%s", bufnr),
|
||||
highlight = highlight,
|
||||
}
|
||||
end
|
||||
|
||||
M.clipboard = function(config, node, state)
|
||||
local clipboard = state.clipboard or {}
|
||||
local clipboard_state = clipboard[node:get_id()]
|
||||
if not clipboard_state then
|
||||
return {}
|
||||
end
|
||||
return {
|
||||
text = " (" .. clipboard_state.action .. ")",
|
||||
highlight = config.highlight or highlights.DIM_TEXT,
|
||||
}
|
||||
end
|
||||
|
||||
M.container = container.render
|
||||
|
||||
M.current_filter = function(config, node, state)
|
||||
local filter = node.search_pattern or ""
|
||||
if filter == "" then
|
||||
return {}
|
||||
end
|
||||
return {
|
||||
{
|
||||
text = "Find",
|
||||
highlight = highlights.DIM_TEXT,
|
||||
},
|
||||
{
|
||||
text = string.format('"%s"', filter),
|
||||
highlight = config.highlight or highlights.FILTER_TERM,
|
||||
},
|
||||
{
|
||||
text = "in",
|
||||
highlight = highlights.DIM_TEXT,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
M.diagnostics = function(config, node, state)
|
||||
local diag = state.diagnostics_lookup or {}
|
||||
local diag_state = diag[node:get_id()]
|
||||
if config.hide_when_expanded and node.type == "directory" and node:is_expanded() then
|
||||
return {}
|
||||
end
|
||||
if not diag_state then
|
||||
return {}
|
||||
end
|
||||
if config.errors_only and diag_state.severity_number > 1 then
|
||||
return {}
|
||||
end
|
||||
local severity = diag_state.severity_string
|
||||
local defined = vim.fn.sign_getdefined("DiagnosticSign" .. severity)
|
||||
if not defined then
|
||||
-- backwards compatibility...
|
||||
local old_severity = severity
|
||||
if severity == "Warning" then
|
||||
old_severity = "Warn"
|
||||
elseif severity == "Information" then
|
||||
old_severity = "Info"
|
||||
end
|
||||
defined = vim.fn.sign_getdefined("LspDiagnosticsSign" .. old_severity)
|
||||
end
|
||||
defined = defined and defined[1]
|
||||
|
||||
-- check for overrides in the component config
|
||||
local severity_lower = severity:lower()
|
||||
if config.symbols and config.symbols[severity_lower] then
|
||||
defined = defined or { texthl = "Diagnostic" .. severity }
|
||||
defined.text = config.symbols[severity_lower]
|
||||
end
|
||||
if config.highlights and config.highlights[severity_lower] then
|
||||
defined = defined or { text = severity:sub(1, 1) }
|
||||
defined.texthl = config.highlights[severity_lower]
|
||||
end
|
||||
|
||||
if defined and defined.text and defined.texthl then
|
||||
return {
|
||||
text = make_two_char(defined.text),
|
||||
highlight = defined.texthl,
|
||||
}
|
||||
else
|
||||
return {
|
||||
text = severity:sub(1, 1),
|
||||
highlight = "Diagnostic" .. severity,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
M.git_status = function(config, node, state)
|
||||
local git_status_lookup = state.git_status_lookup
|
||||
if config.hide_when_expanded and node.type == "directory" and node:is_expanded() then
|
||||
return {}
|
||||
end
|
||||
if not git_status_lookup then
|
||||
return {}
|
||||
end
|
||||
local git_status = git_status_lookup[node.path]
|
||||
if not git_status then
|
||||
if node.filtered_by and node.filtered_by.gitignored then
|
||||
git_status = "!!"
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
local symbols = config.symbols or {}
|
||||
local change_symbol
|
||||
local change_highlt = highlights.FILE_NAME
|
||||
local status_symbol = symbols.staged
|
||||
local status_highlt = highlights.GIT_STAGED
|
||||
if node.type == "directory" and git_status:len() == 1 then
|
||||
status_symbol = nil
|
||||
end
|
||||
|
||||
if git_status:sub(1, 1) == " " then
|
||||
status_symbol = symbols.unstaged
|
||||
status_highlt = highlights.GIT_UNSTAGED
|
||||
end
|
||||
|
||||
if git_status:match("?$") then
|
||||
status_symbol = nil
|
||||
status_highlt = highlights.GIT_UNTRACKED
|
||||
change_symbol = symbols.untracked
|
||||
change_highlt = highlights.GIT_UNTRACKED
|
||||
-- all variations of merge conflicts
|
||||
elseif git_status == "DD" then
|
||||
status_symbol = symbols.conflict
|
||||
status_highlt = highlights.GIT_CONFLICT
|
||||
change_symbol = symbols.deleted
|
||||
change_highlt = highlights.GIT_CONFLICT
|
||||
elseif git_status == "UU" then
|
||||
status_symbol = symbols.conflict
|
||||
status_highlt = highlights.GIT_CONFLICT
|
||||
change_symbol = symbols.modified
|
||||
change_highlt = highlights.GIT_CONFLICT
|
||||
elseif git_status == "AA" then
|
||||
status_symbol = symbols.conflict
|
||||
status_highlt = highlights.GIT_CONFLICT
|
||||
change_symbol = symbols.added
|
||||
change_highlt = highlights.GIT_CONFLICT
|
||||
elseif git_status:match("U") then
|
||||
status_symbol = symbols.conflict
|
||||
status_highlt = highlights.GIT_CONFLICT
|
||||
if git_status:match("A") then
|
||||
change_symbol = symbols.added
|
||||
elseif git_status:match("D") then
|
||||
change_symbol = symbols.deleted
|
||||
end
|
||||
change_highlt = highlights.GIT_CONFLICT
|
||||
-- end merge conflict section
|
||||
elseif git_status:match("M") then
|
||||
change_symbol = symbols.modified
|
||||
change_highlt = highlights.GIT_MODIFIED
|
||||
elseif git_status:match("R") then
|
||||
change_symbol = symbols.renamed
|
||||
change_highlt = highlights.GIT_RENAMED
|
||||
elseif git_status:match("[ACT]") then
|
||||
change_symbol = symbols.added
|
||||
change_highlt = highlights.GIT_ADDED
|
||||
elseif git_status:match("!") then
|
||||
status_symbol = nil
|
||||
change_symbol = symbols.ignored
|
||||
change_highlt = highlights.GIT_IGNORED
|
||||
elseif git_status:match("D") then
|
||||
change_symbol = symbols.deleted
|
||||
change_highlt = highlights.GIT_DELETED
|
||||
end
|
||||
|
||||
if change_symbol or status_symbol then
|
||||
local components = {}
|
||||
if type(change_symbol) == "string" and #change_symbol > 0 then
|
||||
table.insert(components, {
|
||||
text = make_two_char(change_symbol),
|
||||
highlight = change_highlt,
|
||||
})
|
||||
end
|
||||
if type(status_symbol) == "string" and #status_symbol > 0 then
|
||||
table.insert(components, {
|
||||
text = make_two_char(status_symbol),
|
||||
highlight = status_highlt,
|
||||
})
|
||||
end
|
||||
return components
|
||||
else
|
||||
return {
|
||||
text = "[" .. git_status .. "]",
|
||||
highlight = config.highlight or change_highlt,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
M.filtered_by = function(config, node, state)
|
||||
local result = {}
|
||||
if type(node.filtered_by) == "table" then
|
||||
local fby = node.filtered_by
|
||||
if fby.name then
|
||||
result = {
|
||||
text = "(hide by name)",
|
||||
highlight = highlights.HIDDEN_BY_NAME,
|
||||
}
|
||||
elseif fby.pattern then
|
||||
result = {
|
||||
text = "(hide by pattern)",
|
||||
highlight = highlights.HIDDEN_BY_NAME,
|
||||
}
|
||||
elseif fby.gitignored then
|
||||
result = {
|
||||
text = "(gitignored)",
|
||||
highlight = highlights.GIT_IGNORED,
|
||||
}
|
||||
elseif fby.dotfiles then
|
||||
result = {
|
||||
text = "(dotfile)",
|
||||
highlight = highlights.DOTFILE,
|
||||
}
|
||||
elseif fby.hidden then
|
||||
result = {
|
||||
text = "(hidden)",
|
||||
highlight = highlights.WINDOWS_HIDDEN,
|
||||
}
|
||||
end
|
||||
fby = nil
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
M.icon = function(config, node, state)
|
||||
local icon = config.default or " "
|
||||
local highlight = config.highlight or highlights.FILE_ICON
|
||||
if node.type == "directory" then
|
||||
highlight = highlights.DIRECTORY_ICON
|
||||
if node.loaded and not node:has_children() then
|
||||
icon = not node.empty_expanded and config.folder_empty or config.folder_empty_open
|
||||
elseif node:is_expanded() then
|
||||
icon = config.folder_open or "-"
|
||||
else
|
||||
icon = config.folder_closed or "+"
|
||||
end
|
||||
elseif node.type == "file" or node.type == "terminal" then
|
||||
local success, web_devicons = pcall(require, "nvim-web-devicons")
|
||||
if success then
|
||||
local devicon, hl = web_devicons.get_icon(node.name, node.ext)
|
||||
icon = devicon or icon
|
||||
highlight = hl or highlight
|
||||
end
|
||||
end
|
||||
|
||||
local filtered_by = M.filtered_by(config, node, state)
|
||||
|
||||
return {
|
||||
text = icon .. " ",
|
||||
highlight = filtered_by.highlight or highlight,
|
||||
}
|
||||
end
|
||||
|
||||
M.modified = function(config, node, state)
|
||||
local opened_buffers = state.opened_buffers or {}
|
||||
local buf_info = opened_buffers[node.path]
|
||||
|
||||
if buf_info and buf_info.modified then
|
||||
return {
|
||||
text = (make_two_char(config.symbol) or "[+]"),
|
||||
highlight = config.highlight or highlights.MODIFIED,
|
||||
}
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
M.name = function(config, node, state)
|
||||
local highlight = config.highlight or highlights.FILE_NAME
|
||||
local text = node.name
|
||||
if node.type == "directory" then
|
||||
highlight = highlights.DIRECTORY_NAME
|
||||
if config.trailing_slash and text ~= "/" then
|
||||
text = text .. "/"
|
||||
end
|
||||
end
|
||||
|
||||
if node:get_depth() == 1 and node.type ~= "message" then
|
||||
highlight = highlights.ROOT_NAME
|
||||
else
|
||||
local filtered_by = M.filtered_by(config, node, state)
|
||||
highlight = filtered_by.highlight or highlight
|
||||
if config.use_git_status_colors then
|
||||
local git_status = state.components.git_status({}, node, state)
|
||||
if git_status and git_status.highlight then
|
||||
highlight = git_status.highlight
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local hl_opened = config.highlight_opened_files
|
||||
if hl_opened then
|
||||
local opened_buffers = state.opened_buffers or {}
|
||||
if
|
||||
(hl_opened == "all" and opened_buffers[node.path])
|
||||
or (opened_buffers[node.path] and opened_buffers[node.path].loaded)
|
||||
then
|
||||
highlight = highlights.FILE_NAME_OPENED
|
||||
end
|
||||
end
|
||||
|
||||
if type(config.right_padding) == "number" then
|
||||
if config.right_padding > 0 then
|
||||
text = text .. string.rep(" ", config.right_padding)
|
||||
end
|
||||
else
|
||||
text = text
|
||||
end
|
||||
|
||||
return {
|
||||
text = text,
|
||||
highlight = highlight,
|
||||
}
|
||||
end
|
||||
|
||||
M.indent = function(config, node, state)
|
||||
if not state.skip_marker_at_level then
|
||||
state.skip_marker_at_level = {}
|
||||
end
|
||||
|
||||
local strlen = vim.fn.strdisplaywidth
|
||||
local skip_marker = state.skip_marker_at_level
|
||||
local indent_size = config.indent_size or 2
|
||||
local padding = config.padding or 0
|
||||
local level = node.level
|
||||
local with_markers = config.with_markers
|
||||
local with_expanders = config.with_expanders == nil and file_nesting.is_enabled()
|
||||
or config.with_expanders
|
||||
local marker_highlight = config.highlight or highlights.INDENT_MARKER
|
||||
local expander_highlight = config.expander_highlight or config.highlight or highlights.EXPANDER
|
||||
|
||||
local function get_expander()
|
||||
if with_expanders and utils.is_expandable(node) then
|
||||
return node:is_expanded() and (config.expander_expanded or "")
|
||||
or (config.expander_collapsed or "")
|
||||
end
|
||||
end
|
||||
|
||||
if indent_size == 0 or level < 2 or not with_markers then
|
||||
local len = indent_size * level + padding
|
||||
local expander = get_expander()
|
||||
if level == 0 or not expander then
|
||||
return {
|
||||
text = string.rep(" ", len),
|
||||
}
|
||||
end
|
||||
return {
|
||||
text = string.rep(" ", len - strlen(expander) - 1) .. expander .. " ",
|
||||
highlight = expander_highlight,
|
||||
}
|
||||
end
|
||||
|
||||
local indent_marker = config.indent_marker or "│"
|
||||
local last_indent_marker = config.last_indent_marker or "└"
|
||||
|
||||
skip_marker[level] = node.is_last_child
|
||||
local indent = {}
|
||||
if padding > 0 then
|
||||
table.insert(indent, { text = string.rep(" ", padding) })
|
||||
end
|
||||
|
||||
for i = 1, level do
|
||||
local char = ""
|
||||
local spaces_count = indent_size
|
||||
local highlight = nil
|
||||
|
||||
if i > 1 and not skip_marker[i] or i == level then
|
||||
spaces_count = spaces_count - 1
|
||||
char = indent_marker
|
||||
highlight = marker_highlight
|
||||
if i == level then
|
||||
local expander = get_expander()
|
||||
if expander then
|
||||
char = expander
|
||||
highlight = expander_highlight
|
||||
elseif node.is_last_child then
|
||||
char = last_indent_marker
|
||||
spaces_count = spaces_count - (vim.api.nvim_strwidth(last_indent_marker) - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(indent, { text = char .. string.rep(" ", spaces_count), highlight = highlight })
|
||||
end
|
||||
|
||||
return indent
|
||||
end
|
||||
|
||||
M.symlink_target = function(config, node, state)
|
||||
if node.is_link then
|
||||
return {
|
||||
text = string.format(" ➛ %s", node.link_to),
|
||||
highlight = config.highlight or highlights.SYMBOLIC_LINK_TARGET,
|
||||
}
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
331
bundle/neo-tree.nvim/lua/neo-tree/sources/common/container.lua
vendored
Normal file
331
bundle/neo-tree.nvim/lua/neo-tree/sources/common/container.lua
vendored
Normal file
@ -0,0 +1,331 @@
|
||||
local utils = require("neo-tree.utils")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local log = require("neo-tree.log")
|
||||
|
||||
local M = {}
|
||||
|
||||
local calc_rendered_width = function(rendered_item)
|
||||
local width = 0
|
||||
|
||||
for _, item in ipairs(rendered_item) do
|
||||
if item.text then
|
||||
width = width + vim.fn.strchars(item.text)
|
||||
end
|
||||
end
|
||||
|
||||
return width
|
||||
end
|
||||
|
||||
local calc_container_width = function(config, node, state, context)
|
||||
local container_width = 0
|
||||
if type(config.width) == "string" then
|
||||
if config.width == "fit_content" then
|
||||
container_width = context.max_width
|
||||
elseif config.width == "100%" then
|
||||
container_width = context.available_width
|
||||
elseif config.width:match("^%d+%%$") then
|
||||
local percent = tonumber(config.width:sub(1, -2)) / 100
|
||||
container_width = math.floor(percent * context.available_width)
|
||||
else
|
||||
error("Invalid container width: " .. config.width)
|
||||
end
|
||||
elseif type(config.width) == "number" then
|
||||
container_width = config.width
|
||||
elseif type(config.width) == "function" then
|
||||
container_width = config.width(node, state)
|
||||
else
|
||||
error("Invalid container width: " .. config.width)
|
||||
end
|
||||
|
||||
if config.min_width then
|
||||
container_width = math.max(container_width, config.min_width)
|
||||
end
|
||||
if config.max_width then
|
||||
container_width = math.min(container_width, config.max_width)
|
||||
end
|
||||
context.container_width = container_width
|
||||
return container_width
|
||||
end
|
||||
|
||||
local render_content = function(config, node, state, context)
|
||||
local add_padding = function(rendered_item, should_pad)
|
||||
for _, data in ipairs(rendered_item) do
|
||||
if data.text then
|
||||
local padding = (should_pad and #data.text and data.text:sub(1, 1) ~= " ") and " " or ""
|
||||
data.text = padding .. data.text
|
||||
should_pad = data.text:sub(#data.text) ~= " "
|
||||
end
|
||||
end
|
||||
return should_pad
|
||||
end
|
||||
|
||||
local max_width = 0
|
||||
local grouped_by_zindex = utils.group_by(config.content, "zindex")
|
||||
|
||||
for zindex, items in pairs(grouped_by_zindex) do
|
||||
local should_pad = { left = false, right = false }
|
||||
local zindex_rendered = { left = {}, right = {} }
|
||||
local rendered_width = 0
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
local rendered_item = renderer.render_component(item, node, state, context.available_width)
|
||||
if rendered_item then
|
||||
local align = item.align or "left"
|
||||
should_pad[align] = add_padding(rendered_item, should_pad[align])
|
||||
|
||||
vim.list_extend(zindex_rendered[align], rendered_item)
|
||||
rendered_width = rendered_width + calc_rendered_width(rendered_item)
|
||||
end
|
||||
end
|
||||
|
||||
max_width = math.max(max_width, rendered_width)
|
||||
grouped_by_zindex[zindex] = zindex_rendered
|
||||
end
|
||||
|
||||
context.max_width = max_width
|
||||
context.grouped_by_zindex = grouped_by_zindex
|
||||
return context
|
||||
end
|
||||
|
||||
---Takes a list of rendered components and truncates them to fit the container width
|
||||
---@param layer table The list of rendered components.
|
||||
---@param skip_count number The number of characters to skip from the begining/left.
|
||||
---@param max_length number The maximum number of characters to return.
|
||||
local truncate_layer_keep_left = function(layer, skip_count, max_length)
|
||||
local result = {}
|
||||
local taken = 0
|
||||
local skipped = 0
|
||||
for _, item in ipairs(layer) do
|
||||
local remaining_to_skip = skip_count - skipped
|
||||
if remaining_to_skip > 0 then
|
||||
if #item.text <= remaining_to_skip then
|
||||
skipped = skipped + vim.fn.strchars(item.text)
|
||||
item.text = ""
|
||||
else
|
||||
item.text = item.text:sub(remaining_to_skip)
|
||||
if #item.text + taken > max_length then
|
||||
item.text = item.text:sub(1, max_length - taken)
|
||||
end
|
||||
table.insert(result, item)
|
||||
taken = taken + #item.text
|
||||
skipped = skipped + remaining_to_skip
|
||||
end
|
||||
elseif taken <= max_length then
|
||||
if #item.text + taken > max_length then
|
||||
item.text = item.text:sub(1, max_length - taken)
|
||||
end
|
||||
table.insert(result, item)
|
||||
taken = taken + vim.fn.strchars(item.text)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---Takes a list of rendered components and truncates them to fit the container width
|
||||
---@param layer table The list of rendered components.
|
||||
---@param skip_count number The number of characters to skip from the end/right.
|
||||
---@param max_length number The maximum number of characters to return.
|
||||
local truncate_layer_keep_right = function(layer, skip_count, max_length)
|
||||
local result = {}
|
||||
local taken = 0
|
||||
local skipped = 0
|
||||
local i = #layer
|
||||
while i > 0 do
|
||||
local item = layer[i]
|
||||
i = i - 1
|
||||
local text_length = vim.fn.strchars(item.text)
|
||||
local remaining_to_skip = skip_count - skipped
|
||||
if remaining_to_skip > 0 then
|
||||
if text_length <= remaining_to_skip then
|
||||
skipped = skipped + text_length
|
||||
item.text = ""
|
||||
else
|
||||
item.text = vim.fn.strcharpart(item.text, 0, text_length - remaining_to_skip)
|
||||
text_length = vim.fn.strchars(item.text)
|
||||
if text_length + taken > max_length then
|
||||
item.text = vim.fn.strcharpart(item.text, text_length - (max_length - taken))
|
||||
text_length = vim.fn.strchars(item.text)
|
||||
end
|
||||
table.insert(result, item)
|
||||
taken = taken + text_length
|
||||
skipped = skipped + remaining_to_skip
|
||||
end
|
||||
elseif taken <= max_length then
|
||||
if text_length + taken > max_length then
|
||||
item.text = vim.fn.strcharpart(item.text, text_length - (max_length - taken))
|
||||
text_length = vim.fn.strchars(item.text)
|
||||
end
|
||||
table.insert(result, item)
|
||||
taken = taken + text_length
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local fade_content = function(layer, fade_char_count)
|
||||
local text = layer[#layer].text
|
||||
if not text or #text == 0 then
|
||||
return
|
||||
end
|
||||
local hl = layer[#layer].highlight or "Normal"
|
||||
local fade = {
|
||||
highlights.get_faded_highlight_group(hl, 0.68),
|
||||
highlights.get_faded_highlight_group(hl, 0.6),
|
||||
highlights.get_faded_highlight_group(hl, 0.35),
|
||||
}
|
||||
|
||||
for i = 3, 1, -1 do
|
||||
if #text >= i and fade_char_count >= i then
|
||||
layer[#layer].text = text:sub(1, -i - 1)
|
||||
for j = i, 1, -1 do
|
||||
-- force no padding for each faded character
|
||||
local entry = { text = text:sub(-j, -j), highlight = fade[i - j + 1], no_padding = true }
|
||||
table.insert(layer, entry)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local try_fade_content = function(layer, fade_char_count)
|
||||
local success, err = pcall(fade_content, layer, fade_char_count)
|
||||
if not success then
|
||||
log.debug("Error while trying to fade content: ", err)
|
||||
end
|
||||
end
|
||||
|
||||
local merge_content = function(context)
|
||||
-- Heres the idea:
|
||||
-- * Starting backwards from the layer with the highest zindex
|
||||
-- set the left and right tables to the content of the layer
|
||||
-- * If a layer has more content than will fit, the left side will be truncated.
|
||||
-- * If the available space is not used up, move on to the next layer
|
||||
-- * With each subsequent layer, if the length of that layer is greater then the existing
|
||||
-- length for that side (left or right), then clip that layer and append whatver portion is
|
||||
-- not covered up to the appropriate side.
|
||||
-- * Check again to see if we have used up the available width, short circuit if we have.
|
||||
-- * Repeat until all layers have been merged.
|
||||
-- * Join the left and right tables together and return.
|
||||
--
|
||||
local remaining_width = context.container_width
|
||||
local left, right = {}, {}
|
||||
local left_width, right_width = 0, 0
|
||||
local wanted_width = 0
|
||||
|
||||
if context.left_padding and context.left_padding > 0 then
|
||||
table.insert(left, { text = string.rep(" ", context.left_padding) })
|
||||
remaining_width = remaining_width - context.left_padding
|
||||
left_width = left_width + context.left_padding
|
||||
wanted_width = wanted_width + context.left_padding
|
||||
end
|
||||
|
||||
if context.right_padding and context.right_padding > 0 then
|
||||
remaining_width = remaining_width - context.right_padding
|
||||
wanted_width = wanted_width + context.right_padding
|
||||
end
|
||||
|
||||
local keys = utils.get_keys(context.grouped_by_zindex, true)
|
||||
if type(keys) ~= "table" then
|
||||
return {}
|
||||
end
|
||||
local i = #keys
|
||||
while i > 0 do
|
||||
local key = keys[i]
|
||||
local layer = context.grouped_by_zindex[key]
|
||||
i = i - 1
|
||||
|
||||
if utils.truthy(layer.right) then
|
||||
local width = calc_rendered_width(layer.right)
|
||||
wanted_width = wanted_width + width
|
||||
if remaining_width > 0 then
|
||||
context.has_right_content = true
|
||||
if width > remaining_width then
|
||||
local truncated = truncate_layer_keep_right(layer.right, right_width, remaining_width)
|
||||
vim.list_extend(right, truncated)
|
||||
remaining_width = 0
|
||||
else
|
||||
remaining_width = remaining_width - width
|
||||
vim.list_extend(right, layer.right)
|
||||
right_width = right_width + width
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if utils.truthy(layer.left) then
|
||||
local width = calc_rendered_width(layer.left)
|
||||
wanted_width = wanted_width + width
|
||||
if remaining_width > 0 then
|
||||
if width > remaining_width then
|
||||
local truncated = truncate_layer_keep_left(layer.left, left_width, remaining_width)
|
||||
if context.enable_character_fade then
|
||||
try_fade_content(truncated, 3)
|
||||
end
|
||||
vim.list_extend(left, truncated)
|
||||
remaining_width = 0
|
||||
else
|
||||
remaining_width = remaining_width - width
|
||||
if context.enable_character_fade and not context.auto_expand_width then
|
||||
local fade_chars = 3 - remaining_width
|
||||
if fade_chars > 0 then
|
||||
try_fade_content(layer.left, fade_chars)
|
||||
end
|
||||
end
|
||||
vim.list_extend(left, layer.left)
|
||||
left_width = left_width + width
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if remaining_width == 0 and not context.auto_expand_width then
|
||||
i = 0
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if remaining_width > 0 and #right > 0 then
|
||||
table.insert(left, { text = string.rep(" ", remaining_width) })
|
||||
end
|
||||
|
||||
local result = {}
|
||||
vim.list_extend(result, left)
|
||||
|
||||
-- we do not pad between left and right side
|
||||
if #right >= 1 then
|
||||
right[1].no_padding = true
|
||||
end
|
||||
|
||||
vim.list_extend(result, right)
|
||||
context.merged_content = result
|
||||
log.trace("wanted width: ", wanted_width, " actual width: ", context.container_width)
|
||||
context.wanted_width = math.max(wanted_width, context.wanted_width)
|
||||
end
|
||||
|
||||
M.render = function(config, node, state, available_width)
|
||||
local context = {
|
||||
wanted_width = 0,
|
||||
max_width = 0,
|
||||
grouped_by_zindex = {},
|
||||
available_width = available_width,
|
||||
left_padding = config.left_padding,
|
||||
right_padding = config.right_padding,
|
||||
enable_character_fade = config.enable_character_fade,
|
||||
auto_expand_width = state.window.auto_expand_width and state.window.position ~= "float",
|
||||
}
|
||||
|
||||
render_content(config, node, state, context)
|
||||
calc_container_width(config, node, state, context)
|
||||
merge_content(context)
|
||||
|
||||
if context.has_right_content then
|
||||
state.has_right_content = true
|
||||
end
|
||||
|
||||
-- we still want padding between this container and the previous component
|
||||
if #context.merged_content > 0 then
|
||||
context.merged_content[1].no_padding = false
|
||||
end
|
||||
return context.merged_content, context.wanted_width
|
||||
end
|
||||
|
||||
return M
|
231
bundle/neo-tree.nvim/lua/neo-tree/sources/common/file-items.lua
vendored
Normal file
231
bundle/neo-tree.nvim/lua/neo-tree/sources/common/file-items.lua
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
local vim = vim
|
||||
local files_nesting = require("neo-tree.sources.common.file-nesting")
|
||||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
local git = require("neo-tree.git")
|
||||
|
||||
local unused_to_produce_diagnostic = {}
|
||||
|
||||
local function sort_items(a, b)
|
||||
if a.type == b.type then
|
||||
return a.path < b.path
|
||||
else
|
||||
return a.type < b.type
|
||||
end
|
||||
end
|
||||
|
||||
local function sort_items_case_insensitive(a, b)
|
||||
if a.type == b.type then
|
||||
return a.path:lower() < b.path:lower()
|
||||
else
|
||||
return a.type < b.type
|
||||
end
|
||||
end
|
||||
|
||||
local function sort_function_is_valid(func)
|
||||
if func == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
local a = { type = "dir", path = "foo" }
|
||||
local b = { type = "dir", path = "baz" }
|
||||
|
||||
local success, result = pcall(func, a, b)
|
||||
if success and type(result) == "boolean" then
|
||||
return true
|
||||
end
|
||||
|
||||
log.error("sort function isn't valid ", result)
|
||||
return false
|
||||
end
|
||||
|
||||
local function deep_sort(tbl, sort_func)
|
||||
if sort_func == nil then
|
||||
local config = require("neo-tree").config
|
||||
if sort_function_is_valid(config.sort_function) then
|
||||
sort_func = config.sort_function
|
||||
elseif config.sort_case_insensitive then
|
||||
sort_func = sort_items_case_insensitive
|
||||
else
|
||||
sort_func = sort_items
|
||||
end
|
||||
end
|
||||
table.sort(tbl, sort_func)
|
||||
for _, item in pairs(tbl) do
|
||||
if item.type == "directory" then
|
||||
deep_sort(item.children, sort_func)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local create_item, set_parents
|
||||
|
||||
function create_item(context, path, _type, bufnr)
|
||||
local parent_path, name = utils.split_path(path)
|
||||
local id = path
|
||||
if path == "[No Name]" and bufnr then
|
||||
parent_path = context.state.path
|
||||
name = "[No Name]"
|
||||
id = tostring(bufnr)
|
||||
else
|
||||
-- avoid creating duplicate items
|
||||
if context.folders[path] or context.nesting[path] then
|
||||
return context.folders[path] or context.nesting[path]
|
||||
end
|
||||
end
|
||||
|
||||
if _type == nil then
|
||||
local stat = vim.loop.fs_stat(path)
|
||||
_type = stat and stat.type or "unknown"
|
||||
end
|
||||
local item = {
|
||||
id = id,
|
||||
name = name,
|
||||
parent_path = parent_path,
|
||||
path = path,
|
||||
type = _type,
|
||||
}
|
||||
if item.type == "link" then
|
||||
item.is_link = true
|
||||
item.link_to = vim.loop.fs_realpath(path)
|
||||
if item.link_to ~= nil then
|
||||
item.type = vim.loop.fs_stat(item.link_to).type
|
||||
end
|
||||
end
|
||||
if item.type == "directory" then
|
||||
item.children = {}
|
||||
item.loaded = false
|
||||
context.folders[path] = item
|
||||
if context.state.search_pattern then
|
||||
table.insert(context.state.default_expanded_nodes, item.id)
|
||||
end
|
||||
else
|
||||
item.base = item.name:match("^([-_,()%s%w%i]+)%.")
|
||||
item.ext = item.name:match("%.([-_,()%s%w%i]+)$")
|
||||
item.exts = item.name:match("^[-_,()%s%w%i]+%.(.*)")
|
||||
|
||||
if files_nesting.can_have_nesting(item) then
|
||||
item.children = {}
|
||||
context.nesting[path] = item
|
||||
end
|
||||
end
|
||||
|
||||
item.is_reveal_target = (path == context.path_to_reveal)
|
||||
local state = context.state
|
||||
local f = state.filtered_items
|
||||
local is_not_root = not utils.is_subpath(path, context.state.path)
|
||||
if f and is_not_root then
|
||||
if f.never_show[name] then
|
||||
item.filtered_by = item.filtered_by or {}
|
||||
item.filtered_by.never_show = true
|
||||
else
|
||||
if utils.is_filtered_by_pattern(f.never_show_by_pattern, path, name) then
|
||||
item.filtered_by = item.filtered_by or {}
|
||||
item.filtered_by.never_show = true
|
||||
end
|
||||
end
|
||||
if f.always_show[name] then
|
||||
item.filtered_by = item.filtered_by or {}
|
||||
item.filtered_by.always_show = true
|
||||
end
|
||||
if f.hide_by_name[name] then
|
||||
item.filtered_by = item.filtered_by or {}
|
||||
item.filtered_by.name = true
|
||||
end
|
||||
if utils.is_filtered_by_pattern(f.hide_by_pattern, path, name) then
|
||||
item.filtered_by = item.filtered_by or {}
|
||||
item.filtered_by.pattern = true
|
||||
end
|
||||
if f.hide_dotfiles and string.sub(name, 1, 1) == "." then
|
||||
item.filtered_by = item.filtered_by or {}
|
||||
item.filtered_by.dotfiles = true
|
||||
end
|
||||
if f.hide_hidden and utils.is_hidden(path) then
|
||||
item.filtered_by = item.filtered_by or {}
|
||||
item.filtered_by.hidden = true
|
||||
end
|
||||
-- NOTE: git_ignored logic moved to job_complete
|
||||
end
|
||||
|
||||
set_parents(context, item)
|
||||
if context.all_items == nil then
|
||||
context.all_items = {}
|
||||
end
|
||||
if is_not_root then
|
||||
table.insert(context.all_items, item)
|
||||
end
|
||||
return item
|
||||
end
|
||||
|
||||
-- function to set (or create) parent folder
|
||||
function set_parents(context, item)
|
||||
-- we can get duplicate items if we navigate up with open folders
|
||||
-- this is probably hacky, but it works
|
||||
if context.item_exists[item.id] then
|
||||
return
|
||||
end
|
||||
if not item.parent_path then
|
||||
return
|
||||
end
|
||||
|
||||
local nesting_parent_path = files_nesting.get_parent(item)
|
||||
local nesting_parent = context.nesting[nesting_parent_path]
|
||||
|
||||
if
|
||||
nesting_parent_path
|
||||
and not nesting_parent
|
||||
and utils.truthy(vim.loop.fs_stat(nesting_parent_path))
|
||||
then
|
||||
local success
|
||||
success, nesting_parent = pcall(create_item, context, nesting_parent_path)
|
||||
if not success then
|
||||
log.error("error, creating item for ", nesting_parent_path)
|
||||
end
|
||||
end
|
||||
|
||||
local parent = context.folders[item.parent_path]
|
||||
if not utils.truthy(item.parent_path) then
|
||||
return
|
||||
end
|
||||
if parent == nil and nesting_parent == nil then
|
||||
local success
|
||||
success, parent = pcall(create_item, context, item.parent_path, "directory")
|
||||
if not success then
|
||||
log.error("error creating item for ", item.parent_path)
|
||||
end
|
||||
context.folders[parent.id] = parent
|
||||
set_parents(context, parent)
|
||||
end
|
||||
if nesting_parent then
|
||||
table.insert(nesting_parent.children, item)
|
||||
item.is_nested = true
|
||||
else
|
||||
table.insert(parent.children, item)
|
||||
end
|
||||
context.item_exists[item.id] = true
|
||||
|
||||
if item.filtered_by == nil and type(parent.filtered_by) == "table" then
|
||||
item.filtered_by = vim.deepcopy(parent.filtered_by)
|
||||
end
|
||||
end
|
||||
|
||||
---Create context to be used in other file-items functions.
|
||||
---@param state table|nil The state of the file-items.
|
||||
---@return table
|
||||
local create_context = function(state)
|
||||
local context = {}
|
||||
-- Make the context a weak table so that it can be garbage collected
|
||||
--setmetatable(context, { __mode = 'v' })
|
||||
context.state = state
|
||||
context.folders = {}
|
||||
context.nesting = {}
|
||||
context.item_exists = {}
|
||||
context.all_items = {}
|
||||
return context
|
||||
end
|
||||
|
||||
return {
|
||||
create_context = create_context,
|
||||
create_item = create_item,
|
||||
deep_sort = deep_sort,
|
||||
}
|
54
bundle/neo-tree.nvim/lua/neo-tree/sources/common/file-nesting.lua
vendored
Normal file
54
bundle/neo-tree.nvim/lua/neo-tree/sources/common/file-nesting.lua
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
local iter = require("plenary.iterators").iter
|
||||
local utils = require("neo-tree.utils")
|
||||
local Path = require("plenary.path")
|
||||
|
||||
-- File nesting a la JetBrains (#117).
|
||||
local M = {}
|
||||
M.config = {}
|
||||
|
||||
--- Checks if file-nesting module is enabled by config
|
||||
---@return boolean
|
||||
function M.is_enabled()
|
||||
return next(M.config) ~= nil
|
||||
end
|
||||
|
||||
--- Returns `item` nesting parent path if exists
|
||||
---@return string?
|
||||
function M.get_parent(item)
|
||||
for base_exts, nesting_exts in pairs(M.config) do
|
||||
for _, exts in ipairs(nesting_exts) do
|
||||
if item.exts == exts then
|
||||
local parent_id = utils.path_join(item.parent_path, item.base) .. "." .. base_exts
|
||||
if Path:new(parent_id):exists() then
|
||||
return parent_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Checks if `item` have a valid nesting lookup
|
||||
---@return boolean
|
||||
function M.can_have_nesting(item)
|
||||
return utils.truthy(M.config[item.exts])
|
||||
end
|
||||
|
||||
--- Checks if `target` should be nested into `base`
|
||||
---@return boolean
|
||||
function M.should_nest_file(base, target)
|
||||
local ext_lookup = M.config[base.exts]
|
||||
|
||||
return utils.truthy(
|
||||
base.base == target.base and ext_lookup and iter(ext_lookup):find(target.exts)
|
||||
)
|
||||
end
|
||||
|
||||
---Setup the module with the given config
|
||||
---@param config table
|
||||
function M.setup(config)
|
||||
M.config = config or {}
|
||||
end
|
||||
|
||||
return M
|
246
bundle/neo-tree.nvim/lua/neo-tree/sources/common/filters/filter_fzy.lua
vendored
Normal file
246
bundle/neo-tree.nvim/lua/neo-tree/sources/common/filters/filter_fzy.lua
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
-- The lua implementation of the fzy string matching algorithm
|
||||
-- credits to: https://github.com/swarn/fzy-lua
|
||||
--[[
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Seth Warn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
--]]
|
||||
-- modified by: @pysan3 (2023)
|
||||
|
||||
local SCORE_GAP_LEADING = -0.005
|
||||
local SCORE_GAP_TRAILING = -0.005
|
||||
local SCORE_GAP_INNER = -0.01
|
||||
local SCORE_MATCH_CONSECUTIVE = 1.0
|
||||
local SCORE_MATCH_SLASH = 0.9
|
||||
local SCORE_MATCH_WORD = 0.8
|
||||
local SCORE_MATCH_CAPITAL = 0.7
|
||||
local SCORE_MATCH_DOT = 0.6
|
||||
local SCORE_MAX = math.huge
|
||||
local SCORE_MIN = -math.huge
|
||||
local MATCH_MAX_LENGTH = 1024
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Return `true` if `needle` is a subsequence of `haystack`.
|
||||
function M.has_match(needle, haystack, case_sensitive)
|
||||
if not case_sensitive then
|
||||
needle = string.lower(needle)
|
||||
haystack = string.lower(haystack)
|
||||
end
|
||||
|
||||
local j = 1
|
||||
for i = 1, string.len(needle) do
|
||||
j = string.find(haystack, needle:sub(i, i), j, true)
|
||||
if not j then
|
||||
return false
|
||||
else
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function is_lower(c)
|
||||
return c:match('%l')
|
||||
end
|
||||
|
||||
local function is_upper(c)
|
||||
return c:match('%u')
|
||||
end
|
||||
|
||||
local function precompute_bonus(haystack)
|
||||
local match_bonus = {}
|
||||
|
||||
local last_char = '/'
|
||||
for i = 1, string.len(haystack) do
|
||||
local this_char = haystack:sub(i, i)
|
||||
if last_char == '/' or last_char == '\\' then
|
||||
match_bonus[i] = SCORE_MATCH_SLASH
|
||||
elseif last_char == '-' or last_char == '_' or last_char == ' ' then
|
||||
match_bonus[i] = SCORE_MATCH_WORD
|
||||
elseif last_char == '.' then
|
||||
match_bonus[i] = SCORE_MATCH_DOT
|
||||
elseif is_lower(last_char) and is_upper(this_char) then
|
||||
match_bonus[i] = SCORE_MATCH_CAPITAL
|
||||
else
|
||||
match_bonus[i] = 0
|
||||
end
|
||||
|
||||
last_char = this_char
|
||||
end
|
||||
|
||||
return match_bonus
|
||||
end
|
||||
|
||||
local function compute(needle, haystack, D, T, case_sensitive)
|
||||
-- Note that the match bonuses must be computed before the arguments are
|
||||
-- converted to lowercase, since there are bonuses for camelCase.
|
||||
local match_bonus = precompute_bonus(haystack)
|
||||
local n = string.len(needle)
|
||||
local m = string.len(haystack)
|
||||
|
||||
if not case_sensitive then
|
||||
needle = string.lower(needle)
|
||||
haystack = string.lower(haystack)
|
||||
end
|
||||
|
||||
-- Because lua only grants access to chars through substring extraction,
|
||||
-- get all the characters from the haystack once now, to reuse below.
|
||||
local haystack_chars = {}
|
||||
for i = 1, m do
|
||||
haystack_chars[i] = haystack:sub(i, i)
|
||||
end
|
||||
|
||||
for i = 1, n do
|
||||
D[i] = {}
|
||||
T[i] = {}
|
||||
|
||||
local prev_score = SCORE_MIN
|
||||
local gap_score = i == n and SCORE_GAP_TRAILING or SCORE_GAP_INNER
|
||||
local needle_char = needle:sub(i, i)
|
||||
|
||||
for j = 1, m do
|
||||
if needle_char == haystack_chars[j] then
|
||||
local score = SCORE_MIN
|
||||
if i == 1 then
|
||||
score = ((j - 1) * SCORE_GAP_LEADING) + match_bonus[j]
|
||||
elseif j > 1 then
|
||||
local a = T[i - 1][j - 1] + match_bonus[j]
|
||||
local b = D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE
|
||||
score = math.max(a, b)
|
||||
end
|
||||
D[i][j] = score
|
||||
prev_score = math.max(score, prev_score + gap_score)
|
||||
T[i][j] = prev_score
|
||||
else
|
||||
D[i][j] = SCORE_MIN
|
||||
prev_score = prev_score + gap_score
|
||||
T[i][j] = prev_score
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Compute a matching score for two strings.
|
||||
--
|
||||
-- Where `needle` is a subsequence of `haystack`, this returns a score
|
||||
-- measuring the quality of their match. Better matches get higher scores.
|
||||
--
|
||||
-- `needle` must be a subsequence of `haystack`, the result is undefined
|
||||
-- otherwise. Call `has_match()` before calling `score`.
|
||||
--
|
||||
-- returns `get_score_min()` where a or b are longer than `get_max_length()`
|
||||
--
|
||||
-- returns `get_score_min()` when a or b are empty strings.
|
||||
--
|
||||
-- returns `get_score_max()` when a and b are the same string.
|
||||
--
|
||||
-- When the return value is not covered by the above rules, it is a number
|
||||
-- in the range (`get_score_floor()`, `get_score_ceiling()`)
|
||||
function M.score(needle, haystack, case_sensitive)
|
||||
local n = string.len(needle)
|
||||
local m = string.len(haystack)
|
||||
|
||||
if n == 0 or m == 0 or m > MATCH_MAX_LENGTH or n > MATCH_MAX_LENGTH then
|
||||
return SCORE_MIN
|
||||
elseif n == m then
|
||||
return SCORE_MAX
|
||||
else
|
||||
local D = {}
|
||||
local T = {}
|
||||
compute(needle, haystack, D, T, case_sensitive)
|
||||
return T[n][m]
|
||||
end
|
||||
end
|
||||
|
||||
-- Find the locations where fzy matched a string.
|
||||
--
|
||||
-- Returns {score, indices}, where indices is an array showing where each
|
||||
-- character of the needle matches the haystack in the best match.
|
||||
function M.score_and_positions(needle, haystack, case_sensitive)
|
||||
local n = string.len(needle)
|
||||
local m = string.len(haystack)
|
||||
|
||||
if n == 0 or m == 0 or m > MATCH_MAX_LENGTH or n > MATCH_MAX_LENGTH then
|
||||
return SCORE_MIN, {}
|
||||
elseif n == m then
|
||||
local consecutive = {}
|
||||
for i = 1, n do
|
||||
consecutive[i] = i
|
||||
end
|
||||
return SCORE_MAX, consecutive
|
||||
end
|
||||
|
||||
local D = {}
|
||||
local T = {}
|
||||
compute(needle, haystack, D, T, case_sensitive)
|
||||
|
||||
local positions = {}
|
||||
local match_required = false
|
||||
local j = m
|
||||
for i = n, 1, -1 do
|
||||
while j >= 1 do
|
||||
if D[i][j] ~= SCORE_MIN and (match_required or D[i][j] == T[i][j]) then
|
||||
match_required = (i ~= 1) and (j ~= 1) and (T[i][j] == D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE)
|
||||
positions[i] = j
|
||||
j = j - 1
|
||||
break
|
||||
else
|
||||
j = j - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return T[n][m], positions
|
||||
end
|
||||
|
||||
-- Return only the positions of a match.
|
||||
function M.positions(needle, haystack, case_sensitive)
|
||||
local _, positions = M.score_and_positions(needle, haystack, case_sensitive)
|
||||
return positions
|
||||
end
|
||||
|
||||
function M.get_score_min()
|
||||
return SCORE_MIN
|
||||
end
|
||||
|
||||
function M.get_score_max()
|
||||
return SCORE_MAX
|
||||
end
|
||||
|
||||
function M.get_max_length()
|
||||
return MATCH_MAX_LENGTH
|
||||
end
|
||||
|
||||
function M.get_score_floor()
|
||||
return MATCH_MAX_LENGTH * SCORE_GAP_INNER
|
||||
end
|
||||
|
||||
function M.get_score_ceiling()
|
||||
return MATCH_MAX_LENGTH * SCORE_MATCH_CONSECUTIVE
|
||||
end
|
||||
|
||||
function M.get_implementation_name()
|
||||
return 'lua'
|
||||
end
|
||||
|
||||
return M
|
225
bundle/neo-tree.nvim/lua/neo-tree/sources/common/filters/init.lua
vendored
Normal file
225
bundle/neo-tree.nvim/lua/neo-tree/sources/common/filters/init.lua
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
---A generalization of the filter functionality to directly filter the
|
||||
---source tree instead of relying on pre-filtered data, which is specific
|
||||
---to the filesystem source.
|
||||
local vim = vim
|
||||
local Input = require("nui.input")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
local popups = require("neo-tree.ui.popups")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local fzy = require("neo-tree.sources.common.filters.filter_fzy")
|
||||
|
||||
local M = {}
|
||||
|
||||
local cmds = {
|
||||
move_cursor_down = function(state, scroll_padding)
|
||||
renderer.focus_node(state, nil, true, 1, scroll_padding)
|
||||
end,
|
||||
|
||||
move_cursor_up = function(state, scroll_padding)
|
||||
renderer.focus_node(state, nil, true, -1, scroll_padding)
|
||||
vim.cmd("redraw!")
|
||||
end,
|
||||
}
|
||||
|
||||
---Reset the current filter to the empty string.
|
||||
---@param state any
|
||||
---@param refresh boolean? whether to refresh the source tree
|
||||
---@param open_current_node boolean? whether to open the current node
|
||||
local reset_filter = function(state, refresh, open_current_node)
|
||||
log.trace("reset_search")
|
||||
if refresh == nil then
|
||||
refresh = true
|
||||
end
|
||||
|
||||
-- Cancel any pending search
|
||||
require("neo-tree.sources.filesystem.lib.filter_external").cancel()
|
||||
|
||||
-- reset search state
|
||||
if state.open_folders_before_search then
|
||||
state.force_open_folders = vim.deepcopy(state.open_folders_before_search, { noref = 1 })
|
||||
else
|
||||
state.force_open_folders = nil
|
||||
end
|
||||
state.open_folders_before_search = nil
|
||||
state.search_pattern = nil
|
||||
|
||||
if open_current_node then
|
||||
local success, node = pcall(state.tree.get_node, state.tree)
|
||||
if success and node then
|
||||
local id = node:get_id()
|
||||
renderer.position.set(state, id)
|
||||
id = utils.remove_trailing_slash(id)
|
||||
manager.navigate(state, nil, id, utils.wrap(pcall, renderer.focus_node, state, id, false))
|
||||
end
|
||||
elseif refresh then
|
||||
manager.navigate(state)
|
||||
else
|
||||
state.tree = vim.deepcopy(state.orig_tree)
|
||||
end
|
||||
state.orig_tree = nil
|
||||
end
|
||||
|
||||
---Show the filtered tree
|
||||
---@param state any
|
||||
---@param do_not_focus_window boolean? whether to focus the window
|
||||
local show_filtered_tree = function(state, do_not_focus_window)
|
||||
state.tree = vim.deepcopy(state.orig_tree)
|
||||
state.tree:get_nodes()[1].search_pattern = state.search_pattern
|
||||
local max_score, max_id = fzy.get_score_min(), nil
|
||||
local function filter_tree(node_id)
|
||||
local node = state.tree:get_node(node_id)
|
||||
local path = node.extra.search_path or node.path
|
||||
|
||||
local should_keep = fzy.has_match(state.search_pattern, path)
|
||||
if should_keep then
|
||||
local score = fzy.score(state.search_pattern, path)
|
||||
node.extra.fzy_score = score
|
||||
if score > max_score then
|
||||
max_score = score
|
||||
max_id = node_id
|
||||
end
|
||||
end
|
||||
|
||||
if node:has_children() then
|
||||
for _, child_id in ipairs(node:get_child_ids()) do
|
||||
should_keep = filter_tree(child_id) or should_keep
|
||||
end
|
||||
end
|
||||
if not should_keep then
|
||||
state.tree:remove_node(node_id) -- TODO: this might not be efficient
|
||||
end
|
||||
return should_keep
|
||||
end
|
||||
if #state.search_pattern > 0 then
|
||||
for _, root in ipairs(state.tree:get_nodes()) do
|
||||
filter_tree(root:get_id())
|
||||
end
|
||||
end
|
||||
manager.redraw(state.name)
|
||||
if max_id then
|
||||
renderer.focus_node(state, max_id, do_not_focus_window)
|
||||
end
|
||||
end
|
||||
|
||||
---Main entry point for the filter functionality.
|
||||
---This will display a filter input popup and filter the source tree on change and on submit
|
||||
---@param state table the source state
|
||||
---@param search_as_you_type boolean? whether to filter as you type or only on submit
|
||||
---@param keep_filter_on_submit boolean? whether to keep the filter on <CR> or reset it
|
||||
M.show_filter = function(state, search_as_you_type, keep_filter_on_submit)
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local height = vim.api.nvim_win_get_height(winid)
|
||||
local scroll_padding = 3
|
||||
|
||||
-- setup the input popup options
|
||||
local popup_msg = "Search:"
|
||||
if search_as_you_type then
|
||||
popup_msg = "Filter:"
|
||||
end
|
||||
|
||||
local width = vim.fn.winwidth(0) - 2
|
||||
local row = height - 3
|
||||
if state.current_position == "float" then
|
||||
scroll_padding = 0
|
||||
width = vim.fn.winwidth(winid)
|
||||
row = height - 2
|
||||
vim.api.nvim_win_set_height(winid, row)
|
||||
end
|
||||
|
||||
state.orig_tree = vim.deepcopy(state.tree)
|
||||
|
||||
local popup_options = popups.popup_options(popup_msg, width, {
|
||||
relative = "win",
|
||||
winid = winid,
|
||||
position = {
|
||||
row = row,
|
||||
col = 0,
|
||||
},
|
||||
size = width,
|
||||
})
|
||||
|
||||
local has_pre_search_folders = utils.truthy(state.open_folders_before_search)
|
||||
if not has_pre_search_folders then
|
||||
log.trace("No search or pre-search folders, recording pre-search folders now")
|
||||
state.open_folders_before_search = renderer.get_expanded_nodes(state.tree)
|
||||
end
|
||||
|
||||
local waiting_for_default_value = utils.truthy(state.search_pattern)
|
||||
local input = Input(popup_options, {
|
||||
prompt = " ",
|
||||
default_value = state.search_pattern,
|
||||
on_submit = function(value)
|
||||
if value == "" then
|
||||
reset_filter(state)
|
||||
return
|
||||
end
|
||||
if search_as_you_type and not keep_filter_on_submit then
|
||||
reset_filter(state, true, true)
|
||||
return
|
||||
end
|
||||
-- do the search
|
||||
state.search_pattern = value
|
||||
show_filtered_tree(state, false)
|
||||
end,
|
||||
--this can be bad in a deep folder structure
|
||||
on_change = function(value)
|
||||
if not search_as_you_type then
|
||||
return
|
||||
end
|
||||
-- apparently when a default value is set, on_change fires for every character
|
||||
if waiting_for_default_value then
|
||||
if #value < #state.search_pattern then
|
||||
return
|
||||
end
|
||||
waiting_for_default_value = false
|
||||
end
|
||||
if value == state.search_pattern or value == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- finally do the search
|
||||
log.trace("Setting search in on_change to: " .. value)
|
||||
state.search_pattern = value
|
||||
local len_to_delay = { [0] = 500, 500, 400, 200 }
|
||||
local delay = len_to_delay[#value] or 100
|
||||
|
||||
utils.debounce(state.name .. "_filter", function()
|
||||
show_filtered_tree(state, true)
|
||||
end, delay, utils.debounce_strategy.CALL_LAST_ONLY)
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
|
||||
local restore_height = vim.schedule_wrap(function()
|
||||
if vim.api.nvim_win_is_valid(winid) then
|
||||
vim.api.nvim_win_set_height(winid, height)
|
||||
end
|
||||
end)
|
||||
|
||||
-- create mappings and autocmd
|
||||
input:map("i", "<C-w>", "<C-S-w>", { noremap = true })
|
||||
input:map("i", "<esc>", function(bufnr)
|
||||
vim.cmd("stopinsert")
|
||||
input:unmount()
|
||||
if utils.truthy(state.search_pattern) then
|
||||
reset_filter(state, true)
|
||||
end
|
||||
restore_height()
|
||||
end, { noremap = true })
|
||||
|
||||
local config = require("neo-tree").config
|
||||
for lhs, cmd_name in pairs(config.filesystem.window.fuzzy_finder_mappings) do
|
||||
local cmd = cmds[cmd_name]
|
||||
if cmd then
|
||||
input:map("i", lhs, utils.wrap(cmd, state, scroll_padding), { noremap = true })
|
||||
else
|
||||
log.warn(string.format("Invalid command in fuzzy_finder_mappings: %s = %s", lhs, cmd_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
93
bundle/neo-tree.nvim/lua/neo-tree/sources/common/help.lua
vendored
Normal file
93
bundle/neo-tree.nvim/lua/neo-tree/sources/common/help.lua
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
local Popup = require("nui.popup")
|
||||
local NuiLine = require("nui.line")
|
||||
local utils = require("neo-tree.utils")
|
||||
local popups = require("neo-tree.ui.popups")
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local M = {}
|
||||
|
||||
local add_text = function(text, highlight)
|
||||
local line = NuiLine()
|
||||
line:append(text, highlight)
|
||||
return line
|
||||
end
|
||||
|
||||
M.show = function(state)
|
||||
local tree_width = vim.api.nvim_win_get_width(state.winid)
|
||||
local keys = utils.get_keys(state.resolved_mappings, true)
|
||||
|
||||
local lines = { add_text("") }
|
||||
lines[1] = add_text(" Press the corresponding key to execute the command.", "Comment")
|
||||
lines[2] = add_text(" Press <Esc> to cancel.", "Comment")
|
||||
lines[3] = add_text("")
|
||||
local header = NuiLine()
|
||||
header:append(string.format(" %14s", "KEY(S)"), highlights.ROOT_NAME)
|
||||
header:append(" ", highlights.DIM_TEXT)
|
||||
header:append("COMMAND", highlights.ROOT_NAME)
|
||||
lines[4] = header
|
||||
local max_width = #lines[1]:content()
|
||||
for _, key in ipairs(keys) do
|
||||
local value = state.resolved_mappings[key]
|
||||
local nline = NuiLine()
|
||||
nline:append(string.format(" %14s", key), highlights.FILTER_TERM)
|
||||
nline:append(" -> ", highlights.DIM_TEXT)
|
||||
nline:append(value.text, highlights.NORMAL)
|
||||
local line = nline:content()
|
||||
if #line > max_width then
|
||||
max_width = #line
|
||||
end
|
||||
table.insert(lines, nline)
|
||||
end
|
||||
|
||||
local width = math.min(60, max_width + 1)
|
||||
|
||||
if state.current_position == "right" then
|
||||
col = vim.o.columns - tree_width - width - 1
|
||||
else
|
||||
col = tree_width - 1
|
||||
end
|
||||
|
||||
local options = {
|
||||
position = {
|
||||
row = 2,
|
||||
col = col,
|
||||
},
|
||||
size = {
|
||||
width = width,
|
||||
height = #keys + 5,
|
||||
},
|
||||
enter = true,
|
||||
focusable = true,
|
||||
zindex = 50,
|
||||
relative = "editor",
|
||||
}
|
||||
local options = popups.popup_options("Neotree Help", width, options)
|
||||
local popup = Popup(options)
|
||||
popup:mount()
|
||||
|
||||
popup:map("n", "<esc>", function()
|
||||
popup:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
local event = require("nui.utils.autocmd").event
|
||||
popup:on({ event.BufLeave, event.BufDelete }, function()
|
||||
popup:unmount()
|
||||
end, { once = true })
|
||||
|
||||
for _, key in ipairs(keys) do
|
||||
-- map everything except for <escape>
|
||||
if string.match(key:lower(), "^<esc") == nil then
|
||||
local value = state.resolved_mappings[key]
|
||||
popup:map("n", key, function()
|
||||
popup:unmount()
|
||||
vim.api.nvim_set_current_win(state.winid)
|
||||
value.handler()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
line:render(popup.bufnr, -1, i)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
419
bundle/neo-tree.nvim/lua/neo-tree/sources/common/preview.lua
vendored
Normal file
419
bundle/neo-tree.nvim/lua/neo-tree/sources/common/preview.lua
vendored
Normal file
@ -0,0 +1,419 @@
|
||||
local vim = vim
|
||||
local utils = require("neo-tree.utils")
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local events = require("neo-tree.events")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local log = require("neo-tree.log")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
|
||||
local neo_tree_preview_namespace = vim.api.nvim_create_namespace("neo_tree_preview")
|
||||
|
||||
local function create_floating_preview_window(state)
|
||||
local default_position = utils.resolve_config_option(state, "window.position", "left")
|
||||
state.current_position = state.current_position or default_position
|
||||
|
||||
local winwidth = vim.api.nvim_win_get_width(state.winid)
|
||||
local winheight = vim.api.nvim_win_get_height(state.winid)
|
||||
local height = vim.o.lines - 4
|
||||
local width = 120
|
||||
local row, col = 0, 0
|
||||
|
||||
if state.current_position == "left" then
|
||||
col = winwidth + 1
|
||||
width = math.min(vim.o.columns - col, 120)
|
||||
elseif state.current_position == "top" or state.current_position == "bottom" then
|
||||
height = height - winheight
|
||||
width = winwidth - 2
|
||||
if state.current_position == "top" then
|
||||
row = vim.api.nvim_win_get_height(state.winid) + 1
|
||||
end
|
||||
elseif state.current_position == "right" then
|
||||
width = math.min(vim.o.columns - winwidth - 4, 120)
|
||||
col = vim.o.columns - winwidth - width - 3
|
||||
elseif state.current_position == "float" then
|
||||
local pos = vim.api.nvim_win_get_position(state.winid)
|
||||
-- preview will be same height and top as tree
|
||||
row = pos[1] - 1
|
||||
height = winheight
|
||||
|
||||
-- tree and preview window will be side by side and centered in the editor
|
||||
width = math.min(vim.o.columns - winwidth - 4, 120)
|
||||
local total_width = winwidth + width + 4
|
||||
local margin = math.floor((vim.o.columns - total_width) / 2)
|
||||
col = margin + winwidth + 2
|
||||
|
||||
-- move the tree window to make the combined layout centered
|
||||
local popup = renderer.get_nui_popup(state.winid)
|
||||
popup:update_layout({
|
||||
relative = "editor",
|
||||
position = {
|
||||
row = row,
|
||||
col = margin,
|
||||
},
|
||||
})
|
||||
else
|
||||
local cur_pos = state.current_position or "unknown"
|
||||
log.error('Preview cannot be used when position = "' .. cur_pos .. '"')
|
||||
return
|
||||
end
|
||||
|
||||
local popups = require("neo-tree.ui.popups")
|
||||
local options = popups.popup_options("Neo-tree Preview", width, {
|
||||
ns_id = highlights.ns_id,
|
||||
size = { height = height, width = width },
|
||||
relative = "editor",
|
||||
position = {
|
||||
row = row,
|
||||
col = col,
|
||||
},
|
||||
win_options = {
|
||||
number = true,
|
||||
winhighlight = "Normal:"
|
||||
.. highlights.FLOAT_NORMAL
|
||||
.. ",FloatBorder:"
|
||||
.. highlights.FLOAT_BORDER,
|
||||
},
|
||||
})
|
||||
options.zindex = 40
|
||||
options.buf_options.filetype = "neo-tree-preview"
|
||||
|
||||
local NuiPopup = require("nui.popup")
|
||||
local win = NuiPopup(options)
|
||||
win:mount()
|
||||
return win
|
||||
end
|
||||
|
||||
local Preview = {}
|
||||
local instance = nil
|
||||
|
||||
---Creates a new preview.
|
||||
---@param state table The state of the source.
|
||||
---@return table preview A new preview. A preview is a table consisting of the following keys:
|
||||
-- active = boolean Whether the preview is active.
|
||||
-- winid = number The id of the window being used to preview.
|
||||
-- is_neo_tree_window boolean Whether the preview window belongs to neo-tree.
|
||||
-- bufnr = number The buffer that is currently in the preview window.
|
||||
-- start_pos = array or nil An array-like table specifying the (0-indexed) starting position of the previewed text.
|
||||
-- end_pos = array or nil An array-like table specifying the (0-indexed) ending position of the preview text.
|
||||
-- truth = table A table containing information to be restored when the preview ends.
|
||||
-- events = array A list of events the preview is subscribed to.
|
||||
--These keys should not be altered directly. Note that the keys `start_pos`, `end_pos` and `truth`
|
||||
--may be inaccurate if `active` is false.
|
||||
function Preview:new(state)
|
||||
local preview = {}
|
||||
preview.active = false
|
||||
preview.config = vim.deepcopy(state.config)
|
||||
setmetatable(preview, { __index = self })
|
||||
preview:findWindow(state)
|
||||
return preview
|
||||
end
|
||||
|
||||
---Preview a buffer in the preview window and optionally reveal and highlight the previewed text.
|
||||
---@param bufnr number? The number of the buffer to be previewed.
|
||||
---@param start_pos table? The (0-indexed) starting position of the previewed text. May be absent.
|
||||
---@param end_pos table? The (0-indexed) ending position of the previewed text. May be absent
|
||||
function Preview:preview(bufnr, start_pos, end_pos)
|
||||
if self.is_neo_tree_window then
|
||||
log.warn("Could not find appropriate window for preview")
|
||||
return
|
||||
end
|
||||
|
||||
bufnr = bufnr or self.bufnr
|
||||
if not self.active then
|
||||
self:activate()
|
||||
end
|
||||
|
||||
if not self.active then
|
||||
return
|
||||
end
|
||||
|
||||
if bufnr ~= self.bufnr then
|
||||
self:setBuffer(bufnr)
|
||||
end
|
||||
|
||||
self:clearHighlight()
|
||||
|
||||
self.bufnr = bufnr
|
||||
self.start_pos = start_pos
|
||||
self.end_pos = end_pos
|
||||
|
||||
self:reveal()
|
||||
self:highlight()
|
||||
end
|
||||
|
||||
---Reverts the preview and inactivates it, restoring the preview window to its previous state.
|
||||
function Preview:revert()
|
||||
self.active = false
|
||||
self:unsubscribe()
|
||||
self:clearHighlight()
|
||||
|
||||
if not renderer.is_window_valid(self.winid) then
|
||||
self.winid = nil
|
||||
return
|
||||
end
|
||||
|
||||
if self.config.use_float then
|
||||
vim.api.nvim_win_close(self.winid, true)
|
||||
self.winid = nil
|
||||
return
|
||||
else
|
||||
local foldenable = utils.get_value(self.truth, "options.foldenable", nil, false)
|
||||
if foldenable ~= nil then
|
||||
vim.api.nvim_win_set_option(self.winid, "foldenable", self.truth.options.foldenable)
|
||||
end
|
||||
vim.api.nvim_win_set_var(self.winid, "neo_tree_preview", 0)
|
||||
end
|
||||
|
||||
local bufnr = self.truth.bufnr
|
||||
if type(bufnr) ~= "number" then
|
||||
return
|
||||
end
|
||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
self:setBuffer(bufnr)
|
||||
self.bufnr = bufnr
|
||||
if vim.api.nvim_win_is_valid(self.winid) then
|
||||
vim.api.nvim_win_call(self.winid, function()
|
||||
vim.fn.winrestview(self.truth.view)
|
||||
end)
|
||||
end
|
||||
vim.api.nvim_buf_set_option(self.bufnr, "bufhidden", self.truth.options.bufhidden)
|
||||
end
|
||||
|
||||
---Subscribe to event and add it to the preview event list.
|
||||
--@param source string? Name of the source to add the event to. Will use `events.subscribe` if nil.
|
||||
--@param event table Event to subscribe to.
|
||||
function Preview:subscribe(source, event)
|
||||
if source == nil then
|
||||
events.subscribe(event)
|
||||
else
|
||||
manager.subscribe(source, event)
|
||||
end
|
||||
self.events = self.events or {}
|
||||
table.insert(self.events, { source = source, event = event })
|
||||
end
|
||||
|
||||
---Unsubscribe to all events in the preview event list.
|
||||
function Preview:unsubscribe()
|
||||
if self.events == nil then
|
||||
return
|
||||
end
|
||||
for _, event in ipairs(self.events) do
|
||||
if event.source == nil then
|
||||
events.unsubscribe(event.event)
|
||||
else
|
||||
manager.unsubscribe(event.source, event.event)
|
||||
end
|
||||
end
|
||||
self.events = {}
|
||||
end
|
||||
|
||||
---Finds the appropriate window and updates the preview accordingly.
|
||||
---@param state table The state of the source.
|
||||
function Preview:findWindow(state)
|
||||
local winid, is_neo_tree_window
|
||||
if self.config.use_float then
|
||||
if
|
||||
type(self.winid) == "number"
|
||||
and vim.api.nvim_win_is_valid(self.winid)
|
||||
and utils.is_floating(self.winid)
|
||||
then
|
||||
return
|
||||
end
|
||||
local win = create_floating_preview_window(state)
|
||||
if not win then
|
||||
self.active = false
|
||||
return
|
||||
end
|
||||
winid = win.winid
|
||||
is_neo_tree_window = false
|
||||
else
|
||||
winid, is_neo_tree_window = utils.get_appropriate_window(state)
|
||||
self.bufnr = vim.api.nvim_win_get_buf(winid)
|
||||
end
|
||||
|
||||
if winid == self.winid then
|
||||
return
|
||||
end
|
||||
self.winid, self.is_neo_tree_window = winid, is_neo_tree_window
|
||||
|
||||
if self.active then
|
||||
self:revert()
|
||||
self:preview()
|
||||
end
|
||||
end
|
||||
|
||||
---Activates the preview, but does not populate the preview window,
|
||||
function Preview:activate()
|
||||
if self.active then
|
||||
return
|
||||
end
|
||||
if not renderer.is_window_valid(self.winid) then
|
||||
return
|
||||
end
|
||||
if self.config.use_float then
|
||||
self.truth = {}
|
||||
else
|
||||
self.truth = {
|
||||
bufnr = self.bufnr,
|
||||
view = vim.api.nvim_win_call(self.winid, vim.fn.winsaveview),
|
||||
options = {
|
||||
bufhidden = vim.api.nvim_buf_get_option(self.bufnr, "bufhidden"),
|
||||
foldenable = vim.api.nvim_win_get_option(self.winid, "foldenable"),
|
||||
},
|
||||
}
|
||||
vim.api.nvim_buf_set_option(self.bufnr, "bufhidden", "hide")
|
||||
vim.api.nvim_win_set_option(self.winid, "foldenable", false)
|
||||
end
|
||||
self.active = true
|
||||
vim.api.nvim_win_set_var(self.winid, "neo_tree_preview", 1)
|
||||
end
|
||||
|
||||
---Set the buffer in the preview window without executing BufEnter or BufWinEnter autocommands.
|
||||
--@param bufnr number The buffer number of the buffer to set.
|
||||
function Preview:setBuffer(bufnr)
|
||||
local eventignore = vim.opt.eventignore
|
||||
vim.opt.eventignore:append("BufEnter,BufWinEnter")
|
||||
vim.api.nvim_win_set_buf(self.winid, bufnr)
|
||||
if self.config.use_float then
|
||||
-- I'm not sufe why float windows won;t show numbers without this
|
||||
vim.api.nvim_win_set_option(self.winid, "number", true)
|
||||
end
|
||||
vim.opt.eventignore = eventignore
|
||||
end
|
||||
|
||||
---Move the cursor to the previewed position and center the screen.
|
||||
function Preview:reveal()
|
||||
local pos = self.start_pos or self.end_pos
|
||||
if not self.active or not self.winid or not pos then
|
||||
return
|
||||
end
|
||||
vim.api.nvim_win_set_cursor(self.winid, { (pos[1] or 0) + 1, pos[2] or 0 })
|
||||
vim.api.nvim_win_call(self.winid, function()
|
||||
vim.cmd("normal! zz")
|
||||
end)
|
||||
end
|
||||
|
||||
---Highlight the previewed range
|
||||
function Preview:highlight()
|
||||
if not self.active or not self.bufnr then
|
||||
return
|
||||
end
|
||||
local start_pos, end_pos = self.start_pos, self.end_pos
|
||||
if not start_pos and not end_pos then
|
||||
return
|
||||
elseif not start_pos then
|
||||
start_pos = end_pos
|
||||
elseif not end_pos then
|
||||
end_pos = start_pos
|
||||
end
|
||||
|
||||
local highlight = function(line, col_start, col_end)
|
||||
vim.api.nvim_buf_add_highlight(
|
||||
self.bufnr,
|
||||
neo_tree_preview_namespace,
|
||||
highlights.PREVIEW,
|
||||
line,
|
||||
col_start,
|
||||
col_end
|
||||
)
|
||||
end
|
||||
|
||||
local start_line, end_line = start_pos[1], end_pos[1]
|
||||
local start_col, end_col = start_pos[2], end_pos[2]
|
||||
if start_line == end_line then
|
||||
highlight(start_line, start_col, end_col)
|
||||
else
|
||||
highlight(start_line, start_col, -1)
|
||||
for line = start_line + 1, end_line - 1 do
|
||||
highlight(line, 0, -1)
|
||||
end
|
||||
highlight(end_line, 0, end_col)
|
||||
end
|
||||
end
|
||||
|
||||
---Clear the preview highlight in the buffer currently in the preview window.
|
||||
function Preview:clearHighlight()
|
||||
if type(self.bufnr) == "number" and vim.api.nvim_buf_is_valid(self.bufnr) then
|
||||
vim.api.nvim_buf_clear_namespace(self.bufnr, neo_tree_preview_namespace, 0, -1)
|
||||
end
|
||||
end
|
||||
|
||||
local toggle_state = false
|
||||
|
||||
Preview.hide = function()
|
||||
toggle_state = false
|
||||
if instance then
|
||||
instance:revert()
|
||||
end
|
||||
instance = nil
|
||||
end
|
||||
|
||||
Preview.is_active = function()
|
||||
return instance and instance.active
|
||||
end
|
||||
|
||||
Preview.show = function(state)
|
||||
local node = state.tree:get_node()
|
||||
if node.type == "directory" then
|
||||
return
|
||||
end
|
||||
|
||||
if instance then
|
||||
instance:findWindow(state)
|
||||
else
|
||||
instance = Preview:new(state)
|
||||
end
|
||||
|
||||
local extra = node.extra or {}
|
||||
local position = extra.position
|
||||
local end_position = extra.end_position
|
||||
local path = node.path or node:get_id()
|
||||
local bufnr = extra.bufnr or vim.fn.bufadd(path)
|
||||
|
||||
if bufnr and bufnr > 0 and instance then
|
||||
instance:preview(bufnr, position, end_position)
|
||||
end
|
||||
end
|
||||
|
||||
Preview.toggle = function(state)
|
||||
if toggle_state then
|
||||
Preview.hide()
|
||||
else
|
||||
Preview.show(state)
|
||||
if instance and instance.active then
|
||||
toggle_state = true
|
||||
else
|
||||
Preview.hide()
|
||||
return
|
||||
end
|
||||
local winid = state.winid
|
||||
local source_name = state.name
|
||||
local preview_event = {
|
||||
event = events.VIM_CURSOR_MOVED,
|
||||
handler = function()
|
||||
if not toggle_state or vim.api.nvim_get_current_win() == instance.winid then
|
||||
return
|
||||
end
|
||||
if vim.api.nvim_get_current_win() == winid then
|
||||
log.debug("Cursor moved in tree window, updating preview")
|
||||
Preview.show(state)
|
||||
else
|
||||
log.debug("Neo-tree window lost focus, disposing preview")
|
||||
Preview.hide()
|
||||
end
|
||||
end,
|
||||
id = "preview-event",
|
||||
}
|
||||
instance:subscribe(source_name, preview_event)
|
||||
end
|
||||
end
|
||||
|
||||
Preview.focus = function()
|
||||
if Preview.is_active() then
|
||||
vim.fn.win_gotoid(instance.winid)
|
||||
end
|
||||
end
|
||||
|
||||
return Preview
|
69
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/commands.lua
vendored
Normal file
69
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/commands.lua
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
--This file should contain all commands meant to be used by mappings.
|
||||
local cc = require("neo-tree.sources.common.commands")
|
||||
local utils = require("neo-tree.utils")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local inputs = require("neo-tree.ui.inputs")
|
||||
local filters = require("neo-tree.sources.common.filters")
|
||||
|
||||
local vim = vim
|
||||
|
||||
local M = {}
|
||||
local SOURCE_NAME = "document_symbols"
|
||||
M.refresh = utils.wrap(manager.refresh, SOURCE_NAME)
|
||||
M.redraw = utils.wrap(manager.redraw, SOURCE_NAME)
|
||||
|
||||
M.show_debug_info = function(state)
|
||||
print(vim.inspect(state))
|
||||
end
|
||||
|
||||
M.jump_to_symbol = function(state, node)
|
||||
node = node or state.tree:get_node()
|
||||
if node:get_depth() == 1 then
|
||||
return
|
||||
end
|
||||
vim.api.nvim_set_current_win(state.lsp_winid)
|
||||
vim.api.nvim_set_current_buf(state.lsp_bufnr)
|
||||
local symbol_loc = node.extra.selection_range.start
|
||||
vim.api.nvim_win_set_cursor(state.lsp_winid, { symbol_loc[1] + 1, symbol_loc[2] })
|
||||
end
|
||||
|
||||
M.rename = function(state)
|
||||
local node = state.tree:get_node()
|
||||
if node:get_depth() == 1 then
|
||||
return
|
||||
end
|
||||
local old_name = node.name
|
||||
|
||||
local callback = function(new_name)
|
||||
if not new_name or new_name == "" or new_name == old_name then
|
||||
return
|
||||
end
|
||||
M.jump_to_symbol(state, node)
|
||||
vim.lsp.buf.rename(new_name)
|
||||
M.refresh(state)
|
||||
end
|
||||
local msg = string.format('Enter new name for "%s":', old_name)
|
||||
inputs.input(msg, old_name, callback)
|
||||
end
|
||||
|
||||
M.open = M.jump_to_symbol
|
||||
|
||||
M.filter_on_submit = function(state)
|
||||
filters.show_filter(state, true, true)
|
||||
end
|
||||
|
||||
M.filter = function(state)
|
||||
filters.show_filter(state, true)
|
||||
end
|
||||
|
||||
cc._add_common_commands(M, "node") -- common tree commands
|
||||
cc._add_common_commands(M, "^open") -- open commands
|
||||
cc._add_common_commands(M, "^close_window$")
|
||||
cc._add_common_commands(M, "source$") -- source navigation
|
||||
cc._add_common_commands(M, "preview") -- preview
|
||||
cc._add_common_commands(M, "^cancel$") -- cancel
|
||||
cc._add_common_commands(M, "help") -- help commands
|
||||
cc._add_common_commands(M, "with_window_picker$") -- open using window picker
|
||||
cc._add_common_commands(M, "^toggle_auto_expand_width$")
|
||||
|
||||
return M
|
41
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/components.lua
vendored
Normal file
41
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/components.lua
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
-- This file contains the built-in components. Each componment is a function
|
||||
-- that takes the following arguments:
|
||||
-- config: A table containing the configuration provided by the user
|
||||
-- when declaring this component in their renderer config.
|
||||
-- node: A NuiNode object for the currently focused node.
|
||||
-- state: The current state of the source providing the items.
|
||||
--
|
||||
-- The function should return either a table, or a list of tables, each of which
|
||||
-- contains the following keys:
|
||||
-- text: The text to display for this item.
|
||||
-- highlight: The highlight group to apply to this text.
|
||||
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local common = require("neo-tree.sources.common.components")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.icon = function(config, node, state)
|
||||
return {
|
||||
text = node:get_depth() == 1 and "" or node.extra.kind.icon,
|
||||
highlight = node.extra.kind.hl,
|
||||
}
|
||||
end
|
||||
|
||||
M.kind_icon = M.icon
|
||||
|
||||
M.kind_name = function(config, node, state)
|
||||
return {
|
||||
text = node:get_depth() == 1 and "" or node.extra.kind.name,
|
||||
highlight = node.extra.kind.hl,
|
||||
}
|
||||
end
|
||||
|
||||
M.name = function(config, node, state)
|
||||
return {
|
||||
text = node.name,
|
||||
highlight = node.extra.kind.hl or highlights.FILE_NAME,
|
||||
}
|
||||
end
|
||||
|
||||
return vim.tbl_deep_extend("force", common, M)
|
108
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/init.lua
vendored
Normal file
108
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/init.lua
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
--This file should have all functions that are in the public api and either set
|
||||
--or read the state of this source.
|
||||
|
||||
local vim = vim
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local events = require("neo-tree.events")
|
||||
local utils = require("neo-tree.utils")
|
||||
local symbols = require("neo-tree.sources.document_symbols.lib.symbols_utils")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
|
||||
local M = {
|
||||
name = "document_symbols",
|
||||
display_name = " Symbols ",
|
||||
}
|
||||
|
||||
local get_state = function()
|
||||
return manager.get_state(M.name)
|
||||
end
|
||||
|
||||
---Refresh the source with debouncing
|
||||
---@param args { afile: string }
|
||||
local refresh_debounced = function(args)
|
||||
if utils.is_real_file(args.afile) == false then
|
||||
return
|
||||
end
|
||||
utils.debounce(
|
||||
"document_symbols_refresh",
|
||||
utils.wrap(manager.refresh, M.name),
|
||||
100,
|
||||
utils.debounce_strategy.CALL_LAST_ONLY
|
||||
)
|
||||
end
|
||||
|
||||
---Internal function to follow the cursor
|
||||
local follow_symbol = function()
|
||||
local state = get_state()
|
||||
if state.lsp_bufnr ~= vim.api.nvim_get_current_buf() then
|
||||
return
|
||||
end
|
||||
local cursor = vim.api.nvim_win_get_cursor(state.lsp_winid)
|
||||
local node_id = symbols.get_symbol_by_loc(state.tree, { cursor[1] - 1, cursor[2] })
|
||||
if #node_id > 0 then
|
||||
renderer.focus_node(state, node_id, true)
|
||||
end
|
||||
end
|
||||
|
||||
---Follow the cursor with debouncing
|
||||
---@param args { afile: string }
|
||||
local follow_debounced = function(args)
|
||||
if utils.is_real_file(args.afile) == false then
|
||||
return
|
||||
end
|
||||
utils.debounce(
|
||||
"document_symbols_follow",
|
||||
utils.wrap(follow_symbol, args.afile),
|
||||
100,
|
||||
utils.debounce_strategy.CALL_LAST_ONLY
|
||||
)
|
||||
end
|
||||
|
||||
---Navigate to the given path.
|
||||
M.navigate = function(state)
|
||||
state.lsp_winid, _ = utils.get_appropriate_window(state)
|
||||
state.lsp_bufnr = vim.api.nvim_win_get_buf(state.lsp_winid)
|
||||
state.path = vim.api.nvim_buf_get_name(state.lsp_bufnr)
|
||||
|
||||
symbols.render_symbols(state)
|
||||
end
|
||||
|
||||
---Configures the plugin, should be called before the plugin is used.
|
||||
---@param config table Configuration table containing any keys that the user
|
||||
---wants to change from the defaults. May be empty to accept default values.
|
||||
M.setup = function(config, global_config)
|
||||
symbols.setup(config)
|
||||
|
||||
if config.before_render then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.BEFORE_RENDER,
|
||||
handler = function(state)
|
||||
local this_state = get_state()
|
||||
if state == this_state then
|
||||
config.before_render(this_state)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local refresh_events = {
|
||||
events.VIM_BUFFER_ENTER,
|
||||
events.VIM_INSERT_LEAVE,
|
||||
events.VIM_TEXT_CHANGED_NORMAL,
|
||||
}
|
||||
for _, event in ipairs(refresh_events) do
|
||||
manager.subscribe(M.name, {
|
||||
event = event,
|
||||
handler = refresh_debounced,
|
||||
})
|
||||
end
|
||||
|
||||
if config.follow_cursor then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_CURSOR_MOVED,
|
||||
handler = follow_debounced,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
88
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/lib/client_filters.lua
vendored
Normal file
88
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/lib/client_filters.lua
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
---Utilities function to filter the LSP servers
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
---@alias LspRespRaw table<integer, { result: LspRespNode }>
|
||||
local M = {}
|
||||
|
||||
---@alias FilterFn fun(client_name: string): boolean
|
||||
|
||||
---Filter clients
|
||||
---@param filter_type "first" | "all"
|
||||
---@param filter_fn FilterFn
|
||||
---@param resp LspRespRaw
|
||||
---@return table<string, LspRespNode>
|
||||
local filter_clients = function(filter_type, filter_fn, resp)
|
||||
if resp == nil or type(resp) ~= "table" then
|
||||
return {}
|
||||
end
|
||||
filter_fn = filter_fn or function(client_name)
|
||||
return true
|
||||
end
|
||||
|
||||
local result = {}
|
||||
for client_id, client_resp in pairs(resp) do
|
||||
local client_name = vim.lsp.get_client_by_id(client_id).name
|
||||
if filter_fn(client_name) and client_resp.result ~= nil then
|
||||
result[client_name] = client_resp.result
|
||||
if filter_type ~= "all" then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---Filter only allowed clients
|
||||
---@param allow_only string[] the list of clients to keep
|
||||
---@return FilterFn
|
||||
local allow_only = function(allow_only)
|
||||
return function(client_name)
|
||||
return vim.tbl_contains(allow_only, client_name)
|
||||
end
|
||||
end
|
||||
|
||||
---Ignore clients
|
||||
---@param ignore string[] the list of clients to remove
|
||||
---@return FilterFn
|
||||
local ignore = function(ignore)
|
||||
return function(client_name)
|
||||
return not vim.tbl_contains(ignore, client_name)
|
||||
end
|
||||
end
|
||||
|
||||
---Main entry point for the filter
|
||||
---@param resp LspRespRaw
|
||||
---@return table<string, LspRespNode>
|
||||
M.filter_resp = function(resp)
|
||||
return {}
|
||||
end
|
||||
|
||||
---Setup the filter accordingly to the config
|
||||
---@see neo-tree-document-symbols-source for more details on options that the filter accepts
|
||||
---@param cfg_flt "first" | "all" | { type: "first" | "all", fn: FilterFn, allow_only: string[], ignore: string[] }
|
||||
M.setup = function(cfg_flt)
|
||||
local filter_type = "first"
|
||||
local filter_fn = nil
|
||||
|
||||
if type(cfg_flt) == "table" then
|
||||
if cfg_flt.type == "all" then
|
||||
filter_type = "all"
|
||||
end
|
||||
|
||||
if cfg_flt.fn ~= nil then
|
||||
filter_fn = cfg_flt.fn
|
||||
elseif cfg_flt.allow_only then
|
||||
filter_fn = allow_only(cfg_flt.allow_only)
|
||||
elseif cfg_flt.ignore then
|
||||
filter_fn = ignore(cfg_flt.ignore)
|
||||
end
|
||||
elseif cfg_flt == "all" then
|
||||
filter_type = "all"
|
||||
end
|
||||
|
||||
M.filter_resp = function(resp)
|
||||
return filter_clients(filter_type, filter_fn, resp)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
62
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/lib/kinds.lua
vendored
Normal file
62
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/lib/kinds.lua
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
---Helper module to render symbols' kinds
|
||||
---Need to be initialized by calling M.setup()
|
||||
local M = {}
|
||||
|
||||
local kinds_id_to_name = {
|
||||
[0] = "Root",
|
||||
[1] = "File",
|
||||
[2] = "Module",
|
||||
[3] = "Namespace",
|
||||
[4] = "Package",
|
||||
[5] = "Class",
|
||||
[6] = "Method",
|
||||
[7] = "Property",
|
||||
[8] = "Field",
|
||||
[9] = "Constructor",
|
||||
[10] = "Enum",
|
||||
[11] = "Interface",
|
||||
[12] = "Function",
|
||||
[13] = "Variable",
|
||||
[14] = "Constant",
|
||||
[15] = "String",
|
||||
[16] = "Number",
|
||||
[17] = "Boolean",
|
||||
[18] = "Array",
|
||||
[19] = "Object",
|
||||
[20] = "Key",
|
||||
[21] = "Null",
|
||||
[22] = "EnumMember",
|
||||
[23] = "Struct",
|
||||
[24] = "Event",
|
||||
[25] = "Operator",
|
||||
[26] = "TypeParameter",
|
||||
}
|
||||
|
||||
local kinds_map = {}
|
||||
|
||||
---Get how the kind with kind_id should be rendered
|
||||
---@param kind_id integer the kind_id to be render
|
||||
---@return table res of the form { name = kind_display_name, icon = kind_icon, hl = kind_hl }
|
||||
M.get_kind = function(kind_id)
|
||||
local kind_name = kinds_id_to_name[kind_id]
|
||||
return vim.tbl_extend(
|
||||
"force",
|
||||
{ name = kind_name or ("Unknown: " .. kind_id), icon = "?", hl = "" },
|
||||
kind_name and (kinds_map[kind_name] or {}) or kinds_map["Unknown"]
|
||||
)
|
||||
end
|
||||
|
||||
---Setup the module with custom kinds
|
||||
---@param custom_kinds table additional kinds, should be of the form { [kind_id] = kind_name }
|
||||
---@param kinds_display table mapping of kind_name to corresponding display name, icon and hl group
|
||||
--- { [kind_name] = {
|
||||
--- name = kind_display_name,
|
||||
--- icon = kind_icon,
|
||||
--- hl = kind_hl
|
||||
--- }, }
|
||||
M.setup = function(custom_kinds, kinds_display)
|
||||
kinds_id_to_name = vim.tbl_deep_extend("force", kinds_id_to_name, custom_kinds or {})
|
||||
kinds_map = kinds_display
|
||||
end
|
||||
|
||||
return M
|
210
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/lib/symbols_utils.lua
vendored
Normal file
210
bundle/neo-tree.nvim/lua/neo-tree/sources/document_symbols/lib/symbols_utils.lua
vendored
Normal file
@ -0,0 +1,210 @@
|
||||
---Utilities functions for the document_symbols source
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local filters = require("neo-tree.sources.document_symbols.lib.client_filters")
|
||||
local kinds = require("neo-tree.sources.document_symbols.lib.kinds")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@alias Loc integer[] a location in a buffer {row, col}, 0-indexed
|
||||
---@alias LocRange { start: Loc, ["end"]: Loc } a range consisting of two loc
|
||||
|
||||
---@class SymbolExtra
|
||||
---@field bufnr integer the buffer containing the symbols,
|
||||
---@field kind string the kind of each symbol
|
||||
---@field selection_range LocRange the symbol's location
|
||||
---@field position Loc start of symbol's definition
|
||||
---@field end_position Loc start of symbol's definition
|
||||
|
||||
---@class SymbolNode see
|
||||
---@field id string
|
||||
---@field name string name of symbol
|
||||
---@field path string buffer path - should all be the same
|
||||
---@field type "root"|"symbol"
|
||||
---@field children SymbolNode[]
|
||||
---@field extra SymbolExtra additional info
|
||||
|
||||
---@alias LspLoc { line: integer, character: integer}
|
||||
---@alias LspRange { start : LspLoc, ["end"]: LspLoc }
|
||||
---@class LspRespNode
|
||||
---@field name string
|
||||
---@field detail string?
|
||||
---@field kind integer
|
||||
---@field tags any
|
||||
---@field deprecated boolean?
|
||||
---@field range LspRange
|
||||
---@field selectionRange LspRange
|
||||
---@field children LspRespNode[]
|
||||
|
||||
---Parse the LspRange
|
||||
---@param range LspRange the LspRange object to parse
|
||||
---@return LocRange range the parsed range
|
||||
local parse_range = function(range)
|
||||
return {
|
||||
start = { range.start.line, range.start.character },
|
||||
["end"] = { range["end"].line, range["end"].character },
|
||||
}
|
||||
end
|
||||
|
||||
---Compare two tuples of length 2 by first - second elements
|
||||
---@param a Loc
|
||||
---@param b Loc
|
||||
---@return boolean
|
||||
local loc_less_than = function(a, b)
|
||||
if a[1] < b[1] then
|
||||
return true
|
||||
elseif a[1] == b[1] then
|
||||
return a[2] <= b[2]
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Check whether loc is contained in range, i.e range[1] <= loc <= range[2]
|
||||
---@param loc Loc
|
||||
---@param range LocRange
|
||||
---@return boolean
|
||||
M.is_loc_in_range = function(loc, range)
|
||||
return loc_less_than(range[1], loc) and loc_less_than(loc, range[2])
|
||||
end
|
||||
|
||||
---Get the the current symbol under the cursor
|
||||
---@param tree any the Nui symbol tree
|
||||
---@param loc Loc the cursor location {row, col} (0-index)
|
||||
---@return string node_id
|
||||
M.get_symbol_by_loc = function(tree, loc)
|
||||
local function dfs(node)
|
||||
local node_id = node:get_id()
|
||||
if node:has_children() then
|
||||
for _, child in ipairs(tree:get_nodes(node_id)) do
|
||||
if M.is_loc_in_range(loc, { child.extra.position, child.extra.end_position }) then
|
||||
return dfs(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
return node_id
|
||||
end
|
||||
|
||||
for _, root in ipairs(tree:get_nodes()) do
|
||||
local node_id = dfs(root)
|
||||
if node_id ~= root:get_id() then
|
||||
return node_id
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
---Parse the LSP response into a tree. Each node on the tree follows
|
||||
---the same structure as a NuiTree node, with the extra field
|
||||
---containing additional information.
|
||||
---@param resp_node LspRespNode the LSP response node
|
||||
---@param id string the id of the current node
|
||||
---@return SymbolNode symb_node the parsed tree
|
||||
local function parse_resp(resp_node, id, state, parent_search_path)
|
||||
-- parse all children
|
||||
local children = {}
|
||||
local search_path = parent_search_path .. "/" .. resp_node.name
|
||||
for i, child in ipairs(resp_node.children or {}) do
|
||||
local child_node = parse_resp(child, id .. "." .. i, state, search_path)
|
||||
table.insert(children, child_node)
|
||||
end
|
||||
|
||||
-- parse current node
|
||||
local preview_range = parse_range(resp_node.range)
|
||||
local symb_node = {
|
||||
id = id,
|
||||
name = resp_node.name,
|
||||
type = "symbol",
|
||||
path = state.path,
|
||||
children = children,
|
||||
extra = {
|
||||
bufnr = state.lsp_bufnr,
|
||||
kind = kinds.get_kind(resp_node.kind),
|
||||
selection_range = parse_range(resp_node.selectionRange),
|
||||
search_path = search_path,
|
||||
-- detail = resp_node.detail,
|
||||
position = preview_range.start,
|
||||
end_position = preview_range["end"],
|
||||
},
|
||||
}
|
||||
return symb_node
|
||||
end
|
||||
|
||||
---Callback function for lsp request
|
||||
---@param lsp_resp LspRespRaw the response of the lsp client
|
||||
---@param state table the state of the source
|
||||
local on_lsp_resp = function(lsp_resp, state)
|
||||
if lsp_resp == nil or type(lsp_resp) ~= "table" then
|
||||
return
|
||||
end
|
||||
|
||||
-- filter the response to get only the desired LSP
|
||||
local resp = filters.filter_resp(lsp_resp)
|
||||
|
||||
local bufname = state.path
|
||||
local items = {}
|
||||
|
||||
-- parse each client's response
|
||||
for client_name, client_result in pairs(resp) do
|
||||
local symbol_list = {}
|
||||
for i, resp_node in ipairs(client_result) do
|
||||
table.insert(symbol_list, parse_resp(resp_node, #items .. "." .. i, state, "/"))
|
||||
end
|
||||
|
||||
-- add the parsed response to the tree
|
||||
local splits = vim.split(bufname, "/")
|
||||
local filename = splits[#splits]
|
||||
table.insert(items, {
|
||||
id = "" .. #items,
|
||||
name = string.format("SYMBOLS (%s) in %s", client_name, filename),
|
||||
path = bufname,
|
||||
type = "root",
|
||||
children = symbol_list,
|
||||
extra = { kind = kinds.get_kind(0), search_path = "/" },
|
||||
})
|
||||
end
|
||||
renderer.show_nodes(items, state)
|
||||
end
|
||||
|
||||
M.render_symbols = function(state)
|
||||
local bufnr = state.lsp_bufnr
|
||||
local bufname = state.path
|
||||
|
||||
-- if no client found, terminate
|
||||
local client_found = false
|
||||
for _, client in pairs(vim.lsp.get_active_clients({ bufnr = bufnr })) do
|
||||
if client.server_capabilities.documentSymbolProvider then
|
||||
client_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not client_found then
|
||||
local splits = vim.split(bufname, "/")
|
||||
renderer.show_nodes({
|
||||
{
|
||||
id = "0",
|
||||
name = "No client found for " .. splits[#splits],
|
||||
path = bufname,
|
||||
type = "root",
|
||||
children = {},
|
||||
extra = { kind = kinds.get_kind(0), search_path = "/" },
|
||||
},
|
||||
}, state)
|
||||
return
|
||||
end
|
||||
|
||||
-- client found
|
||||
vim.lsp.buf_request_all(
|
||||
bufnr,
|
||||
"textDocument/documentSymbol",
|
||||
{ textDocument = vim.lsp.util.make_text_document_params(bufnr) },
|
||||
function(resp)
|
||||
on_lsp_resp(resp, state)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
M.setup = function(config)
|
||||
filters.setup(config.client_filters)
|
||||
kinds.setup(config.custom_kinds, config.kinds)
|
||||
end
|
||||
|
||||
return M
|
248
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/commands.lua
vendored
Normal file
248
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/commands.lua
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
--This file should contain all commands meant to be used by mappings.
|
||||
|
||||
local cc = require("neo-tree.sources.common.commands")
|
||||
local fs = require("neo-tree.sources.filesystem")
|
||||
local utils = require("neo-tree.utils")
|
||||
local filter = require("neo-tree.sources.filesystem.lib.filter")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local log = require("neo-tree.log")
|
||||
|
||||
local M = {}
|
||||
local refresh = function(state)
|
||||
fs._navigate_internal(state, nil, nil, nil, false)
|
||||
end
|
||||
|
||||
local redraw = function(state)
|
||||
renderer.redraw(state)
|
||||
end
|
||||
|
||||
M.add = function(state)
|
||||
cc.add(state, utils.wrap(fs.show_new_children, state))
|
||||
end
|
||||
|
||||
M.add_directory = function(state)
|
||||
cc.add_directory(state, utils.wrap(fs.show_new_children, state))
|
||||
end
|
||||
|
||||
M.clear_filter = function(state)
|
||||
fs.reset_search(state, true)
|
||||
end
|
||||
|
||||
M.copy = function(state)
|
||||
cc.copy(state, utils.wrap(refresh, state))
|
||||
end
|
||||
|
||||
---Marks node as copied, so that it can be pasted somewhere else.
|
||||
M.copy_to_clipboard = function(state)
|
||||
cc.copy_to_clipboard(state, utils.wrap(redraw, state))
|
||||
end
|
||||
|
||||
M.copy_to_clipboard_visual = function(state, selected_nodes)
|
||||
cc.copy_to_clipboard_visual(state, selected_nodes, utils.wrap(redraw, state))
|
||||
end
|
||||
|
||||
---Marks node as cut, so that it can be pasted (moved) somewhere else.
|
||||
M.cut_to_clipboard = function(state)
|
||||
cc.cut_to_clipboard(state, utils.wrap(redraw, state))
|
||||
end
|
||||
|
||||
M.cut_to_clipboard_visual = function(state, selected_nodes)
|
||||
cc.cut_to_clipboard_visual(state, selected_nodes, utils.wrap(redraw, state))
|
||||
end
|
||||
|
||||
M.move = function(state)
|
||||
cc.move(state, utils.wrap(refresh, state))
|
||||
end
|
||||
|
||||
---Pastes all items from the clipboard to the current directory.
|
||||
M.paste_from_clipboard = function(state)
|
||||
cc.paste_from_clipboard(state, utils.wrap(fs.show_new_children, state))
|
||||
end
|
||||
|
||||
M.delete = function(state)
|
||||
cc.delete(state, utils.wrap(refresh, state))
|
||||
end
|
||||
|
||||
M.delete_visual = function(state, selected_nodes)
|
||||
cc.delete_visual(state, selected_nodes, utils.wrap(refresh, state))
|
||||
end
|
||||
|
||||
M.expand_all_nodes = function(state)
|
||||
local toggle_dir_no_redraw = function(_state, node)
|
||||
fs.toggle_directory(_state, node, nil, true, true)
|
||||
end
|
||||
cc.expand_all_nodes(state, toggle_dir_no_redraw)
|
||||
end
|
||||
|
||||
---Shows the filter input, which will filter the tree.
|
||||
M.filter_as_you_type = function(state)
|
||||
filter.show_filter(state, true)
|
||||
end
|
||||
|
||||
---Shows the filter input, which will filter the tree.
|
||||
M.filter_on_submit = function(state)
|
||||
filter.show_filter(state, false)
|
||||
end
|
||||
|
||||
---Shows the filter input in fuzzy finder mode.
|
||||
M.fuzzy_finder = function(state)
|
||||
filter.show_filter(state, true, true)
|
||||
end
|
||||
|
||||
---Shows the filter input in fuzzy finder mode.
|
||||
M.fuzzy_finder_directory = function(state)
|
||||
filter.show_filter(state, true, "directory")
|
||||
end
|
||||
|
||||
---Shows the filter input in fuzzy sorter
|
||||
M.fuzzy_sorter = function(state)
|
||||
filter.show_filter(state, true, true, true)
|
||||
end
|
||||
|
||||
---Shows the filter input in fuzzy sorter with only directories
|
||||
M.fuzzy_sorter_directory = function(state)
|
||||
filter.show_filter(state, true, "directory", true)
|
||||
end
|
||||
|
||||
---Navigate up one level.
|
||||
M.navigate_up = function(state)
|
||||
local parent_path, _ = utils.split_path(state.path)
|
||||
if not utils.truthy(parent_path) then
|
||||
return
|
||||
end
|
||||
local path_to_reveal = nil
|
||||
local node = state.tree:get_node()
|
||||
if node then
|
||||
path_to_reveal = node:get_id()
|
||||
end
|
||||
if state.search_pattern then
|
||||
fs.reset_search(state, false)
|
||||
end
|
||||
log.debug("Changing directory to:", parent_path)
|
||||
fs._navigate_internal(state, parent_path, path_to_reveal, nil, false)
|
||||
end
|
||||
|
||||
local focus_next_git_modified = function(state, reverse)
|
||||
local node = state.tree:get_node()
|
||||
local current_path = node:get_id()
|
||||
local g = state.git_status_lookup
|
||||
if not utils.truthy(g) then
|
||||
return
|
||||
end
|
||||
local paths = { current_path }
|
||||
for path, status in pairs(g) do
|
||||
if path ~= current_path and status and status ~= "!!" then
|
||||
--don't include files not in the current working directory
|
||||
if utils.is_subpath(state.path, path) then
|
||||
table.insert(paths, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
local sorted_paths = utils.sort_by_tree_display(paths)
|
||||
if reverse then
|
||||
sorted_paths = utils.reverse_list(sorted_paths)
|
||||
end
|
||||
|
||||
local is_file = function(path)
|
||||
local success, stats = pcall(vim.loop.fs_stat, path)
|
||||
return (success and stats and stats.type ~= "directory")
|
||||
end
|
||||
|
||||
local passed = false
|
||||
local target = nil
|
||||
for _, path in ipairs(sorted_paths) do
|
||||
if target == nil and is_file(path) then
|
||||
target = path
|
||||
end
|
||||
if passed then
|
||||
if is_file(path) then
|
||||
target = path
|
||||
break
|
||||
end
|
||||
elseif path == current_path then
|
||||
passed = true
|
||||
end
|
||||
end
|
||||
|
||||
local existing = state.tree:get_node(target)
|
||||
if existing then
|
||||
renderer.focus_node(state, target)
|
||||
else
|
||||
fs.navigate(state, state.path, target, nil, false)
|
||||
end
|
||||
end
|
||||
|
||||
M.next_git_modified = function(state)
|
||||
focus_next_git_modified(state, false)
|
||||
end
|
||||
|
||||
M.prev_git_modified = function(state)
|
||||
focus_next_git_modified(state, true)
|
||||
end
|
||||
|
||||
M.open = function(state)
|
||||
cc.open(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
M.open_split = function(state)
|
||||
cc.open_split(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
M.open_vsplit = function(state)
|
||||
cc.open_vsplit(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
M.open_tabnew = function(state)
|
||||
cc.open_tabnew(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
M.open_drop = function(state)
|
||||
cc.open_drop(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
M.open_tab_drop = function(state)
|
||||
cc.open_tab_drop(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
|
||||
M.open_with_window_picker = function(state)
|
||||
cc.open_with_window_picker(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
M.split_with_window_picker = function(state)
|
||||
cc.split_with_window_picker(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
M.vsplit_with_window_picker = function(state)
|
||||
cc.vsplit_with_window_picker(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
|
||||
M.refresh = refresh
|
||||
|
||||
M.rename = function(state)
|
||||
cc.rename(state, utils.wrap(refresh, state))
|
||||
end
|
||||
|
||||
M.set_root = function(state)
|
||||
local tree = state.tree
|
||||
local node = tree:get_node()
|
||||
if node.type == "directory" then
|
||||
if state.search_pattern then
|
||||
fs.reset_search(state, false)
|
||||
end
|
||||
fs._navigate_internal(state, node.id, nil, nil, false)
|
||||
end
|
||||
end
|
||||
|
||||
---Toggles whether hidden files are shown or not.
|
||||
M.toggle_hidden = function(state)
|
||||
state.filtered_items.visible = not state.filtered_items.visible
|
||||
log.info("Toggling hidden files: " .. tostring(state.filtered_items.visible))
|
||||
refresh(state)
|
||||
end
|
||||
|
||||
---Toggles whether the tree is filtered by gitignore or not.
|
||||
M.toggle_gitignore = function(state)
|
||||
log.warn("`toggle_gitignore` has been removed, running toggle_hidden instead.")
|
||||
M.toggle_hidden(state)
|
||||
end
|
||||
|
||||
M.toggle_node = function(state)
|
||||
cc.toggle_node(state, utils.wrap(fs.toggle_directory, state))
|
||||
end
|
||||
|
||||
cc._add_common_commands(M)
|
||||
|
||||
return M
|
40
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/components.lua
vendored
Normal file
40
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/components.lua
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
-- This file contains the built-in components. Each componment is a function
|
||||
-- that takes the following arguments:
|
||||
-- config: A table containing the configuration provided by the user
|
||||
-- when declaring this component in their renderer config.
|
||||
-- node: A NuiNode object for the currently focused node.
|
||||
-- state: The current state of the source providing the items.
|
||||
--
|
||||
-- The function should return either a table, or a list of tables, each of which
|
||||
-- contains the following keys:
|
||||
-- text: The text to display for this item.
|
||||
-- highlight: The highlight group to apply to this text.
|
||||
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local common = require("neo-tree.sources.common.components")
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.current_filter = function(config, node, state)
|
||||
local filter = node.search_pattern or ""
|
||||
if filter == "" then
|
||||
return {}
|
||||
end
|
||||
return {
|
||||
{
|
||||
text = "Find",
|
||||
highlight = highlights.DIM_TEXT,
|
||||
},
|
||||
{
|
||||
text = string.format('"%s"', filter),
|
||||
highlight = config.highlight or highlights.FILTER_TERM,
|
||||
},
|
||||
{
|
||||
text = "in",
|
||||
highlight = highlights.DIM_TEXT,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
return vim.tbl_deep_extend("force", common, M)
|
431
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/init.lua
vendored
Normal file
431
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/init.lua
vendored
Normal file
@ -0,0 +1,431 @@
|
||||
--This file should have all functions that are in the public api and either set
|
||||
--or read the state of this source.
|
||||
|
||||
local vim = vim
|
||||
local utils = require("neo-tree.utils")
|
||||
local fs_scan = require("neo-tree.sources.filesystem.lib.fs_scan")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local events = require("neo-tree.events")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local git = require("neo-tree.git")
|
||||
local glob = require("neo-tree.sources.filesystem.lib.globtopattern")
|
||||
|
||||
local M = {
|
||||
name = "filesystem",
|
||||
display_name = " Files ",
|
||||
}
|
||||
|
||||
local wrap = function(func)
|
||||
return utils.wrap(func, M.name)
|
||||
end
|
||||
|
||||
local get_state = function(tabid)
|
||||
return manager.get_state(M.name, tabid)
|
||||
end
|
||||
|
||||
-- TODO: DEPRECATED in 1.19, remove in 2.0
|
||||
-- Leaving this here for now because it was mentioned in the help file.
|
||||
M.reveal_current_file = function()
|
||||
log.warn("DEPRECATED: use `neotree.sources.manager.reveal_current_file('filesystem')` instead")
|
||||
return manager.reveal_current_file(M.name)
|
||||
end
|
||||
|
||||
local follow_internal = function(callback, force_show, async)
|
||||
log.trace("follow called")
|
||||
if vim.bo.filetype == "neo-tree" or vim.bo.filetype == "neo-tree-popup" then
|
||||
return
|
||||
end
|
||||
local path_to_reveal = manager.get_path_to_reveal()
|
||||
if not utils.truthy(path_to_reveal) then
|
||||
return false
|
||||
end
|
||||
|
||||
local state = get_state()
|
||||
if state.current_position == "float" then
|
||||
return false
|
||||
end
|
||||
if not state.path then
|
||||
return false
|
||||
end
|
||||
local window_exists = renderer.window_exists(state)
|
||||
if window_exists then
|
||||
local node = state.tree and state.tree:get_node()
|
||||
if node then
|
||||
if node:get_id() == path_to_reveal then
|
||||
-- already focused
|
||||
return false
|
||||
end
|
||||
end
|
||||
else
|
||||
if not force_show then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local is_in_path = path_to_reveal:sub(1, #state.path) == state.path
|
||||
if not is_in_path then
|
||||
return false
|
||||
end
|
||||
|
||||
log.debug("follow file: ", path_to_reveal)
|
||||
local show_only_explicitly_opened = function()
|
||||
local eod = state.explicitly_opened_directories or {}
|
||||
local expanded_nodes = renderer.get_expanded_nodes(state.tree)
|
||||
local state_changed = false
|
||||
for _, id in ipairs(expanded_nodes) do
|
||||
local is_explicit = eod[id]
|
||||
if not is_explicit then
|
||||
local is_in_path = path_to_reveal:sub(1, #id) == id
|
||||
if is_in_path then
|
||||
is_explicit = true
|
||||
end
|
||||
end
|
||||
if not is_explicit then
|
||||
local node = state.tree:get_node(id)
|
||||
if node then
|
||||
node:collapse()
|
||||
state_changed = true
|
||||
end
|
||||
end
|
||||
if state_changed then
|
||||
renderer.redraw(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
state.position.is.restorable = false -- we will handle setting cursor position here
|
||||
fs_scan.get_items(state, nil, path_to_reveal, function()
|
||||
show_only_explicitly_opened()
|
||||
renderer.focus_node(state, path_to_reveal, true)
|
||||
if type(callback) == "function" then
|
||||
callback()
|
||||
end
|
||||
end, async)
|
||||
return true
|
||||
end
|
||||
|
||||
M.follow = function(callback, force_show)
|
||||
if vim.fn.bufname(0) == "COMMIT_EDITMSG" then
|
||||
return false
|
||||
end
|
||||
if utils.is_floating() then
|
||||
return false
|
||||
end
|
||||
utils.debounce("neo-tree-follow", function()
|
||||
return follow_internal(callback, force_show)
|
||||
end, 100, utils.debounce_strategy.CALL_LAST_ONLY)
|
||||
end
|
||||
|
||||
M._navigate_internal = function(state, path, path_to_reveal, callback, async)
|
||||
log.trace("navigate_internal", state.current_position, path, path_to_reveal)
|
||||
state.dirty = false
|
||||
local is_search = utils.truthy(state.search_pattern)
|
||||
local path_changed = false
|
||||
if not path and not state.bind_to_cwd then
|
||||
path = state.path
|
||||
end
|
||||
if path == nil then
|
||||
log.debug("navigate_internal: path is nil, using cwd")
|
||||
path = manager.get_cwd(state)
|
||||
end
|
||||
if path ~= state.path then
|
||||
log.debug("navigate_internal: path changed from ", state.path, " to ", path)
|
||||
state.path = path
|
||||
path_changed = true
|
||||
end
|
||||
|
||||
if path_to_reveal then
|
||||
renderer.position.set(state, path_to_reveal)
|
||||
log.debug(
|
||||
"navigate_internal: in path_to_reveal, state.position is ",
|
||||
state.position.node_id,
|
||||
", restorable = ",
|
||||
state.position.is.restorable
|
||||
)
|
||||
fs_scan.get_items(state, nil, path_to_reveal, callback)
|
||||
else
|
||||
local is_current = state.current_position == "current"
|
||||
local follow_file = state.follow_current_file
|
||||
and not is_search
|
||||
and not is_current
|
||||
and manager.get_path_to_reveal()
|
||||
local handled = false
|
||||
if utils.truthy(follow_file) then
|
||||
handled = follow_internal(callback, true, async)
|
||||
end
|
||||
if not handled then
|
||||
local success, msg = pcall(renderer.position.save, state)
|
||||
if success then
|
||||
log.trace("navigate_internal: position saved")
|
||||
else
|
||||
log.trace("navigate_internal: FAILED to save position: ", msg)
|
||||
end
|
||||
fs_scan.get_items(state, nil, nil, callback, async)
|
||||
end
|
||||
end
|
||||
|
||||
if path_changed and state.bind_to_cwd then
|
||||
manager.set_cwd(state)
|
||||
end
|
||||
local config = require("neo-tree").config
|
||||
if config.enable_git_status and not is_search and config.git_status_async then
|
||||
git.status_async(state.path, state.git_base, config.git_status_async_options)
|
||||
end
|
||||
end
|
||||
|
||||
---Navigate to the given path.
|
||||
---@param path string? Path to navigate to. If empty, will navigate to the cwd.
|
||||
---@param path_to_reveal string? Node to focus after the items are loaded.
|
||||
---@param callback function? Callback to call after the items are loaded.
|
||||
M.navigate = function(state, path, path_to_reveal, callback, async)
|
||||
log.trace("navigate", path, path_to_reveal, async)
|
||||
utils.debounce("filesystem_navigate", function()
|
||||
M._navigate_internal(state, path, path_to_reveal, callback, async)
|
||||
end, utils.debounce_strategy.CALL_FIRST_AND_LAST, 100)
|
||||
end
|
||||
|
||||
M.reset_search = function(state, refresh, open_current_node)
|
||||
log.trace("reset_search")
|
||||
-- Cancel any pending search
|
||||
require("neo-tree.sources.filesystem.lib.filter_external").cancel()
|
||||
-- reset search state
|
||||
state.fuzzy_finder_mode = nil
|
||||
state.use_fzy = nil
|
||||
state.fzy_sort_result_scores = nil
|
||||
state.fzy_sort_file_list_cache = nil
|
||||
state.sort_function_override = nil
|
||||
|
||||
if refresh == nil then
|
||||
refresh = true
|
||||
end
|
||||
if state.open_folders_before_search then
|
||||
state.force_open_folders = vim.deepcopy(state.open_folders_before_search, { noref = 1 })
|
||||
else
|
||||
state.force_open_folders = nil
|
||||
end
|
||||
state.search_pattern = nil
|
||||
state.open_folders_before_search = nil
|
||||
if open_current_node then
|
||||
local success, node = pcall(state.tree.get_node, state.tree)
|
||||
if success and node then
|
||||
local path = node:get_id()
|
||||
renderer.position.set(state, path)
|
||||
if node.type == "directory" then
|
||||
path = utils.remove_trailing_slash(path)
|
||||
log.trace("opening directory from search: ", path)
|
||||
M.navigate(state, nil, path, function()
|
||||
pcall(renderer.focus_node, state, path, false)
|
||||
end)
|
||||
else
|
||||
utils.open_file(state, path)
|
||||
if
|
||||
refresh
|
||||
and state.current_position ~= "current"
|
||||
and state.current_position ~= "float"
|
||||
then
|
||||
M.navigate(state, nil, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif refresh then
|
||||
M.navigate(state)
|
||||
end
|
||||
end
|
||||
|
||||
M.show_new_children = function(state, node_or_path)
|
||||
local node = node_or_path
|
||||
if node_or_path == nil then
|
||||
node = state.tree:get_node()
|
||||
node_or_path = node:get_id()
|
||||
elseif type(node_or_path) == "string" then
|
||||
node = state.tree:get_node(node_or_path)
|
||||
if node == nil then
|
||||
local parent_path, _ = utils.split_path(node_or_path)
|
||||
node = state.tree:get_node(parent_path)
|
||||
if node == nil then
|
||||
M.navigate(state, nil, node_or_path)
|
||||
return
|
||||
end
|
||||
end
|
||||
else
|
||||
node = node_or_path
|
||||
node_or_path = node:get_id()
|
||||
end
|
||||
|
||||
if node.type ~= "directory" then
|
||||
return
|
||||
end
|
||||
|
||||
M.navigate(state, nil, node_or_path)
|
||||
end
|
||||
|
||||
---Configures the plugin, should be called before the plugin is used.
|
||||
---@param config table Configuration table containing any keys that the user
|
||||
--wants to change from the defaults. May be empty to accept default values.
|
||||
M.setup = function(config, global_config)
|
||||
config.filtered_items = config.filtered_items or {}
|
||||
config.enable_git_status = global_config.enable_git_status
|
||||
|
||||
for _, key in ipairs({ "hide_by_pattern", "never_show_by_pattern" }) do
|
||||
local list = config.filtered_items[key]
|
||||
if type(list) == "table" then
|
||||
for i, pattern in ipairs(list) do
|
||||
list[i] = glob.globtopattern(pattern)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, key in ipairs({ "hide_by_name", "always_show", "never_show" }) do
|
||||
local list = config.filtered_items[key]
|
||||
if type(list) == "table" then
|
||||
config.filtered_items[key] = utils.list_to_dict(list)
|
||||
end
|
||||
end
|
||||
|
||||
--Configure events for before_render
|
||||
if config.before_render then
|
||||
--convert to new event system
|
||||
manager.subscribe(M.name, {
|
||||
event = events.BEFORE_RENDER,
|
||||
handler = function(state)
|
||||
local this_state = get_state()
|
||||
if state == this_state then
|
||||
config.before_render(this_state)
|
||||
end
|
||||
end,
|
||||
})
|
||||
elseif global_config.enable_git_status and global_config.git_status_async then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.GIT_STATUS_CHANGED,
|
||||
handler = wrap(manager.git_status_changed),
|
||||
})
|
||||
elseif global_config.enable_git_status then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.BEFORE_RENDER,
|
||||
handler = function(state)
|
||||
local this_state = get_state()
|
||||
if state == this_state then
|
||||
state.git_status_lookup = git.status(state.git_base)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
-- Respond to git events from git_status source or Fugitive
|
||||
if global_config.enable_git_status then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.GIT_EVENT,
|
||||
handler = function()
|
||||
manager.refresh(M.name)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
--Configure event handlers for file changes
|
||||
if config.use_libuv_file_watcher then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.FS_EVENT,
|
||||
handler = wrap(manager.refresh),
|
||||
})
|
||||
else
|
||||
require("neo-tree.sources.filesystem.lib.fs_watch").unwatch_all()
|
||||
if global_config.enable_refresh_on_write then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_BUFFER_CHANGED,
|
||||
handler = function(arg)
|
||||
local afile = arg.afile or ""
|
||||
if utils.is_real_file(afile) then
|
||||
log.trace("refreshing due to vim_buffer_changed event: ", afile)
|
||||
manager.refresh("filesystem")
|
||||
else
|
||||
log.trace("Ignoring vim_buffer_changed event for non-file: ", afile)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
--Configure event handlers for cwd changes
|
||||
if config.bind_to_cwd then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_DIR_CHANGED,
|
||||
handler = wrap(manager.dir_changed),
|
||||
})
|
||||
end
|
||||
|
||||
--Configure event handlers for lsp diagnostic updates
|
||||
if global_config.enable_diagnostics then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_DIAGNOSTIC_CHANGED,
|
||||
handler = wrap(manager.diagnostics_changed),
|
||||
})
|
||||
end
|
||||
|
||||
--Configure event handlers for modified files
|
||||
if global_config.enable_modified_markers then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_BUFFER_MODIFIED_SET,
|
||||
handler = wrap(manager.opened_buffers_changed),
|
||||
})
|
||||
end
|
||||
|
||||
if global_config.enable_opened_markers then
|
||||
for _, event in ipairs({ events.VIM_BUFFER_ADDED, events.VIM_BUFFER_DELETED }) do
|
||||
manager.subscribe(M.name, {
|
||||
event = event,
|
||||
handler = wrap(manager.opened_buffers_changed),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- Configure event handler for follow_current_file option
|
||||
if config.follow_current_file then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_BUFFER_ENTER,
|
||||
handler = function(args)
|
||||
if utils.is_real_file(args.afile) then
|
||||
M.follow()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
---Expands or collapses the current node.
|
||||
M.toggle_directory = function(state, node, path_to_reveal, skip_redraw, recursive)
|
||||
local tree = state.tree
|
||||
if not node then
|
||||
node = tree:get_node()
|
||||
end
|
||||
if node.type ~= "directory" then
|
||||
return
|
||||
end
|
||||
state.explicitly_opened_directories = state.explicitly_opened_directories or {}
|
||||
if node.loaded == false then
|
||||
local id = node:get_id()
|
||||
state.explicitly_opened_directories[id] = true
|
||||
renderer.position.set(state, nil)
|
||||
fs_scan.get_items(state, id, path_to_reveal, nil, false, recursive)
|
||||
elseif node:has_children() then
|
||||
local updated = false
|
||||
if node:is_expanded() then
|
||||
updated = node:collapse()
|
||||
state.explicitly_opened_directories[node:get_id()] = false
|
||||
else
|
||||
updated = node:expand()
|
||||
state.explicitly_opened_directories[node:get_id()] = true
|
||||
end
|
||||
if updated and not skip_redraw then
|
||||
renderer.redraw(state)
|
||||
end
|
||||
if path_to_reveal then
|
||||
renderer.focus_node(state, path_to_reveal)
|
||||
end
|
||||
elseif require("neo-tree").config.filesystem.scan_mode == "deep" then
|
||||
node.empty_expanded = not node.empty_expanded
|
||||
renderer.redraw(state)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
233
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/filter.lua
vendored
Normal file
233
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/filter.lua
vendored
Normal file
@ -0,0 +1,233 @@
|
||||
-- This file holds all code for the search function.
|
||||
|
||||
local vim = vim
|
||||
local Input = require("nui.input")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
local fs = require("neo-tree.sources.filesystem")
|
||||
local popups = require("neo-tree.ui.popups")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
|
||||
local M = {}
|
||||
|
||||
local cmds = {
|
||||
move_cursor_down = function(state, scroll_padding)
|
||||
renderer.focus_node(state, nil, true, 1, scroll_padding)
|
||||
end,
|
||||
|
||||
move_cursor_up = function(state, scroll_padding)
|
||||
renderer.focus_node(state, nil, true, -1, scroll_padding)
|
||||
vim.cmd("redraw!")
|
||||
end,
|
||||
}
|
||||
|
||||
local function create_input_mapping_handle(cmd, state, scroll_padding)
|
||||
return function()
|
||||
cmd(state, scroll_padding)
|
||||
end
|
||||
end
|
||||
|
||||
M.show_filter = function(state, search_as_you_type, fuzzy_finder_mode, use_fzy)
|
||||
local popup_options
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local height = vim.api.nvim_win_get_height(winid)
|
||||
local scroll_padding = 3
|
||||
local popup_msg = "Search:"
|
||||
|
||||
if search_as_you_type then
|
||||
if fuzzy_finder_mode == "directory" then
|
||||
popup_msg = "Filter Directories:"
|
||||
else
|
||||
popup_msg = "Filter:"
|
||||
end
|
||||
end
|
||||
if state.current_position == "float" then
|
||||
scroll_padding = 0
|
||||
local width = vim.fn.winwidth(winid)
|
||||
local row = height - 2
|
||||
vim.api.nvim_win_set_height(winid, row)
|
||||
popup_options = popups.popup_options(popup_msg, width, {
|
||||
relative = "win",
|
||||
winid = winid,
|
||||
position = {
|
||||
row = row,
|
||||
col = 0,
|
||||
},
|
||||
size = width,
|
||||
})
|
||||
else
|
||||
local width = vim.fn.winwidth(0) - 2
|
||||
local row = height - 3
|
||||
popup_options = popups.popup_options(popup_msg, width, {
|
||||
relative = "win",
|
||||
winid = winid,
|
||||
position = {
|
||||
row = row,
|
||||
col = 0,
|
||||
},
|
||||
size = width,
|
||||
})
|
||||
end
|
||||
|
||||
local sort_by_score = function(a, b)
|
||||
-- `state.fzy_sort_result_scores` should be defined in
|
||||
-- `sources.filesystem.lib.filter_external.fzy_sort_files`
|
||||
local result_scores = state.fzy_sort_result_scores or { foo = 0, baz = 0 }
|
||||
local a_score = result_scores[a.path]
|
||||
local b_score = result_scores[b.path]
|
||||
if a_score == nil or b_score == nil then
|
||||
log.debug(string.format([[Fzy: failed to compare %s: %s, %s: %s]], a.path, a_score, b.path, b_score))
|
||||
local config = require("neo-tree").config
|
||||
if config.sort_function ~= nil then
|
||||
return config.sort_function(a, b)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
return a_score > b_score
|
||||
end
|
||||
|
||||
local select_first_file = function()
|
||||
local is_file = function(node)
|
||||
return node.type == "file"
|
||||
end
|
||||
local files = renderer.select_nodes(state.tree, is_file, 1)
|
||||
if #files > 0 then
|
||||
renderer.focus_node(state, files[1]:get_id(), true)
|
||||
end
|
||||
end
|
||||
|
||||
local has_pre_search_folders = utils.truthy(state.open_folders_before_search)
|
||||
if not has_pre_search_folders then
|
||||
log.trace("No search or pre-search folders, recording pre-search folders now")
|
||||
state.open_folders_before_search = renderer.get_expanded_nodes(state.tree)
|
||||
end
|
||||
|
||||
local waiting_for_default_value = utils.truthy(state.search_pattern)
|
||||
local input = Input(popup_options, {
|
||||
prompt = " ",
|
||||
default_value = state.search_pattern,
|
||||
on_submit = function(value)
|
||||
if value == "" then
|
||||
fs.reset_search(state)
|
||||
else
|
||||
if search_as_you_type and fuzzy_finder_mode then
|
||||
fs.reset_search(state, true, true)
|
||||
return
|
||||
end
|
||||
state.search_pattern = value
|
||||
manager.refresh("filesystem", function()
|
||||
-- focus first file
|
||||
local nodes = renderer.get_all_visible_nodes(state.tree)
|
||||
for _, node in ipairs(nodes) do
|
||||
if node.type == "file" then
|
||||
renderer.focus_node(state, node:get_id(), false)
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end,
|
||||
--this can be bad in a deep folder structure
|
||||
on_change = function(value)
|
||||
if not search_as_you_type then
|
||||
return
|
||||
end
|
||||
-- apparently when a default value is set, on_change fires for every character
|
||||
if waiting_for_default_value then
|
||||
if #value < #state.search_pattern then
|
||||
return
|
||||
else
|
||||
waiting_for_default_value = false
|
||||
end
|
||||
end
|
||||
if value == state.search_pattern then
|
||||
return
|
||||
elseif value == nil then
|
||||
return
|
||||
elseif value == "" then
|
||||
if state.search_pattern == nil then
|
||||
return
|
||||
end
|
||||
log.trace("Resetting search in on_change")
|
||||
local original_open_folders = nil
|
||||
if type(state.open_folders_before_search) == "table" then
|
||||
original_open_folders = vim.deepcopy(state.open_folders_before_search, { noref = 1 })
|
||||
end
|
||||
fs.reset_search(state)
|
||||
state.open_folders_before_search = original_open_folders
|
||||
else
|
||||
log.trace("Setting search in on_change to: " .. value)
|
||||
state.search_pattern = value
|
||||
state.fuzzy_finder_mode = fuzzy_finder_mode
|
||||
if use_fzy then
|
||||
state.sort_function_override = sort_by_score
|
||||
state.use_fzy = true
|
||||
end
|
||||
local callback = select_first_file
|
||||
if fuzzy_finder_mode == "directory" then
|
||||
callback = nil
|
||||
end
|
||||
|
||||
local len = #value
|
||||
local delay = 500
|
||||
if len > 3 then
|
||||
delay = 100
|
||||
elseif len > 2 then
|
||||
delay = 200
|
||||
elseif len > 1 then
|
||||
delay = 400
|
||||
end
|
||||
|
||||
utils.debounce("filesystem_filter", function()
|
||||
fs._navigate_internal(state, nil, nil, callback)
|
||||
end, delay, utils.debounce_strategy.CALL_LAST_ONLY)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
|
||||
local restore_height = vim.schedule_wrap(function()
|
||||
if vim.api.nvim_win_is_valid(winid) then
|
||||
vim.api.nvim_win_set_height(winid, height)
|
||||
end
|
||||
end)
|
||||
input:map("i", "<esc>", function(bufnr)
|
||||
vim.cmd("stopinsert")
|
||||
input:unmount()
|
||||
if fuzzy_finder_mode and utils.truthy(state.search_pattern) then
|
||||
fs.reset_search(state, true)
|
||||
end
|
||||
restore_height()
|
||||
end, { noremap = true })
|
||||
|
||||
input:map("i", "<C-w>", "<C-S-w>", { noremap = true })
|
||||
|
||||
input:on({ event.BufLeave, event.BufDelete }, function()
|
||||
vim.cmd("stopinsert")
|
||||
input:unmount()
|
||||
-- If this was closed due to submit, that function will handle the reset_search
|
||||
vim.defer_fn(function()
|
||||
if fuzzy_finder_mode and utils.truthy(state.search_pattern) then
|
||||
fs.reset_search(state, true)
|
||||
end
|
||||
end, 100)
|
||||
restore_height()
|
||||
end, { once = true })
|
||||
|
||||
if fuzzy_finder_mode then
|
||||
local config = require("neo-tree").config
|
||||
for lhs, cmd_name in pairs(config.filesystem.window.fuzzy_finder_mappings) do
|
||||
local cmd = cmds[cmd_name]
|
||||
if cmd then
|
||||
input:map("i", lhs, create_input_mapping_handle(cmd, state, scroll_padding), { noremap = true })
|
||||
else
|
||||
log.warn(string.format('Invalid command in fuzzy_finder_mappings: %s = %s', lhs, cmd_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
392
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/filter_external.lua
vendored
Normal file
392
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/filter_external.lua
vendored
Normal file
@ -0,0 +1,392 @@
|
||||
local vim = vim
|
||||
local log = require("neo-tree.log")
|
||||
local Job = require("plenary.job")
|
||||
local utils = require("neo-tree.utils")
|
||||
local Queue = require("neo-tree.collections").Queue
|
||||
|
||||
local M = {}
|
||||
local fd_supports_max_results = nil
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local test_for_max_results = function(cmd)
|
||||
if fd_supports_max_results == nil then
|
||||
if cmd == "fd" or cmd == "fdfind" then
|
||||
--test if it supports the max-results option
|
||||
local test = vim.fn.system(cmd .. " this_is_only_a_test --max-depth=1 --max-results=1")
|
||||
if test:match("^error:") then
|
||||
fd_supports_max_results = false
|
||||
log.debug(cmd, "does NOT support max-results")
|
||||
else
|
||||
fd_supports_max_results = true
|
||||
log.debug(cmd, "supports max-results")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local get_find_command = function(state)
|
||||
if state.find_command then
|
||||
test_for_max_results(state.find_command)
|
||||
return state.find_command
|
||||
end
|
||||
|
||||
if 1 == vim.fn.executable("fdfind") then
|
||||
state.find_command = "fdfind"
|
||||
elseif 1 == vim.fn.executable("fd") then
|
||||
state.find_command = "fd"
|
||||
elseif 1 == vim.fn.executable("find") and vim.fn.has("win32") == 0 then
|
||||
state.find_command = "find"
|
||||
elseif 1 == vim.fn.executable("where") then
|
||||
state.find_command = "where"
|
||||
end
|
||||
|
||||
test_for_max_results(state.find_command)
|
||||
return state.find_command
|
||||
end
|
||||
|
||||
local running_jobs = Queue:new()
|
||||
local kill_job = function(job)
|
||||
local pid = job.pid
|
||||
job:shutdown()
|
||||
if pid ~= nil and pid > 0 then
|
||||
if utils.is_windows then
|
||||
vim.fn.system("taskkill /F /T /PID " .. pid)
|
||||
else
|
||||
vim.fn.system("kill -9 " .. pid)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
M.cancel = function()
|
||||
if running_jobs:is_empty() then
|
||||
return
|
||||
end
|
||||
running_jobs:for_each(kill_job)
|
||||
end
|
||||
|
||||
---@class FileTypes
|
||||
---@field file boolean
|
||||
---@field directory boolean
|
||||
---@field symlink boolean
|
||||
---@field socket boolean
|
||||
---@field pipe boolean
|
||||
---@field executable boolean
|
||||
---@field empty boolean
|
||||
---@field block boolean Only for `find`
|
||||
---@field character boolean Only for `find`
|
||||
|
||||
---filter_files_external
|
||||
-- Spawns a filter command based on `cmd`
|
||||
---@param cmd string Command to execute. Use `get_find_command` most times.
|
||||
---@param path string Base directory to start the search.
|
||||
---@param glob string | nil If not nil, do glob search. Take precedence on `regex`
|
||||
---@param regex string | nil If not nil, do regex search if command supports. if glob ~= nil, ignored
|
||||
---@param full_path boolean If true, search agaist the absolute path
|
||||
---@param types FileTypes | nil Return only true filetypes. If nil, all are returned.
|
||||
---@param ignore { dotfiles: boolean?, gitignore: boolean? } If true, ignored from result. Default: false
|
||||
---@param limit? integer | nil Maximim number of results. nil will return everything.
|
||||
---@param find_args? string[] | table<string, string[]> Any additional options passed to command if any.
|
||||
---@param on_insert? fun(err: string, line: string): any Executed for each line of stdout and stderr.
|
||||
---@param on_exit? fun(return_val: table): any Executed at the end.
|
||||
M.filter_files_external = function(
|
||||
cmd,
|
||||
path,
|
||||
glob,
|
||||
regex,
|
||||
full_path,
|
||||
types,
|
||||
ignore,
|
||||
limit,
|
||||
find_args,
|
||||
on_insert,
|
||||
on_exit
|
||||
)
|
||||
if glob ~= nil and regex ~= nil then
|
||||
local log_msg = string.format([[glob: %s, regex: %s]], glob, regex)
|
||||
log.warn("both glob and regex are set. glob will take precedence. " .. log_msg)
|
||||
end
|
||||
ignore = ignore or {}
|
||||
types = types or {}
|
||||
limit = limit or math.huge -- math.huge == no limit
|
||||
local file_type_map = {
|
||||
file = "f",
|
||||
directory = "d",
|
||||
symlink = "l",
|
||||
socket = "s",
|
||||
pipe = "p",
|
||||
executable = "x", -- only for `fd`
|
||||
empty = "e", -- only for `fd`
|
||||
block = "b", -- only for `find`
|
||||
character = "c", -- only for `find`
|
||||
}
|
||||
|
||||
local args = {}
|
||||
local function append(...)
|
||||
for _, v in pairs({ ... }) do
|
||||
if v ~= nil then
|
||||
args[#args + 1] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function append_find_args()
|
||||
if find_args then
|
||||
if type(find_args) == "string" then
|
||||
append(find_args)
|
||||
elseif type(find_args) == "table" then
|
||||
if find_args[1] then
|
||||
append(unpack(find_args))
|
||||
elseif find_args[cmd] then
|
||||
append(unpack(find_args[cmd])) ---@diagnostic disable-line
|
||||
end
|
||||
elseif type(find_args) == "function" then
|
||||
args = find_args(cmd, path, glob, args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if cmd == "fd" or cmd == "fdfind" then
|
||||
if not ignore.dotfiles then
|
||||
append("--hidden")
|
||||
end
|
||||
if not ignore.gitignore then
|
||||
append("--no-ignore")
|
||||
end
|
||||
append("--color", "never")
|
||||
if fd_supports_max_results and 0 < limit and limit < math.huge then
|
||||
append("--max-results", limit)
|
||||
end
|
||||
for k, v in pairs(types) do
|
||||
if v and file_type_map[k] ~= nil then
|
||||
append("--type", k)
|
||||
end
|
||||
end
|
||||
if full_path then
|
||||
append("--full-path")
|
||||
if glob ~= nil then
|
||||
local words = utils.split(glob, " ")
|
||||
regex = ".*" .. table.concat(words, ".*") .. ".*"
|
||||
glob = nil
|
||||
end
|
||||
end
|
||||
if glob ~= nil then
|
||||
append("--glob")
|
||||
end
|
||||
append_find_args()
|
||||
append("--", glob or regex or "")
|
||||
append(path)
|
||||
elseif cmd == "find" then
|
||||
append(path)
|
||||
local file_types = {}
|
||||
for k, v in pairs(types) do
|
||||
if v and file_type_map[k] ~= nil then
|
||||
file_types[#file_types + 1] = file_type_map[k]
|
||||
end
|
||||
end
|
||||
if #file_types > 0 then
|
||||
append("-type", table.concat(file_types, ","))
|
||||
end
|
||||
if types.empty then
|
||||
append("-empty")
|
||||
end
|
||||
if types.executable then
|
||||
append("-executable")
|
||||
end
|
||||
if not ignore.dotfiles then
|
||||
append("-not", "-path", "*/.*")
|
||||
end
|
||||
if glob ~= nil and not full_path then
|
||||
append("-iname", glob)
|
||||
elseif glob ~= nil and full_path then
|
||||
local words = utils.split(glob, " ")
|
||||
regex = ".*" .. table.concat(words, ".*") .. ".*"
|
||||
append("-regextype", "sed", "-regex", regex)
|
||||
elseif regex ~= nil then
|
||||
append("-regextype", "sed", "-regex", regex)
|
||||
end
|
||||
append_find_args()
|
||||
elseif cmd == "fzf" then
|
||||
-- This does not work yet, there's some kind of issue with how fzf uses stdout
|
||||
error("fzf is not a supported find_command")
|
||||
append_find_args()
|
||||
append("--no-sort", "--no-expect", "--filter", glob or regex) -- using the raw term without glob patterns
|
||||
elseif cmd == "where" then
|
||||
append_find_args()
|
||||
append("/r", path, glob or regex)
|
||||
else
|
||||
return { "No search command found!" }
|
||||
end
|
||||
|
||||
if fd_supports_max_results then
|
||||
limit = math.huge -- `fd` manages limit on its own
|
||||
end
|
||||
local item_count = 0
|
||||
local job = Job:new({
|
||||
command = cmd,
|
||||
cwd = path,
|
||||
args = args,
|
||||
enable_recording = false,
|
||||
on_stdout = function(err, line)
|
||||
if item_count < limit and on_insert then
|
||||
on_insert(err, line)
|
||||
item_count = item_count + 1
|
||||
end
|
||||
end,
|
||||
on_stderr = function(err, line)
|
||||
if item_count < limit and on_insert then
|
||||
on_insert(err or line, line)
|
||||
-- item_count = item_count + 1
|
||||
end
|
||||
end,
|
||||
on_exit = function(_, return_val)
|
||||
if on_exit then
|
||||
on_exit(return_val)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- This ensures that only one job is running at a time
|
||||
running_jobs:for_each(kill_job)
|
||||
running_jobs:add(job)
|
||||
job:start()
|
||||
end
|
||||
|
||||
local function fzy_sort_get_total_score(terms, path)
|
||||
local fzy = require("neo-tree.sources.common.filters.filter_fzy")
|
||||
local total_score = 0
|
||||
for _, term in ipairs(terms) do -- spaces in `opts.term` are treated as `and`
|
||||
local score = fzy.score(term, path)
|
||||
if score == fzy.get_score_min() then -- if any not found, end searching
|
||||
return 0
|
||||
end
|
||||
total_score = total_score + score
|
||||
end
|
||||
return total_score
|
||||
end
|
||||
|
||||
local function modify_parent_scores(result_scores, path, score)
|
||||
local parent, _ = utils.split_path(path)
|
||||
while parent ~= nil do -- back propagate the score to its ancesters
|
||||
if score > (result_scores[parent] or 0) then
|
||||
result_scores[parent] = score
|
||||
parent, _ = utils.split_path(parent)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.fzy_sort_files = function(opts, state)
|
||||
state = state or {}
|
||||
local filters = opts.filtered_items
|
||||
local limit = opts.limit or 100
|
||||
local full_path_words = opts.find_by_full_path_words
|
||||
local fuzzy_finder_mode = opts.fuzzy_finder_mode
|
||||
local pwd = opts.path
|
||||
if pwd:sub(-1) ~= "/" then
|
||||
pwd = pwd .. "/"
|
||||
end
|
||||
local pwd_length = #pwd
|
||||
local terms = {}
|
||||
for term in string.gmatch(opts.term, "[^%s]+") do -- space split opts.term
|
||||
terms[#terms + 1] = term
|
||||
end
|
||||
|
||||
-- The base search is anything that contains the characters in the term
|
||||
-- The fzy score is then used to sort the results
|
||||
local chars = {}
|
||||
local regex = ".*"
|
||||
local chars_to_escape =
|
||||
{ "%", "+", "-", "?", "[", "^", "$", "(", ")", "{", "}", "=", "!", "<", ">", "|", ":", "#" }
|
||||
for _, term in ipairs(terms) do
|
||||
for c in term:gmatch(".") do
|
||||
if not chars[c] then
|
||||
chars[c] = true
|
||||
if chars_to_escape[c] then
|
||||
c = [[\]] .. c
|
||||
end
|
||||
regex = regex .. c .. ".*"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local result_counter = 0
|
||||
|
||||
local index = 1
|
||||
state.fzy_sort_result_scores = {}
|
||||
local function on_insert(err, path)
|
||||
if not err then
|
||||
local relative_path = path
|
||||
if not full_path_words and #path > pwd_length and path:sub(1, pwd_length) == pwd then
|
||||
relative_path = "./" .. path:sub(pwd_length + 1)
|
||||
end
|
||||
index = index + 1
|
||||
if state.fzy_sort_result_scores == nil then
|
||||
state.fzy_sort_result_scores = {}
|
||||
end
|
||||
state.fzy_sort_result_scores[path] = 0
|
||||
local score = fzy_sort_get_total_score(terms, relative_path)
|
||||
if score > 0 then
|
||||
state.fzy_sort_result_scores[path] = score
|
||||
result_counter = result_counter + 1
|
||||
modify_parent_scores(state.fzy_sort_result_scores, path, score)
|
||||
opts.on_insert(nil, path)
|
||||
if result_counter >= limit then
|
||||
vim.schedule(M.cancel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.filter_files_external(
|
||||
get_find_command(state),
|
||||
pwd,
|
||||
nil,
|
||||
regex,
|
||||
true,
|
||||
{ directory = fuzzy_finder_mode == "directory", file = fuzzy_finder_mode ~= "directory" },
|
||||
{
|
||||
dotfiles = not filters.visible and filters.hide_dotfiles,
|
||||
gitignore = not filters.visible and filters.hide_gitignored,
|
||||
},
|
||||
nil,
|
||||
opts.find_args,
|
||||
on_insert,
|
||||
opts.on_exit
|
||||
)
|
||||
end
|
||||
|
||||
M.find_files = function(opts)
|
||||
local filters = opts.filtered_items
|
||||
local full_path_words = opts.find_by_full_path_words
|
||||
local regex, glob = nil, nil
|
||||
local fuzzy_finder_mode = opts.fuzzy_finder_mode
|
||||
|
||||
glob = opts.term
|
||||
if glob:sub(1) ~= "*" then
|
||||
glob = "*" .. glob
|
||||
end
|
||||
if glob:sub(-1) ~= "*" then
|
||||
glob = glob .. "*"
|
||||
end
|
||||
|
||||
M.filter_files_external(
|
||||
get_find_command(opts),
|
||||
opts.path,
|
||||
glob,
|
||||
regex,
|
||||
full_path_words,
|
||||
{ directory = fuzzy_finder_mode == "directory" },
|
||||
{
|
||||
dotfiles = not filters.visible and filters.hide_dotfiles,
|
||||
gitignore = not filters.visible and filters.hide_gitignored,
|
||||
},
|
||||
opts.limit or 200,
|
||||
opts.find_args,
|
||||
opts.on_insert,
|
||||
opts.on_exit
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
591
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/fs_actions.lua
vendored
Normal file
591
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/fs_actions.lua
vendored
Normal file
@ -0,0 +1,591 @@
|
||||
-- This file is for functions that mutate the filesystem.
|
||||
|
||||
-- This code started out as a copy from:
|
||||
-- https://github.com/mhartington/dotfiles
|
||||
-- and modified to fit neo-tree's api.
|
||||
-- Permalink: https://github.com/mhartington/dotfiles/blob/7560986378753e0c047d940452cb03a3b6439b11/config/nvim/lua/mh/filetree/init.lua
|
||||
local vim = vim
|
||||
local api = vim.api
|
||||
local loop = vim.loop
|
||||
local scan = require("plenary.scandir")
|
||||
local utils = require("neo-tree.utils")
|
||||
local inputs = require("neo-tree.ui.inputs")
|
||||
local events = require("neo-tree.events")
|
||||
local log = require("neo-tree.log")
|
||||
local Path = require("plenary").path
|
||||
|
||||
local M = {}
|
||||
|
||||
local function clear_buffer(path)
|
||||
local buf = utils.find_buffer_by_name(path)
|
||||
if buf < 1 then
|
||||
return
|
||||
end
|
||||
local alt = vim.fn.bufnr("#")
|
||||
-- Check all windows to see if they are using the buffer
|
||||
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
||||
if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == buf then
|
||||
-- if there is no alternate buffer yet, create a blank one now
|
||||
if alt < 1 or alt == buf then
|
||||
alt = vim.api.nvim_create_buf(true, false)
|
||||
end
|
||||
-- replace the buffer displayed in this window with the alternate buffer
|
||||
vim.api.nvim_win_set_buf(win, alt)
|
||||
end
|
||||
end
|
||||
local success, msg = pcall(vim.api.nvim_buf_delete, buf, { force = true })
|
||||
if not success then
|
||||
log.error("Could not clear buffer: ", msg)
|
||||
end
|
||||
end
|
||||
|
||||
---Opens new_buf in each window that has old_buf currently open.
|
||||
---Useful during file rename.
|
||||
---@param old_buf number
|
||||
---@param new_buf number
|
||||
local function replace_buffer_in_windows(old_buf, new_buf)
|
||||
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
||||
if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == old_buf then
|
||||
vim.api.nvim_win_set_buf(win, new_buf)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function rename_buffer(old_path, new_path)
|
||||
local force_save = function()
|
||||
vim.cmd("silent! write!")
|
||||
end
|
||||
|
||||
for _, buf in pairs(vim.api.nvim_list_bufs()) do
|
||||
if vim.api.nvim_buf_is_loaded(buf) then
|
||||
local buf_name = vim.api.nvim_buf_get_name(buf)
|
||||
local new_buf_name = nil
|
||||
if old_path == buf_name then
|
||||
new_buf_name = new_path
|
||||
elseif utils.is_subpath(old_path, buf_name) then
|
||||
new_buf_name = new_path .. buf_name:sub(#old_path + 1)
|
||||
end
|
||||
if utils.truthy(new_buf_name) then
|
||||
local new_buf = vim.fn.bufadd(new_buf_name)
|
||||
vim.fn.bufload(new_buf)
|
||||
vim.api.nvim_buf_set_option(new_buf, "buflisted", true)
|
||||
replace_buffer_in_windows(buf, new_buf)
|
||||
|
||||
if vim.api.nvim_buf_get_option(buf, "buftype") == "" then
|
||||
local modified = vim.api.nvim_buf_get_option(buf, "modified")
|
||||
if modified then
|
||||
local old_buffer_lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
vim.api.nvim_buf_set_lines(new_buf, 0, -1, false, old_buffer_lines)
|
||||
|
||||
local msg = buf_name .. " has been modified. Save under new name? (y/n) "
|
||||
inputs.confirm(msg, function(confirmed)
|
||||
if confirmed then
|
||||
vim.api.nvim_buf_call(new_buf, force_save)
|
||||
log.trace("Force saving renamed buffer with changes")
|
||||
else
|
||||
vim.cmd("echohl WarningMsg")
|
||||
vim.cmd(
|
||||
[[echo "Skipping force save. You'll need to save it with `:w!` when you are ready to force writing with the new name."]]
|
||||
)
|
||||
vim.cmd("echohl NONE")
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
vim.api.nvim_buf_delete(buf, { force = true })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function create_all_parents(path)
|
||||
local create_all_as_folders
|
||||
function create_all_as_folders(in_path)
|
||||
if not loop.fs_stat(in_path) then
|
||||
local parent, _ = utils.split_path(in_path)
|
||||
if parent then
|
||||
create_all_as_folders(parent)
|
||||
end
|
||||
loop.fs_mkdir(in_path, 493)
|
||||
end
|
||||
end
|
||||
|
||||
local parent_path, _ = utils.split_path(path)
|
||||
create_all_as_folders(parent_path)
|
||||
end
|
||||
|
||||
-- Gets a non-existing filename from the user and executes the callback with it.
|
||||
local function get_unused_name(
|
||||
destination,
|
||||
using_root_directory,
|
||||
name_chosen_callback,
|
||||
first_message
|
||||
)
|
||||
if loop.fs_stat(destination) then
|
||||
local parent_path, name
|
||||
if not using_root_directory then
|
||||
parent_path, name = utils.split_path(destination)
|
||||
elseif #using_root_directory > 0 then
|
||||
parent_path = destination:sub(1, #using_root_directory)
|
||||
name = destination:sub(#using_root_directory + 2)
|
||||
else
|
||||
parent_path = nil
|
||||
name = destination
|
||||
end
|
||||
|
||||
local message = first_message or name .. " already exists. Please enter a new name: "
|
||||
inputs.input(message, name, function(new_name)
|
||||
if new_name and string.len(new_name) > 0 then
|
||||
local new_path = parent_path and parent_path .. utils.path_separator .. new_name or new_name
|
||||
get_unused_name(new_path, using_root_directory, name_chosen_callback)
|
||||
end
|
||||
end)
|
||||
else
|
||||
name_chosen_callback(destination)
|
||||
end
|
||||
end
|
||||
|
||||
-- Move Node
|
||||
M.move_node = function(source, destination, callback, using_root_directory)
|
||||
log.trace(
|
||||
"Moving node: ",
|
||||
source,
|
||||
" to ",
|
||||
destination,
|
||||
", using root directory: ",
|
||||
using_root_directory
|
||||
)
|
||||
local _, name = utils.split_path(source)
|
||||
get_unused_name(destination or source, using_root_directory, function(dest)
|
||||
local function move_file()
|
||||
create_all_parents(dest)
|
||||
loop.fs_rename(source, dest, function(err)
|
||||
if err then
|
||||
log.error("Could not move the files from", source, "to", dest, ":", err)
|
||||
return
|
||||
end
|
||||
vim.schedule(function()
|
||||
rename_buffer(source, dest)
|
||||
end)
|
||||
vim.schedule(function()
|
||||
events.fire_event(events.FILE_MOVED, {
|
||||
source = source,
|
||||
destination = dest,
|
||||
})
|
||||
if callback then
|
||||
callback(source, dest)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
local event_result = events.fire_event(events.BEFORE_FILE_MOVE, {
|
||||
source = source,
|
||||
destination = dest,
|
||||
callback = move_file,
|
||||
}) or {}
|
||||
if event_result.handled then
|
||||
return
|
||||
end
|
||||
move_file()
|
||||
end, 'Move "' .. name .. '" to:')
|
||||
end
|
||||
|
||||
---Plenary path.copy() when used to copy a recursive structure, can return a nested
|
||||
-- table with for each file a Path instance and the success result.
|
||||
---@param copy_result table The output of Path.copy()
|
||||
---@param flat_result table Return value containing the flattened results
|
||||
local function flatten_path_copy_result(flat_result, copy_result)
|
||||
if not copy_result then
|
||||
return
|
||||
end
|
||||
for k, v in pairs(copy_result) do
|
||||
if type(v) == "table" then
|
||||
flatten_path_copy_result(flat_result, v)
|
||||
else
|
||||
table.insert(flat_result, { destination = k.filename, success = v })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if all files were copied successfully, using the flattened copy result
|
||||
local function check_path_copy_result(flat_result)
|
||||
if not flat_result then
|
||||
return
|
||||
end
|
||||
for _, file_result in ipairs(flat_result) do
|
||||
if not file_result.success then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Copy Node
|
||||
M.copy_node = function(source, _destination, callback, using_root_directory)
|
||||
local _, name = utils.split_path(source)
|
||||
get_unused_name(_destination or source, using_root_directory, function(destination)
|
||||
local parent_path, _ = utils.split_path(destination)
|
||||
if source == parent_path then
|
||||
log.warn("Cannot copy a file/folder to itself")
|
||||
return
|
||||
end
|
||||
local source_path = Path:new(source)
|
||||
if source_path:is_file() then
|
||||
-- When the source is a file, then Path.copy() currently doesn't create
|
||||
-- the potential non-existing parent directories of the destination.
|
||||
create_all_parents(destination)
|
||||
end
|
||||
local success, result = pcall(source_path.copy, source_path, {
|
||||
destination = destination,
|
||||
recursive = true,
|
||||
parents = true,
|
||||
})
|
||||
if not success then
|
||||
log.error("Could not copy the file(s) from", source, "to", destination, ":", result)
|
||||
return
|
||||
end
|
||||
|
||||
-- It can happen that the Path.copy() function returns successfully but
|
||||
-- the copy action still failed. In this case the copy() result contains
|
||||
-- a nested table of Path instances for each file copied, and the success
|
||||
-- result.
|
||||
local flat_result = {}
|
||||
flatten_path_copy_result(flat_result, result)
|
||||
if not check_path_copy_result(flat_result) then
|
||||
log.error("Could not copy the file(s) from", source, "to", destination, ":", flat_result)
|
||||
return
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
events.fire_event(events.FILE_ADDED, destination)
|
||||
if callback then
|
||||
callback(source, destination)
|
||||
end
|
||||
end)
|
||||
end, 'Copy "' .. name .. '" to:')
|
||||
end
|
||||
|
||||
--- Create a new directory
|
||||
M.create_directory = function(in_directory, callback, using_root_directory)
|
||||
local base
|
||||
if type(using_root_directory) == "string" then
|
||||
if in_directory == using_root_directory then
|
||||
base = ""
|
||||
elseif #using_root_directory > 0 then
|
||||
base = in_directory:sub(#using_root_directory + 2) .. utils.path_separator
|
||||
else
|
||||
base = in_directory .. utils.path_separator
|
||||
end
|
||||
else
|
||||
base = vim.fn.fnamemodify(in_directory .. utils.path_separator, ":~")
|
||||
using_root_directory = false
|
||||
end
|
||||
|
||||
inputs.input("Enter name for new directory:", base, function(destinations)
|
||||
if not destinations then
|
||||
return
|
||||
end
|
||||
|
||||
for _, destination in ipairs(utils.brace_expand(destinations)) do
|
||||
if not destination or destination == base then
|
||||
return
|
||||
end
|
||||
|
||||
if using_root_directory then
|
||||
destination = utils.path_join(using_root_directory, destination)
|
||||
else
|
||||
destination = vim.fn.fnamemodify(destination, ":p")
|
||||
end
|
||||
|
||||
if loop.fs_stat(destination) then
|
||||
log.warn("Directory already exists")
|
||||
return
|
||||
end
|
||||
|
||||
create_all_parents(destination)
|
||||
loop.fs_mkdir(destination, 493)
|
||||
|
||||
vim.schedule(function()
|
||||
events.fire_event(events.FILE_ADDED, destination)
|
||||
if callback then
|
||||
callback(destination)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Create Node
|
||||
M.create_node = function(in_directory, callback, using_root_directory)
|
||||
local base
|
||||
if type(using_root_directory) == "string" then
|
||||
if in_directory == using_root_directory then
|
||||
base = ""
|
||||
elseif #using_root_directory > 0 then
|
||||
base = in_directory:sub(#using_root_directory + 2) .. utils.path_separator
|
||||
else
|
||||
base = in_directory .. utils.path_separator
|
||||
end
|
||||
else
|
||||
base = vim.fn.fnamemodify(in_directory .. utils.path_separator, ":~")
|
||||
using_root_directory = false
|
||||
end
|
||||
|
||||
inputs.input(
|
||||
'Enter name for new file or directory (dirs end with a "/"):',
|
||||
base,
|
||||
function(destinations)
|
||||
if not destinations then
|
||||
return
|
||||
end
|
||||
|
||||
for _, destination in ipairs(utils.brace_expand(destinations)) do
|
||||
if not destination or destination == base then
|
||||
return
|
||||
end
|
||||
local is_dir = vim.endswith(destination, "/")
|
||||
|
||||
if using_root_directory then
|
||||
destination = utils.path_join(using_root_directory, destination)
|
||||
else
|
||||
destination = vim.fn.fnamemodify(destination, ":p")
|
||||
end
|
||||
|
||||
if loop.fs_stat(destination) then
|
||||
log.warn("File already exists")
|
||||
return
|
||||
end
|
||||
|
||||
create_all_parents(destination)
|
||||
if is_dir then
|
||||
loop.fs_mkdir(destination, 493)
|
||||
else
|
||||
local open_mode = loop.constants.O_CREAT
|
||||
+ loop.constants.O_WRONLY
|
||||
+ loop.constants.O_TRUNC
|
||||
local fd = loop.fs_open(destination, "w", open_mode)
|
||||
if not fd then
|
||||
if not loop.fs_stat(destination) then
|
||||
api.nvim_err_writeln("Could not create file " .. destination)
|
||||
return
|
||||
else
|
||||
log.warn("Failed to complete file creation of " .. destination)
|
||||
end
|
||||
else
|
||||
loop.fs_chmod(destination, 420)
|
||||
loop.fs_close(fd)
|
||||
end
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
events.fire_event(events.FILE_ADDED, destination)
|
||||
if callback then
|
||||
callback(destination)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
-- Delete Node
|
||||
M.delete_node = function(path, callback, noconfirm)
|
||||
local _, name = utils.split_path(path)
|
||||
local msg = string.format("Are you sure you want to delete '%s'?", name)
|
||||
|
||||
log.trace("Deleting node: ", path)
|
||||
local _type = "unknown"
|
||||
local stat = loop.fs_stat(path)
|
||||
if stat then
|
||||
_type = stat.type
|
||||
if _type == "link" then
|
||||
local link_to = loop.fs_readlink(path)
|
||||
if not link_to then
|
||||
log.error("Could not read link")
|
||||
return
|
||||
end
|
||||
_type = loop.fs_stat(link_to)
|
||||
end
|
||||
if _type == "directory" then
|
||||
local children = scan.scan_dir(path, {
|
||||
hidden = true,
|
||||
respect_gitignore = false,
|
||||
add_dirs = true,
|
||||
depth = 1,
|
||||
})
|
||||
if #children > 0 then
|
||||
msg = "WARNING: Dir not empty! " .. msg
|
||||
end
|
||||
end
|
||||
else
|
||||
log.warn("Could not read file/dir:", path, stat, ", attempting to delete anyway...")
|
||||
-- Guess the type by whether it appears to have an extension
|
||||
if path:match("%.(.+)$") then
|
||||
_type = "file"
|
||||
else
|
||||
_type = "directory"
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local do_delete = function(confirmed)
|
||||
if not confirmed then
|
||||
return
|
||||
end
|
||||
|
||||
local function delete_dir(dir_path)
|
||||
local handle = loop.fs_scandir(dir_path)
|
||||
if type(handle) == "string" then
|
||||
return api.nvim_err_writeln(handle)
|
||||
end
|
||||
|
||||
while true do
|
||||
local child_name, t = loop.fs_scandir_next(handle)
|
||||
if not child_name then
|
||||
break
|
||||
end
|
||||
|
||||
local child_path = dir_path .. "/" .. child_name
|
||||
if t == "directory" then
|
||||
local success = delete_dir(child_path)
|
||||
if not success then
|
||||
log.error("failed to delete ", child_path)
|
||||
return false
|
||||
end
|
||||
else
|
||||
local success = loop.fs_unlink(child_path)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
clear_buffer(child_path)
|
||||
end
|
||||
end
|
||||
return loop.fs_rmdir(dir_path)
|
||||
end
|
||||
|
||||
if _type == "directory" then
|
||||
-- first try using native system commands, which are recursive
|
||||
local success = false
|
||||
if utils.is_windows then
|
||||
local result = vim.fn.system({ "cmd.exe", "/c", "rmdir", "/s", "/q", path })
|
||||
local error = vim.v.shell_error
|
||||
if error ~= 0 then
|
||||
log.debug("Could not delete directory '", path, "' with rmdir: ", result)
|
||||
else
|
||||
log.info("Deleted directory ", path)
|
||||
success = true
|
||||
end
|
||||
else
|
||||
local result = vim.fn.system({ "rm", "-Rf", path })
|
||||
local error = vim.v.shell_error
|
||||
if error ~= 0 then
|
||||
log.debug("Could not delete directory '", path, "' with rm: ", result)
|
||||
else
|
||||
log.info("Deleted directory ", path)
|
||||
success = true
|
||||
end
|
||||
end
|
||||
-- Fallback to using libuv if native commands fail
|
||||
if not success then
|
||||
success = delete_dir(path)
|
||||
if not success then
|
||||
return api.nvim_err_writeln("Could not remove directory: " .. path)
|
||||
end
|
||||
end
|
||||
else
|
||||
local success = loop.fs_unlink(path)
|
||||
if not success then
|
||||
return api.nvim_err_writeln("Could not remove file: " .. path)
|
||||
end
|
||||
clear_buffer(path)
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
events.fire_event(events.FILE_DELETED, path)
|
||||
if callback then
|
||||
callback(path)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if noconfirm then
|
||||
do_delete(true)
|
||||
else
|
||||
inputs.confirm(msg, do_delete)
|
||||
end
|
||||
end
|
||||
|
||||
M.delete_nodes = function(paths_to_delete, callback)
|
||||
local msg = "Are you sure you want to delete " .. #paths_to_delete .. " items?"
|
||||
inputs.confirm(msg, function(confirmed)
|
||||
if not confirmed then
|
||||
return
|
||||
end
|
||||
|
||||
for _, path in ipairs(paths_to_delete) do
|
||||
M.delete_node(path, nil, true)
|
||||
end
|
||||
|
||||
if callback then
|
||||
vim.schedule(function()
|
||||
callback(paths_to_delete[#paths_to_delete])
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Rename Node
|
||||
M.rename_node = function(path, callback)
|
||||
local parent_path, name = utils.split_path(path)
|
||||
local msg = string.format('Enter new name for "%s":', name)
|
||||
|
||||
inputs.input(msg, name, function(new_name)
|
||||
-- If cancelled
|
||||
if not new_name or new_name == "" then
|
||||
log.info("Operation canceled")
|
||||
return
|
||||
end
|
||||
|
||||
local destination = parent_path .. utils.path_separator .. new_name
|
||||
-- If aleady exists
|
||||
if loop.fs_stat(destination) then
|
||||
log.warn(destination, " already exists")
|
||||
return
|
||||
end
|
||||
|
||||
local complete = vim.schedule_wrap(function()
|
||||
rename_buffer(path, destination)
|
||||
events.fire_event(events.FILE_RENAMED, {
|
||||
source = path,
|
||||
destination = destination,
|
||||
})
|
||||
if callback then
|
||||
callback(path, destination)
|
||||
end
|
||||
log.info("Renamed " .. new_name .. " successfully")
|
||||
end)
|
||||
|
||||
local function fs_rename()
|
||||
loop.fs_rename(path, destination, function(err)
|
||||
if err then
|
||||
log.warn("Could not rename the files")
|
||||
return
|
||||
else
|
||||
complete()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local event_result = events.fire_event(events.BEFORE_FILE_RENAME, {
|
||||
source = path,
|
||||
destination = destination,
|
||||
callback = fs_rename,
|
||||
}) or {}
|
||||
if event_result.handled then
|
||||
return
|
||||
end
|
||||
fs_rename()
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
505
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/fs_scan.lua
vendored
Normal file
505
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/fs_scan.lua
vendored
Normal file
@ -0,0 +1,505 @@
|
||||
-- This files holds code for scanning the filesystem to build the tree.
|
||||
local uv = vim.loop
|
||||
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local utils = require("neo-tree.utils")
|
||||
local filter_external = require("neo-tree.sources.filesystem.lib.filter_external")
|
||||
local file_items = require("neo-tree.sources.common.file-items")
|
||||
local log = require("neo-tree.log")
|
||||
local fs_watch = require("neo-tree.sources.filesystem.lib.fs_watch")
|
||||
local git = require("neo-tree.git")
|
||||
local events = require("neo-tree.events")
|
||||
local async = require("plenary.async")
|
||||
|
||||
local Path = require("plenary.path")
|
||||
local os_sep = Path.path.sep
|
||||
|
||||
local M = {}
|
||||
|
||||
local on_directory_loaded = function(context, dir_path)
|
||||
local state = context.state
|
||||
local scanned_folder = context.folders[dir_path]
|
||||
if scanned_folder then
|
||||
scanned_folder.loaded = true
|
||||
end
|
||||
if state.use_libuv_file_watcher then
|
||||
local root = context.folders[dir_path]
|
||||
if root then
|
||||
local target_path = root.is_link and root.link_to or root.path
|
||||
local fs_watch_callback = vim.schedule_wrap(function(err, fname)
|
||||
if err then
|
||||
log.error("file_event_callback: ", err)
|
||||
return
|
||||
end
|
||||
if context.is_a_never_show_file(fname) then
|
||||
-- don't fire events for nodes that are designated as "never show"
|
||||
return
|
||||
else
|
||||
events.fire_event(events.FS_EVENT, { afile = target_path })
|
||||
end
|
||||
end)
|
||||
|
||||
log.trace("Adding fs watcher for ", target_path)
|
||||
fs_watch.watch_folder(target_path, fs_watch_callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local dir_complete = function(context, dir_path)
|
||||
local paths_to_load = context.paths_to_load
|
||||
local folders = context.folders
|
||||
|
||||
on_directory_loaded(context, dir_path)
|
||||
|
||||
-- check to see if there are more folders to load
|
||||
local next_path = nil
|
||||
while #paths_to_load > 0 and not next_path do
|
||||
next_path = table.remove(paths_to_load)
|
||||
-- ensure that the path is still valid
|
||||
local success, result = pcall(vim.loop.fs_stat, next_path)
|
||||
-- ensure that the result is a directory
|
||||
if success and result and result.type == "directory" then
|
||||
-- ensure that it is not already loaded
|
||||
local existing = folders[next_path]
|
||||
if existing and existing.loaded then
|
||||
next_path = nil
|
||||
end
|
||||
else
|
||||
-- if the path doesn't exist, skip it
|
||||
next_path = nil
|
||||
end
|
||||
end
|
||||
return next_path
|
||||
end
|
||||
|
||||
local render_context = function(context)
|
||||
local state = context.state
|
||||
local root = context.root
|
||||
local parent_id = context.parent_id
|
||||
|
||||
if not parent_id and state.use_libuv_file_watcher and state.enable_git_status then
|
||||
log.trace("Starting .git folder watcher")
|
||||
local path = root.path
|
||||
if root.is_link then
|
||||
path = root.link_to
|
||||
end
|
||||
fs_watch.watch_git_index(path, require("neo-tree").config.git_status_async)
|
||||
end
|
||||
fs_watch.updated_watched()
|
||||
|
||||
if root and root.children then
|
||||
file_items.deep_sort(root.children, state.sort_function_override)
|
||||
end
|
||||
if parent_id then
|
||||
-- lazy loading a child folder
|
||||
renderer.show_nodes(root.children, state, parent_id, context.callback)
|
||||
else
|
||||
-- full render of the tree
|
||||
renderer.show_nodes({ root }, state, nil, context.callback)
|
||||
end
|
||||
|
||||
context.state = nil
|
||||
context.callback = nil
|
||||
context.all_items = nil
|
||||
context.root = nil
|
||||
context.parent_id = nil
|
||||
context = nil
|
||||
end
|
||||
|
||||
local job_complete = function(context)
|
||||
local state = context.state
|
||||
local parent_id = context.parent_id
|
||||
if #context.all_items == 0 then
|
||||
log.info("No items, skipping git ignored/status lookups")
|
||||
render_context(context)
|
||||
return
|
||||
end
|
||||
if state.filtered_items.hide_gitignored or state.enable_git_status then
|
||||
if require("neo-tree").config.git_status_async then
|
||||
git.mark_ignored(state, context.all_items, function(all_items)
|
||||
if parent_id then
|
||||
vim.list_extend(state.git_ignored, all_items)
|
||||
else
|
||||
state.git_ignored = all_items
|
||||
end
|
||||
vim.schedule(function()
|
||||
render_context(context)
|
||||
end)
|
||||
end)
|
||||
return
|
||||
else
|
||||
local all_items = git.mark_ignored(state, context.all_items)
|
||||
if parent_id then
|
||||
vim.list_extend(state.git_ignored, all_items)
|
||||
else
|
||||
state.git_ignored = all_items
|
||||
end
|
||||
end
|
||||
end
|
||||
render_context(context)
|
||||
end
|
||||
|
||||
local function create_node(context, node)
|
||||
local success3, item = pcall(file_items.create_item, context, node.path, node.type)
|
||||
end
|
||||
|
||||
local function process_node(context, path)
|
||||
on_directory_loaded(context, path)
|
||||
end
|
||||
|
||||
local function get_children_sync(path)
|
||||
local children = {}
|
||||
local success, dir = pcall(vim.loop.fs_opendir, path, nil, 1000)
|
||||
if not success then
|
||||
log.error("Error opening dir:", dir)
|
||||
end
|
||||
local success2, stats = pcall(vim.loop.fs_readdir, dir)
|
||||
if success2 and stats then
|
||||
for _, stat in ipairs(stats) do
|
||||
local child_path = utils.path_join(path, stat.name)
|
||||
table.insert(children, { path = child_path, type = stat.type })
|
||||
end
|
||||
end
|
||||
pcall(vim.loop.fs_closedir, dir)
|
||||
return children
|
||||
end
|
||||
|
||||
local function get_children_async(path, callback)
|
||||
uv.fs_opendir(path, function(_, dir)
|
||||
uv.fs_readdir(dir, function(_, stats)
|
||||
local children = {}
|
||||
if stats then
|
||||
for _, stat in ipairs(stats) do
|
||||
local child_path = utils.path_join(path, stat.name)
|
||||
table.insert(children, { path = child_path, type = stat.type })
|
||||
end
|
||||
end
|
||||
uv.fs_closedir(dir)
|
||||
callback(children)
|
||||
end)
|
||||
end, 1000)
|
||||
end
|
||||
|
||||
local function scan_dir_sync(context, path)
|
||||
process_node(context, path)
|
||||
local children = get_children_sync(path)
|
||||
for _, child in ipairs(children) do
|
||||
create_node(context, child)
|
||||
if child.type == "directory" then
|
||||
local grandchild_nodes = get_children_sync(child.path)
|
||||
if
|
||||
grandchild_nodes == nil
|
||||
or #grandchild_nodes == 0
|
||||
or #grandchild_nodes == 1 and grandchild_nodes[1].type == "directory"
|
||||
then
|
||||
scan_dir_sync(context, child.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function scan_dir_async(context, path, callback)
|
||||
get_children_async(path, function(children)
|
||||
for _, child in ipairs(children) do
|
||||
create_node(context, child)
|
||||
if child.type == "directory" then
|
||||
local grandchild_nodes = get_children_sync(child.path)
|
||||
if
|
||||
grandchild_nodes == nil
|
||||
or #grandchild_nodes == 0
|
||||
or #grandchild_nodes == 1 and grandchild_nodes[1].type == "directory"
|
||||
then
|
||||
scan_dir_sync(context, child.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
process_node(context, path)
|
||||
callback(path)
|
||||
end)
|
||||
end
|
||||
|
||||
-- async_scan scans all the directories in context.paths_to_load
|
||||
-- and adds them as items to render in the UI.
|
||||
local function async_scan(context, path)
|
||||
log.trace("async_scan: ", path)
|
||||
local scan_mode = require("neo-tree").config.filesystem.scan_mode
|
||||
|
||||
if scan_mode == "deep" then
|
||||
local scan_tasks = {}
|
||||
for _, p in ipairs(context.paths_to_load) do
|
||||
local scan_task = async.wrap(function(callback)
|
||||
scan_dir_async(context, p, callback)
|
||||
end, 1)
|
||||
table.insert(scan_tasks, scan_task)
|
||||
end
|
||||
|
||||
async.util.run_all(
|
||||
scan_tasks,
|
||||
vim.schedule_wrap(function()
|
||||
job_complete(context)
|
||||
end)
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
-- scan_mode == "shallow"
|
||||
context.directories_scanned = 0
|
||||
context.directories_to_scan = #context.paths_to_load
|
||||
|
||||
context.on_exit = vim.schedule_wrap(function()
|
||||
job_complete(context)
|
||||
end)
|
||||
|
||||
-- from https://github.com/nvim-lua/plenary.nvim/blob/master/lua/plenary/scandir.lua
|
||||
local function read_dir(current_dir, ctx)
|
||||
uv.fs_opendir(current_dir, function(err, dir)
|
||||
if err then
|
||||
log.error(current_dir, ": ", err)
|
||||
return
|
||||
end
|
||||
local function on_fs_readdir(err, entries)
|
||||
if err then
|
||||
log.error(current_dir, ": ", err)
|
||||
return
|
||||
end
|
||||
if entries then
|
||||
for _, entry in ipairs(entries) do
|
||||
local success, item = pcall(
|
||||
file_items.create_item,
|
||||
ctx,
|
||||
utils.path_join(current_dir, entry.name),
|
||||
entry.type
|
||||
)
|
||||
if success then
|
||||
if ctx.recursive and item.type == "directory" then
|
||||
ctx.directories_to_scan = ctx.directories_to_scan + 1
|
||||
table.insert(ctx.paths_to_load, item.path)
|
||||
end
|
||||
else
|
||||
log.error("error creating item for ", path)
|
||||
end
|
||||
end
|
||||
|
||||
uv.fs_readdir(dir, on_fs_readdir)
|
||||
return
|
||||
end
|
||||
uv.fs_closedir(dir)
|
||||
on_directory_loaded(ctx, current_dir)
|
||||
ctx.directories_scanned = ctx.directories_scanned + 1
|
||||
if ctx.directories_scanned == #ctx.paths_to_load then
|
||||
ctx.on_exit()
|
||||
end
|
||||
|
||||
--local next_path = dir_complete(ctx, current_dir)
|
||||
--if next_path then
|
||||
-- local success, error = pcall(read_dir, next_path)
|
||||
-- if not success then
|
||||
-- log.error(next_path, ": ", error)
|
||||
-- end
|
||||
--else
|
||||
-- on_exit()
|
||||
--end
|
||||
end
|
||||
|
||||
uv.fs_readdir(dir, on_fs_readdir)
|
||||
end)
|
||||
end
|
||||
|
||||
--local first = table.remove(context.paths_to_load)
|
||||
--local success, err = pcall(read_dir, first)
|
||||
--if not success then
|
||||
-- log.error(first, ": ", err)
|
||||
--end
|
||||
for i = 1, context.directories_to_scan do
|
||||
read_dir(context.paths_to_load[i], context)
|
||||
end
|
||||
end
|
||||
|
||||
local function sync_scan(context, path_to_scan)
|
||||
log.trace("sync_scan: ", path_to_scan)
|
||||
local scan_mode = require("neo-tree").config.filesystem.scan_mode
|
||||
if scan_mode == "deep" then
|
||||
for _, path in ipairs(context.paths_to_load) do
|
||||
scan_dir_sync(context, path)
|
||||
-- scan_dir(context, path)
|
||||
end
|
||||
job_complete(context)
|
||||
else -- scan_mode == "shallow"
|
||||
local success, dir = pcall(vim.loop.fs_opendir, path_to_scan, nil, 1000)
|
||||
if not success then
|
||||
log.error("Error opening dir:", dir)
|
||||
end
|
||||
local success2, stats = pcall(vim.loop.fs_readdir, dir)
|
||||
if success2 and stats then
|
||||
for _, stat in ipairs(stats) do
|
||||
local path = utils.path_join(path_to_scan, stat.name)
|
||||
local success3, item = pcall(file_items.create_item, context, path, stat.type)
|
||||
if success3 then
|
||||
if context.recursive and stat.type == "directory" then
|
||||
table.insert(context.paths_to_load, path)
|
||||
end
|
||||
else
|
||||
log.error("error creating item for ", path)
|
||||
end
|
||||
end
|
||||
end
|
||||
vim.loop.fs_closedir(dir)
|
||||
|
||||
local next_path = dir_complete(context, path_to_scan)
|
||||
if next_path then
|
||||
sync_scan(context, next_path)
|
||||
else
|
||||
job_complete(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.get_items_sync = function(state, parent_id, path_to_reveal, callback)
|
||||
return M.get_items(state, parent_id, path_to_reveal, callback, false)
|
||||
end
|
||||
|
||||
M.get_items_async = function(state, parent_id, path_to_reveal, callback)
|
||||
M.get_items(state, parent_id, path_to_reveal, callback, true)
|
||||
end
|
||||
|
||||
M.get_items = function(state, parent_id, path_to_reveal, callback, async, recursive)
|
||||
if state.async_directory_scan == "always" then
|
||||
async = true
|
||||
elseif state.async_directory_scan == "never" then
|
||||
async = false
|
||||
elseif type(async) == "nil" then
|
||||
async = (state.async_directory_scan == "auto") or state.async_directory_scan
|
||||
end
|
||||
|
||||
if not parent_id then
|
||||
M.stop_watchers(state)
|
||||
end
|
||||
local context = file_items.create_context()
|
||||
context.state = state
|
||||
context.parent_id = parent_id
|
||||
context.path_to_reveal = path_to_reveal
|
||||
context.recursive = recursive
|
||||
context.callback = callback
|
||||
|
||||
-- Create root folder
|
||||
local root = file_items.create_item(context, parent_id or state.path, "directory")
|
||||
root.name = vim.fn.fnamemodify(root.path, ":~")
|
||||
root.loaded = true
|
||||
root.search_pattern = state.search_pattern
|
||||
context.root = root
|
||||
context.folders[root.path] = root
|
||||
state.default_expanded_nodes = state.force_open_folders or { state.path }
|
||||
|
||||
if state.search_pattern then
|
||||
local search_opts = {
|
||||
filtered_items = state.filtered_items,
|
||||
find_command = state.find_command,
|
||||
limit = state.search_limit or 50,
|
||||
path = root.path,
|
||||
term = state.search_pattern,
|
||||
find_args = state.find_args,
|
||||
find_by_full_path_words = state.find_by_full_path_words,
|
||||
fuzzy_finder_mode = state.fuzzy_finder_mode,
|
||||
on_insert = function(err, path)
|
||||
if err then
|
||||
log.debug(err)
|
||||
else
|
||||
file_items.create_item(context, path)
|
||||
end
|
||||
end,
|
||||
on_exit = vim.schedule_wrap(function()
|
||||
job_complete(context)
|
||||
end),
|
||||
}
|
||||
if state.use_fzy then
|
||||
filter_external.fzy_sort_files(search_opts, state)
|
||||
else
|
||||
-- Use the external command because the plenary search is slow
|
||||
filter_external.find_files(search_opts)
|
||||
end
|
||||
else
|
||||
-- In the case of a refresh or navigating up, we need to make sure that all
|
||||
-- open folders are loaded.
|
||||
local path = parent_id or state.path
|
||||
context.paths_to_load = {}
|
||||
if parent_id == nil then
|
||||
if utils.truthy(state.force_open_folders) then
|
||||
for _, f in ipairs(state.force_open_folders) do
|
||||
table.insert(context.paths_to_load, f)
|
||||
end
|
||||
elseif state.tree then
|
||||
context.paths_to_load = renderer.get_expanded_nodes(state.tree, state.path)
|
||||
end
|
||||
-- Ensure that there are no nested files in the list of folders to load
|
||||
context.paths_to_load = vim.tbl_filter(function(p)
|
||||
local stats = vim.loop.fs_stat(p)
|
||||
return stats and stats.type == "directory"
|
||||
end, context.paths_to_load)
|
||||
if path_to_reveal then
|
||||
-- be sure to load all of the folders leading up to the path to reveal
|
||||
local path_to_reveal_parts = utils.split(path_to_reveal, utils.path_separator)
|
||||
table.remove(path_to_reveal_parts) -- remove the file name
|
||||
-- add all parent folders to the list of paths to load
|
||||
utils.reduce(path_to_reveal_parts, "", function(acc, part)
|
||||
local current_path = utils.path_join(acc, part)
|
||||
if #current_path > #path then -- within current root
|
||||
table.insert(context.paths_to_load, current_path)
|
||||
table.insert(state.default_expanded_nodes, current_path)
|
||||
end
|
||||
return current_path
|
||||
end)
|
||||
context.paths_to_load = utils.unique(context.paths_to_load)
|
||||
end
|
||||
end
|
||||
|
||||
local filtered_items = state.filtered_items or {}
|
||||
context.is_a_never_show_file = function(fname)
|
||||
if fname then
|
||||
local _, name = utils.split_path(fname)
|
||||
if name then
|
||||
if filtered_items.never_show and filtered_items.never_show[name] then
|
||||
return true
|
||||
end
|
||||
if utils.is_filtered_by_pattern(filtered_items.never_show_by_pattern, fname, name) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
table.insert(context.paths_to_load, path)
|
||||
if async then
|
||||
async_scan(context, path)
|
||||
else
|
||||
sync_scan(context, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.stop_watchers = function(state)
|
||||
if state.use_libuv_file_watcher and state.tree then
|
||||
-- We are loaded a new root or refreshing, unwatch any folders that were
|
||||
-- previously being watched.
|
||||
local loaded_folders = renderer.select_nodes(state.tree, function(node)
|
||||
return node.type == "directory" and node.loaded
|
||||
end)
|
||||
fs_watch.unwatch_git_index(state.path, require("neo-tree").config.git_status_async)
|
||||
for _, folder in ipairs(loaded_folders) do
|
||||
log.trace("Unwatching folder ", folder.path)
|
||||
if folder.is_link then
|
||||
fs_watch.unwatch_folder(folder.link_to)
|
||||
else
|
||||
fs_watch.unwatch_folder(folder:get_id())
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug(
|
||||
"Not unwatching folders... use_libuv_file_watcher is ",
|
||||
state.use_libuv_file_watcher,
|
||||
" and state.tree is ",
|
||||
utils.truthy(state.tree)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
177
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/fs_watch.lua
vendored
Normal file
177
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/fs_watch.lua
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
local vim = vim
|
||||
local events = require("neo-tree.events")
|
||||
local log = require("neo-tree.log")
|
||||
local git = require("neo-tree.git")
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {}
|
||||
|
||||
local flags = {
|
||||
watch_entry = false,
|
||||
stat = false,
|
||||
recursive = false,
|
||||
}
|
||||
|
||||
local watched = {}
|
||||
|
||||
local get_dot_git_folder = function(path, callback)
|
||||
if type(callback) == "function" then
|
||||
git.get_repository_root(path, function(git_root)
|
||||
if git_root then
|
||||
local git_folder = utils.path_join(git_root, ".git")
|
||||
local stat = vim.loop.fs_stat(git_folder)
|
||||
if stat and stat.type == "directory" then
|
||||
callback(git_folder, git_root)
|
||||
end
|
||||
else
|
||||
callback(nil, nil)
|
||||
end
|
||||
end)
|
||||
else
|
||||
local git_root = git.get_repository_root(path)
|
||||
if git_root then
|
||||
local git_folder = utils.path_join(git_root, ".git")
|
||||
local stat = vim.loop.fs_stat(git_folder)
|
||||
if stat and stat.type == "directory" then
|
||||
return git_folder, git_root
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
M.show_watched = function()
|
||||
local items = {}
|
||||
for _, handle in pairs(watched) do
|
||||
items[handle.path] = handle.references
|
||||
end
|
||||
log.info("Watched Folders: ", vim.inspect(items))
|
||||
end
|
||||
|
||||
---Watch a directory for changes to it's children. Not recursive.
|
||||
---@param path string The directory to watch.
|
||||
---@param custom_callback? function The callback to call when a change is detected.
|
||||
---@param allow_git_watch? boolean Allow watching of git folders.
|
||||
M.watch_folder = function(path, custom_callback, allow_git_watch)
|
||||
if not allow_git_watch then
|
||||
if path:find("/%.git$") or path:find("/%.git/") then
|
||||
-- git folders seem to throw off fs events constantly.
|
||||
log.debug("watch_folder(path): Skipping git folder: ", path)
|
||||
return
|
||||
end
|
||||
end
|
||||
local h = watched[path]
|
||||
if h == nil then
|
||||
log.trace("Starting new fs watch on: ", path)
|
||||
local callback = custom_callback
|
||||
or vim.schedule_wrap(function(err, fname)
|
||||
if fname and fname:match("^%.null[-]ls_.+") then
|
||||
-- null-ls temp file: https://github.com/jose-elias-alvarez/null-ls.nvim/pull/1075
|
||||
return
|
||||
end
|
||||
if err then
|
||||
log.error("file_event_callback: ", err)
|
||||
return
|
||||
end
|
||||
events.fire_event(events.FS_EVENT, { afile = path })
|
||||
end)
|
||||
h = {
|
||||
handle = vim.loop.new_fs_event(),
|
||||
path = path,
|
||||
references = 0,
|
||||
active = false,
|
||||
callback = callback,
|
||||
}
|
||||
watched[path] = h
|
||||
--w:start(path, flags, callback)
|
||||
else
|
||||
log.trace("Incrementing references for fs watch on: ", path)
|
||||
end
|
||||
h.references = h.references + 1
|
||||
end
|
||||
|
||||
M.watch_git_index = function(path, async)
|
||||
local function watch_git_folder(git_folder, git_root)
|
||||
if git_folder then
|
||||
local git_event_callback = vim.schedule_wrap(function(err, fname)
|
||||
if fname and fname:match("^.+%.lock$") then
|
||||
return
|
||||
end
|
||||
if fname and fname:match("^%._null-ls_.+") then
|
||||
-- null-ls temp file: https://github.com/jose-elias-alvarez/null-ls.nvim/pull/1075
|
||||
return
|
||||
end
|
||||
if err then
|
||||
log.error("git_event_callback: ", err)
|
||||
return
|
||||
end
|
||||
events.fire_event(events.GIT_EVENT, { path = fname, repository = git_root })
|
||||
end)
|
||||
|
||||
M.watch_folder(git_folder, git_event_callback, true)
|
||||
end
|
||||
end
|
||||
|
||||
if async then
|
||||
get_dot_git_folder(path, watch_git_folder)
|
||||
else
|
||||
watch_git_folder(get_dot_git_folder(path))
|
||||
end
|
||||
end
|
||||
|
||||
M.updated_watched = function()
|
||||
for path, w in pairs(watched) do
|
||||
if w.references > 0 then
|
||||
if not w.active then
|
||||
log.trace("References added for fs watch on: ", path, ", starting.")
|
||||
w.handle:start(path, flags, w.callback)
|
||||
w.active = true
|
||||
end
|
||||
else
|
||||
if w.active then
|
||||
log.trace("No more references for fs watch on: ", path, ", stopping.")
|
||||
w.handle:stop()
|
||||
w.active = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Stop watching a directory. If there are no more references to the handle,
|
||||
---it will be destroyed. Otherwise, the reference count will be decremented.
|
||||
---@param path string The directory to stop watching.
|
||||
M.unwatch_folder = function(path, callback_id)
|
||||
local h = watched[path]
|
||||
if h then
|
||||
log.trace("Decrementing references for fs watch on: ", path, callback_id)
|
||||
h.references = h.references - 1
|
||||
else
|
||||
log.trace("(unwatch_folder) No fs watch found for: ", path)
|
||||
end
|
||||
end
|
||||
|
||||
M.unwatch_git_index = function(path, async)
|
||||
local function unwatch_git_folder(git_folder, _)
|
||||
if git_folder then
|
||||
M.unwatch_folder(git_folder)
|
||||
end
|
||||
end
|
||||
|
||||
if async then
|
||||
get_dot_git_folder(path, unwatch_git_folder)
|
||||
else
|
||||
unwatch_git_folder(get_dot_git_folder(path))
|
||||
end
|
||||
end
|
||||
|
||||
---Stop watching all directories. This is the nuclear option and it affects all
|
||||
---sources.
|
||||
M.unwatch_all = function()
|
||||
for _, h in pairs(watched) do
|
||||
h.handle:stop()
|
||||
h.handle = nil
|
||||
end
|
||||
watched = {}
|
||||
end
|
||||
|
||||
return M
|
157
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/globtopattern.lua
vendored
Normal file
157
bundle/neo-tree.nvim/lua/neo-tree/sources/filesystem/lib/globtopattern.lua
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
--(c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT).
|
||||
|
||||
--Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
--of this software and associated documentation files (the "Software"), to deal
|
||||
--in the Software without restriction, including without limitation the rights
|
||||
--to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
--copies of the Software, and to permit persons to whom the Software is
|
||||
--furnished to do so, subject to the following conditions:
|
||||
|
||||
--The above copyright notice and this permission notice shall be included in
|
||||
--all copies or substantial portions of the Software.
|
||||
|
||||
--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
--IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
--FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
--AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
--LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
--OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
--THE SOFTWARE.
|
||||
--(end license)
|
||||
|
||||
local M = { _TYPE = "module", _NAME = "globtopattern", _VERSION = "0.2.1.20120406" }
|
||||
|
||||
function M.globtopattern(g)
|
||||
-- Some useful references:
|
||||
-- - apr_fnmatch in Apache APR. For example,
|
||||
-- http://apr.apache.org/docs/apr/1.3/group__apr__fnmatch.html
|
||||
-- which cites POSIX 1003.2-1992, section B.6.
|
||||
|
||||
local p = "^" -- pattern being built
|
||||
local i = 0 -- index in g
|
||||
local c -- char at index i in g.
|
||||
|
||||
-- unescape glob char
|
||||
local function unescape()
|
||||
if c == "\\" then
|
||||
i = i + 1
|
||||
c = g:sub(i, i)
|
||||
if c == "" then
|
||||
p = "[^]"
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- escape pattern char
|
||||
local function escape(c)
|
||||
return c:match("^%w$") and c or "%" .. c
|
||||
end
|
||||
|
||||
-- Convert tokens at end of charset.
|
||||
local function charset_end()
|
||||
while 1 do
|
||||
if c == "" then
|
||||
p = "[^]"
|
||||
return false
|
||||
elseif c == "]" then
|
||||
p = p .. "]"
|
||||
break
|
||||
else
|
||||
if not unescape() then
|
||||
break
|
||||
end
|
||||
local c1 = c
|
||||
i = i + 1
|
||||
c = g:sub(i, i)
|
||||
if c == "" then
|
||||
p = "[^]"
|
||||
return false
|
||||
elseif c == "-" then
|
||||
i = i + 1
|
||||
c = g:sub(i, i)
|
||||
if c == "" then
|
||||
p = "[^]"
|
||||
return false
|
||||
elseif c == "]" then
|
||||
p = p .. escape(c1) .. "%-]"
|
||||
break
|
||||
else
|
||||
if not unescape() then
|
||||
break
|
||||
end
|
||||
p = p .. escape(c1) .. "-" .. escape(c)
|
||||
end
|
||||
elseif c == "]" then
|
||||
p = p .. escape(c1) .. "]"
|
||||
break
|
||||
else
|
||||
p = p .. escape(c1)
|
||||
i = i - 1 -- put back
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
c = g:sub(i, i)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Convert tokens in charset.
|
||||
local function charset()
|
||||
i = i + 1
|
||||
c = g:sub(i, i)
|
||||
if c == "" or c == "]" then
|
||||
p = "[^]"
|
||||
return false
|
||||
elseif c == "^" or c == "!" then
|
||||
i = i + 1
|
||||
c = g:sub(i, i)
|
||||
if c == "]" then
|
||||
-- ignored
|
||||
else
|
||||
p = p .. "[^"
|
||||
if not charset_end() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
else
|
||||
p = p .. "["
|
||||
if not charset_end() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Convert tokens.
|
||||
while 1 do
|
||||
i = i + 1
|
||||
c = g:sub(i, i)
|
||||
if c == "" then
|
||||
p = p .. "$"
|
||||
break
|
||||
elseif c == "?" then
|
||||
p = p .. "."
|
||||
elseif c == "*" then
|
||||
p = p .. ".*"
|
||||
elseif c == "[" then
|
||||
if not charset() then
|
||||
break
|
||||
end
|
||||
elseif c == "\\" then
|
||||
i = i + 1
|
||||
c = g:sub(i, i)
|
||||
if c == "" then
|
||||
p = p .. "\\$"
|
||||
break
|
||||
end
|
||||
p = p .. escape(c)
|
||||
else
|
||||
p = p .. escape(c)
|
||||
end
|
||||
end
|
||||
return p
|
||||
end
|
||||
|
||||
return M
|
71
bundle/neo-tree.nvim/lua/neo-tree/sources/git_status/commands.lua
vendored
Normal file
71
bundle/neo-tree.nvim/lua/neo-tree/sources/git_status/commands.lua
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
--This file should contain all commands meant to be used by mappings.
|
||||
|
||||
local vim = vim
|
||||
local cc = require("neo-tree.sources.common.commands")
|
||||
local utils = require("neo-tree.utils")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
|
||||
local M = {}
|
||||
|
||||
local refresh = utils.wrap(manager.refresh, "git_status")
|
||||
local redraw = utils.wrap(manager.redraw, "git_status")
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Common commands
|
||||
-- ----------------------------------------------------------------------------
|
||||
M.add = function(state)
|
||||
cc.add(state, refresh)
|
||||
end
|
||||
|
||||
M.add_directory = function(state)
|
||||
cc.add_directory(state, refresh)
|
||||
end
|
||||
|
||||
---Marks node as copied, so that it can be pasted somewhere else.
|
||||
M.copy_to_clipboard = function(state)
|
||||
cc.copy_to_clipboard(state, redraw)
|
||||
end
|
||||
|
||||
M.copy_to_clipboard_visual = function(state, selected_nodes)
|
||||
cc.copy_to_clipboard_visual(state, selected_nodes, redraw)
|
||||
end
|
||||
|
||||
---Marks node as cut, so that it can be pasted (moved) somewhere else.
|
||||
M.cut_to_clipboard = function(state)
|
||||
cc.cut_to_clipboard(state, redraw)
|
||||
end
|
||||
|
||||
M.cut_to_clipboard_visual = function(state, selected_nodes)
|
||||
cc.cut_to_clipboard_visual(state, selected_nodes, redraw)
|
||||
end
|
||||
|
||||
M.copy = function(state)
|
||||
cc.copy(state, redraw)
|
||||
end
|
||||
|
||||
M.move = function(state)
|
||||
cc.move(state, redraw)
|
||||
end
|
||||
|
||||
---Pastes all items from the clipboard to the current directory.
|
||||
M.paste_from_clipboard = function(state)
|
||||
cc.paste_from_clipboard(state, refresh)
|
||||
end
|
||||
|
||||
M.delete = function(state)
|
||||
cc.delete(state, refresh)
|
||||
end
|
||||
|
||||
M.delete_visual = function(state, selected_nodes)
|
||||
cc.delete_visual(state, selected_nodes, refresh)
|
||||
end
|
||||
|
||||
M.refresh = refresh
|
||||
|
||||
M.rename = function(state)
|
||||
cc.rename(state, refresh)
|
||||
end
|
||||
|
||||
cc._add_common_commands(M)
|
||||
|
||||
return M
|
44
bundle/neo-tree.nvim/lua/neo-tree/sources/git_status/components.lua
vendored
Normal file
44
bundle/neo-tree.nvim/lua/neo-tree/sources/git_status/components.lua
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
-- This file contains the built-in components. Each componment is a function
|
||||
-- that takes the following arguments:
|
||||
-- config: A table containing the configuration provided by the user
|
||||
-- when declaring this component in their renderer config.
|
||||
-- node: A NuiNode object for the currently focused node.
|
||||
-- state: The current state of the source providing the items.
|
||||
--
|
||||
-- The function should return either a table, or a list of tables, each of which
|
||||
-- contains the following keys:
|
||||
-- text: The text to display for this item.
|
||||
-- highlight: The highlight group to apply to this text.
|
||||
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local common = require("neo-tree.sources.common.components")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.name = function(config, node, state)
|
||||
local highlight = config.highlight or highlights.FILE_NAME_OPENED
|
||||
local name = node.name
|
||||
if node.type == "directory" then
|
||||
if node:get_depth() == 1 then
|
||||
highlight = highlights.ROOT_NAME
|
||||
if node:has_children() then
|
||||
name = "GIT STATUS for " .. name
|
||||
else
|
||||
name = "GIT STATUS (working tree clean) for " .. name
|
||||
end
|
||||
else
|
||||
highlight = highlights.DIRECTORY_NAME
|
||||
end
|
||||
elseif config.use_git_status_colors then
|
||||
local git_status = state.components.git_status({}, node, state)
|
||||
if git_status and git_status.highlight then
|
||||
highlight = git_status.highlight
|
||||
end
|
||||
end
|
||||
return {
|
||||
text = name,
|
||||
highlight = highlight,
|
||||
}
|
||||
end
|
||||
|
||||
return vim.tbl_deep_extend("force", common, M)
|
94
bundle/neo-tree.nvim/lua/neo-tree/sources/git_status/init.lua
vendored
Normal file
94
bundle/neo-tree.nvim/lua/neo-tree/sources/git_status/init.lua
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
--This file should have all functions that are in the public api and either set
|
||||
--or read the state of this source.
|
||||
|
||||
local vim = vim
|
||||
local utils = require("neo-tree.utils")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local items = require("neo-tree.sources.git_status.lib.items")
|
||||
local events = require("neo-tree.events")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
|
||||
local M = {
|
||||
name = "git_status",
|
||||
display_name = " Git "
|
||||
}
|
||||
|
||||
local wrap = function(func)
|
||||
return utils.wrap(func, M.name)
|
||||
end
|
||||
|
||||
local get_state = function()
|
||||
return manager.get_state(M.name)
|
||||
end
|
||||
|
||||
---Navigate to the given path.
|
||||
---@param path string Path to navigate to. If empty, will navigate to the cwd.
|
||||
M.navigate = function(state, path, path_to_reveal)
|
||||
state.dirty = false
|
||||
if path_to_reveal then
|
||||
renderer.position.set(state, path_to_reveal)
|
||||
end
|
||||
items.get_git_status(state)
|
||||
end
|
||||
|
||||
M.refresh = function()
|
||||
manager.refresh(M.name)
|
||||
end
|
||||
|
||||
---Configures the plugin, should be called before the plugin is used.
|
||||
---@param config table Configuration table containing any keys that the user
|
||||
--wants to change from the defaults. May be empty to accept default values.
|
||||
M.setup = function(config, global_config)
|
||||
if config.before_render then
|
||||
--convert to new event system
|
||||
manager.subscribe(M.name, {
|
||||
event = events.BEFORE_RENDER,
|
||||
handler = function(state)
|
||||
local this_state = get_state()
|
||||
if state == this_state then
|
||||
config.before_render(this_state)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
if global_config.enable_refresh_on_write then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_BUFFER_CHANGED,
|
||||
handler = function(args)
|
||||
if utils.is_real_file(args.afile) then
|
||||
M.refresh()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
if config.bind_to_cwd then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_DIR_CHANGED,
|
||||
handler = M.refresh,
|
||||
})
|
||||
end
|
||||
|
||||
if global_config.enable_diagnostics then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_DIAGNOSTIC_CHANGED,
|
||||
handler = wrap(manager.diagnostics_changed),
|
||||
})
|
||||
end
|
||||
|
||||
--Configure event handlers for modified files
|
||||
if global_config.enable_modified_markers then
|
||||
manager.subscribe(M.name, {
|
||||
event = events.VIM_BUFFER_MODIFIED_SET,
|
||||
handler = wrap(manager.opened_buffers_changed),
|
||||
})
|
||||
end
|
||||
|
||||
manager.subscribe(M.name, {
|
||||
event = events.GIT_EVENT,
|
||||
handler = M.refresh,
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
49
bundle/neo-tree.nvim/lua/neo-tree/sources/git_status/lib/items.lua
vendored
Normal file
49
bundle/neo-tree.nvim/lua/neo-tree/sources/git_status/lib/items.lua
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
local vim = vim
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local file_items = require("neo-tree.sources.common.file-items")
|
||||
local log = require("neo-tree.log")
|
||||
local git = require("neo-tree.git")
|
||||
|
||||
local M = {}
|
||||
|
||||
---Get a table of all open buffers, along with all parent paths of those buffers.
|
||||
---The paths are the keys of the table, and all the values are 'true'.
|
||||
M.get_git_status = function(state)
|
||||
if state.loading then
|
||||
return
|
||||
end
|
||||
state.loading = true
|
||||
local status_lookup, project_root = git.status(state.git_base, true)
|
||||
state.path = project_root or state.path or vim.fn.getcwd()
|
||||
local context = file_items.create_context()
|
||||
context.state = state
|
||||
-- Create root folder
|
||||
local root = file_items.create_item(context, state.path, "directory")
|
||||
root.name = vim.fn.fnamemodify(root.path, ":~")
|
||||
root.loaded = true
|
||||
root.search_pattern = state.search_pattern
|
||||
context.folders[root.path] = root
|
||||
|
||||
for path, status in pairs(status_lookup) do
|
||||
local success, item = pcall(file_items.create_item, context, path, "file")
|
||||
item.status = status
|
||||
if success then
|
||||
item.extra = {
|
||||
git_status = status,
|
||||
}
|
||||
else
|
||||
log.error("Error creating item for " .. path .. ": " .. item)
|
||||
end
|
||||
end
|
||||
|
||||
state.git_status_lookup = status_lookup
|
||||
state.default_expanded_nodes = {}
|
||||
for id, _ in pairs(context.folders) do
|
||||
table.insert(state.default_expanded_nodes, id)
|
||||
end
|
||||
file_items.deep_sort(root.children, state.sort_function_override)
|
||||
renderer.show_nodes({ root }, state)
|
||||
state.loading = false
|
||||
end
|
||||
|
||||
return M
|
644
bundle/neo-tree.nvim/lua/neo-tree/sources/manager.lua
vendored
Normal file
644
bundle/neo-tree.nvim/lua/neo-tree/sources/manager.lua
vendored
Normal file
@ -0,0 +1,644 @@
|
||||
--This file should have all functions that are in the public api and either set
|
||||
--or read the state of this source.
|
||||
|
||||
local vim = vim
|
||||
local utils = require("neo-tree.utils")
|
||||
local fs_scan = require("neo-tree.sources.filesystem.lib.fs_scan")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local inputs = require("neo-tree.ui.inputs")
|
||||
local events = require("neo-tree.events")
|
||||
local log = require("neo-tree.log")
|
||||
local fs_watch = require("neo-tree.sources.filesystem.lib.fs_watch")
|
||||
|
||||
local M = {}
|
||||
local source_data = {}
|
||||
local all_states = {}
|
||||
local default_configs = {}
|
||||
|
||||
local get_source_data = function(source_name)
|
||||
if source_name == nil then
|
||||
error("get_source_data: source_name cannot be nil")
|
||||
end
|
||||
local sd = source_data[source_name]
|
||||
if sd then
|
||||
return sd
|
||||
end
|
||||
sd = {
|
||||
name = source_name,
|
||||
state_by_tab = {},
|
||||
state_by_win = {},
|
||||
subscriptions = {},
|
||||
}
|
||||
source_data[source_name] = sd
|
||||
return sd
|
||||
end
|
||||
|
||||
local function create_state(tabid, sd, winid)
|
||||
local default_config = default_configs[sd.name]
|
||||
local state = vim.deepcopy(default_config, { noref = 1 })
|
||||
state.tabid = tabid
|
||||
state.id = winid or tabid
|
||||
state.dirty = true
|
||||
state.position = {
|
||||
is = { restorable = false },
|
||||
}
|
||||
state.git_base = "HEAD"
|
||||
table.insert(all_states, state)
|
||||
return state
|
||||
end
|
||||
|
||||
M._get_all_states = function()
|
||||
return all_states
|
||||
end
|
||||
|
||||
M._for_each_state = function(source_name, action)
|
||||
for _, state in ipairs(all_states) do
|
||||
if source_name == nil or state.name == source_name then
|
||||
action(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---For use in tests only, completely resets the state of all sources.
|
||||
---This closes all windows as well since they would be broken by this action.
|
||||
M._clear_state = function()
|
||||
fs_watch.unwatch_all()
|
||||
renderer.close_all_floating_windows()
|
||||
for _, data in pairs(source_data) do
|
||||
for _, state in pairs(data.state_by_tab) do
|
||||
renderer.close(state)
|
||||
end
|
||||
for _, state in pairs(data.state_by_win) do
|
||||
renderer.close(state)
|
||||
end
|
||||
end
|
||||
source_data = {}
|
||||
end
|
||||
|
||||
M.set_default_config = function(source_name, config)
|
||||
if source_name == nil then
|
||||
error("set_default_config: source_name cannot be nil")
|
||||
end
|
||||
default_configs[source_name] = config
|
||||
local sd = get_source_data(source_name)
|
||||
for tabid, tab_config in pairs(sd.state_by_tab) do
|
||||
sd.state_by_tab[tabid] = vim.tbl_deep_extend("force", tab_config, config)
|
||||
end
|
||||
end
|
||||
|
||||
--TODO: we need to track state per window when working with netwrw style "current"
|
||||
--position. How do we know which one to return when this is called?
|
||||
M.get_state = function(source_name, tabid, winid)
|
||||
if source_name == nil then
|
||||
error("get_state: source_name cannot be nil")
|
||||
end
|
||||
tabid = tabid or vim.api.nvim_get_current_tabpage()
|
||||
local sd = get_source_data(source_name)
|
||||
if type(winid) == "number" then
|
||||
local win_state = sd.state_by_win[winid]
|
||||
if not win_state then
|
||||
win_state = create_state(tabid, sd, winid)
|
||||
sd.state_by_win[winid] = win_state
|
||||
end
|
||||
return win_state
|
||||
else
|
||||
local tab_state = sd.state_by_tab[tabid]
|
||||
if tab_state and tab_state.winid then
|
||||
-- just in case tab and window get tangled up, tab state replaces window
|
||||
sd.state_by_win[tab_state.winid] = nil
|
||||
end
|
||||
if not tab_state then
|
||||
tab_state = create_state(tabid, sd)
|
||||
sd.state_by_tab[tabid] = tab_state
|
||||
end
|
||||
return tab_state
|
||||
end
|
||||
end
|
||||
|
||||
---Returns the state for the current buffer, assuming it is a neo-tree buffer.
|
||||
---@param winid number|nil The window id to use, if nil, the current window is used.
|
||||
---@return table|nil The state for the current buffer, or nil if it is not a
|
||||
---neo-tree buffer.
|
||||
M.get_state_for_window = function(winid)
|
||||
local winid = winid or vim.api.nvim_get_current_win()
|
||||
local bufnr = vim.api.nvim_win_get_buf(winid)
|
||||
local source_status, source_name = pcall(vim.api.nvim_buf_get_var, bufnr, "neo_tree_source")
|
||||
local position_status, position = pcall(vim.api.nvim_buf_get_var, bufnr, "neo_tree_position")
|
||||
if not source_status or not position_status then
|
||||
return nil
|
||||
end
|
||||
|
||||
local tabid = vim.api.nvim_get_current_tabpage()
|
||||
if position == "current" then
|
||||
return M.get_state(source_name, tabid, winid)
|
||||
else
|
||||
return M.get_state(source_name, tabid, nil)
|
||||
end
|
||||
end
|
||||
|
||||
M.get_path_to_reveal = function(include_terminals)
|
||||
local win_id = vim.api.nvim_get_current_win()
|
||||
local cfg = vim.api.nvim_win_get_config(win_id)
|
||||
if cfg.relative > "" or cfg.external then
|
||||
-- floating window, ignore
|
||||
return nil
|
||||
end
|
||||
if vim.bo.filetype == "neo-tree" then
|
||||
return nil
|
||||
end
|
||||
local path = vim.fn.expand("%:p")
|
||||
if not utils.truthy(path) then
|
||||
return nil
|
||||
end
|
||||
if not include_terminals and path:match("term://") then
|
||||
return nil
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
M.subscribe = function(source_name, event)
|
||||
if source_name == nil then
|
||||
error("subscribe: source_name cannot be nil")
|
||||
end
|
||||
local sd = get_source_data(source_name)
|
||||
if not sd.subscriptions then
|
||||
sd.subscriptions = {}
|
||||
end
|
||||
if not utils.truthy(event.id) then
|
||||
event.id = sd.name .. "." .. event.event
|
||||
end
|
||||
log.trace("subscribing to event: " .. event.id)
|
||||
sd.subscriptions[event] = true
|
||||
events.subscribe(event)
|
||||
end
|
||||
|
||||
M.unsubscribe = function(source_name, event)
|
||||
if source_name == nil then
|
||||
error("unsubscribe: source_name cannot be nil")
|
||||
end
|
||||
local sd = get_source_data(source_name)
|
||||
log.trace("unsubscribing to event: " .. event.id or event.event)
|
||||
if sd.subscriptions then
|
||||
for sub, _ in pairs(sd.subscriptions) do
|
||||
if sub.event == event.event and sub.id == event.id then
|
||||
sd.subscriptions[sub] = false
|
||||
events.unsubscribe(sub)
|
||||
end
|
||||
end
|
||||
end
|
||||
events.unsubscribe(event)
|
||||
end
|
||||
|
||||
M.unsubscribe_all = function(source_name)
|
||||
if source_name == nil then
|
||||
error("unsubscribe_all: source_name cannot be nil")
|
||||
end
|
||||
local sd = get_source_data(source_name)
|
||||
if sd.subscriptions then
|
||||
for event, subscribed in pairs(sd.subscriptions) do
|
||||
if subscribed then
|
||||
events.unsubscribe(event)
|
||||
end
|
||||
end
|
||||
end
|
||||
sd.subscriptions = {}
|
||||
end
|
||||
|
||||
M.close = function(source_name, at_position)
|
||||
local state = M.get_state(source_name)
|
||||
if at_position then
|
||||
if state.current_position == at_position then
|
||||
return renderer.close(state)
|
||||
else
|
||||
return false
|
||||
end
|
||||
else
|
||||
return renderer.close(state)
|
||||
end
|
||||
end
|
||||
|
||||
M.close_all = function(at_position)
|
||||
local tabid = vim.api.nvim_get_current_tabpage()
|
||||
for source_name, _ in pairs(source_data) do
|
||||
M._for_each_state(source_name, function(state)
|
||||
if state.tabid == tabid then
|
||||
if at_position then
|
||||
if state.current_position == at_position then
|
||||
log.trace("Closing " .. source_name .. " at position " .. at_position)
|
||||
pcall(renderer.close, state)
|
||||
end
|
||||
else
|
||||
log.trace("Closing " .. source_name)
|
||||
pcall(renderer.close, state)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
M.close_all_except = function(except_source_name)
|
||||
local tabid = vim.api.nvim_get_current_tabpage()
|
||||
for source_name, _ in pairs(source_data) do
|
||||
M._for_each_state(source_name, function(state)
|
||||
if state.tabid == tabid and source_name ~= except_source_name then
|
||||
log.trace("Closing " .. source_name)
|
||||
pcall(renderer.close, state)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---Redraws the tree with updated diagnostics without scanning the filesystem again.
|
||||
M.diagnostics_changed = function(source_name, args)
|
||||
if not type(args) == "table" then
|
||||
error("diagnostics_changed: args must be a table")
|
||||
end
|
||||
M._for_each_state(source_name, function(state)
|
||||
state.diagnostics_lookup = args.diagnostics_lookup
|
||||
renderer.redraw(state)
|
||||
end)
|
||||
end
|
||||
|
||||
---Called by autocmds when the cwd dir is changed. This will change the root.
|
||||
M.dir_changed = function(source_name)
|
||||
M._for_each_state(source_name, function(state)
|
||||
local cwd = M.get_cwd(state)
|
||||
if state.path and cwd == state.path then
|
||||
return
|
||||
end
|
||||
if renderer.window_exists(state) then
|
||||
M.navigate(state, cwd)
|
||||
else
|
||||
state.path = nil
|
||||
state.dirty = true
|
||||
end
|
||||
end)
|
||||
end
|
||||
--
|
||||
---Redraws the tree with updated git_status without scanning the filesystem again.
|
||||
M.git_status_changed = function(source_name, args)
|
||||
if not type(args) == "table" then
|
||||
error("git_status_changed: args must be a table")
|
||||
end
|
||||
M._for_each_state(source_name, function(state)
|
||||
if utils.is_subpath(args.git_root, state.path) then
|
||||
state.git_status_lookup = args.git_status
|
||||
renderer.redraw(state)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Vimscript functions like vim.fn.getcwd take tabpage number (tab position counting from left)
|
||||
-- but API functions operate on tabpage id (as returned by nvim_tabpage_get_number). These values
|
||||
-- get out of sync when tabs are being moved and we want to track state according to tabpage id.
|
||||
local to_tabnr = function(tabid)
|
||||
return tabid > 0 and vim.api.nvim_tabpage_get_number(tabid) or tabid
|
||||
end
|
||||
|
||||
local get_params_for_cwd = function(state)
|
||||
local tabid = state.tabid
|
||||
-- the id is either the tabid for sidebars or the winid for splits
|
||||
local winid = state.id == tabid and -1 or state.id
|
||||
|
||||
if state.cwd_target then
|
||||
local target = state.cwd_target.sidebar
|
||||
if state.current_position == "current" then
|
||||
target = state.cwd_target.current
|
||||
end
|
||||
if target == "window" then
|
||||
return winid, to_tabnr(tabid)
|
||||
elseif target == "global" then
|
||||
return -1, -1
|
||||
elseif target == "none" then
|
||||
return nil, nil
|
||||
else -- default to tab
|
||||
return -1, to_tabnr(tabid)
|
||||
end
|
||||
else
|
||||
return winid, to_tabnr(tabid)
|
||||
end
|
||||
end
|
||||
|
||||
M.get_cwd = function(state)
|
||||
local winid, tabnr = get_params_for_cwd(state)
|
||||
local success, cwd = false, ""
|
||||
if winid or tabnr then
|
||||
success, cwd = pcall(vim.fn.getcwd, winid, tabnr)
|
||||
end
|
||||
if success then
|
||||
return cwd
|
||||
else
|
||||
success, cwd = pcall(vim.fn.getcwd)
|
||||
if success then
|
||||
return cwd
|
||||
else
|
||||
return state.path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.set_cwd = function(state)
|
||||
if not state.path then
|
||||
return
|
||||
end
|
||||
|
||||
local winid, tabnr = get_params_for_cwd(state)
|
||||
|
||||
if winid == nil and tabnr == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local _, cwd = pcall(vim.fn.getcwd, winid, tabnr)
|
||||
if state.path ~= cwd then
|
||||
if winid > 0 then
|
||||
vim.cmd("lcd " .. state.path)
|
||||
elseif tabnr > 0 then
|
||||
vim.cmd("tcd " .. state.path)
|
||||
else
|
||||
vim.cmd("cd " .. state.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local dispose_state = function(state)
|
||||
pcall(fs_scan.stop_watchers, state)
|
||||
pcall(renderer.close, state)
|
||||
source_data[state.name].state_by_tab[state.id] = nil
|
||||
source_data[state.name].state_by_win[state.id] = nil
|
||||
state.disposed = true
|
||||
end
|
||||
|
||||
M.dispose = function(source_name, tabid)
|
||||
for i, state in ipairs(all_states) do
|
||||
if source_name == nil or state.name == source_name then
|
||||
if not tabid or tabid == state.tabid then
|
||||
log.trace(state.name, " disposing of tab: ", tabid)
|
||||
dispose_state(state)
|
||||
table.remove(all_states, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.dispose_tab = function(tabid)
|
||||
if not tabid then
|
||||
error("dispose_tab: tabid cannot be nil")
|
||||
end
|
||||
for i, state in ipairs(all_states) do
|
||||
if tabid == state.tabid then
|
||||
log.trace(state.name, " disposing of tab: ", tabid, state.name)
|
||||
dispose_state(state)
|
||||
table.remove(all_states, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.dispose_invalid_tabs = function()
|
||||
-- Iterate in reverse because we are removing items during loop
|
||||
for i = #all_states, 1, -1 do
|
||||
local state = all_states[i]
|
||||
-- if not valid_tabs[state.tabid] then
|
||||
if not vim.api.nvim_tabpage_is_valid(state.tabid) then
|
||||
log.trace(state.name, " disposing of tab: ", state.tabid, state.name)
|
||||
dispose_state(state)
|
||||
table.remove(all_states, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.dispose_window = function(winid)
|
||||
if not winid then
|
||||
error("dispose_window: winid cannot be nil")
|
||||
end
|
||||
for i, state in ipairs(all_states) do
|
||||
if state.id == winid then
|
||||
log.trace(state.name, " disposing of window: ", winid, state.name)
|
||||
dispose_state(state)
|
||||
table.remove(all_states, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.float = function(source_name)
|
||||
local state = M.get_state(source_name)
|
||||
state.current_position = "float"
|
||||
local path_to_reveal = M.get_path_to_reveal()
|
||||
M.navigate(source_name, state.path, path_to_reveal)
|
||||
end
|
||||
|
||||
---Focus the window, opening it if it is not already open.
|
||||
---@param source_name string Source name.
|
||||
---@param path_to_reveal string|nil Node to focus after the items are loaded.
|
||||
---@param callback function|nil Callback to call after the items are loaded.
|
||||
M.focus = function(source_name, path_to_reveal, callback)
|
||||
local state = M.get_state(source_name)
|
||||
state.current_position = nil
|
||||
if path_to_reveal then
|
||||
M.navigate(source_name, state.path, path_to_reveal, callback)
|
||||
else
|
||||
if not state.dirty and renderer.window_exists(state) then
|
||||
vim.api.nvim_set_current_win(state.winid)
|
||||
else
|
||||
M.navigate(source_name, state.path, nil, callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Redraws the tree with updated modified markers without scanning the filesystem again.
|
||||
M.opened_buffers_changed = function(source_name, args)
|
||||
if not type(args) == "table" then
|
||||
error("opened_buffers_changed: args must be a table")
|
||||
end
|
||||
if type(args.opened_buffers) == "table" then
|
||||
M._for_each_state(source_name, function(state)
|
||||
if utils.tbl_equals(args.opened_buffers, state.opened_buffers) then
|
||||
-- no changes, no need to redraw
|
||||
return
|
||||
end
|
||||
state.opened_buffers = args.opened_buffers
|
||||
renderer.redraw(state)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---Navigate to the given path.
|
||||
---@param state_or_source_name string|table The state or source name to navigate.
|
||||
---@param path string? Path to navigate to. If empty, will navigate to the cwd.
|
||||
---@param path_to_reveal string? Node to focus after the items are loaded.
|
||||
---@param callback function? Callback to call after the items are loaded.
|
||||
---@param async boolean? Whether to load the items asynchronously, may not be respected by all sources.
|
||||
M.navigate = function(state_or_source_name, path, path_to_reveal, callback, async)
|
||||
require("neo-tree").ensure_config()
|
||||
local state, source_name
|
||||
if type(state_or_source_name) == "string" then
|
||||
state = M.get_state(state_or_source_name)
|
||||
source_name = state_or_source_name
|
||||
elseif type(state_or_source_name) == "table" then
|
||||
state = state_or_source_name
|
||||
source_name = state.name
|
||||
else
|
||||
log.error("navigate: state_or_source_name must be a string or a table")
|
||||
end
|
||||
log.trace("navigate", source_name, path, path_to_reveal)
|
||||
local mod = get_source_data(source_name).module
|
||||
if not mod then
|
||||
mod = require("neo-tree.sources." .. source_name)
|
||||
end
|
||||
mod.navigate(state, path, path_to_reveal, callback, async)
|
||||
end
|
||||
|
||||
---Redraws the tree without scanning the filesystem again. Use this after
|
||||
-- making changes to the nodes that would affect how their components are
|
||||
-- rendered.
|
||||
M.redraw = function(source_name)
|
||||
M._for_each_state(source_name, function(state)
|
||||
renderer.redraw(state)
|
||||
end)
|
||||
end
|
||||
|
||||
---Refreshes the tree by scanning the filesystem again.
|
||||
M.refresh = function(source_name, callback)
|
||||
if type(callback) ~= "function" then
|
||||
callback = nil
|
||||
end
|
||||
local current_tabid = vim.api.nvim_get_current_tabpage()
|
||||
log.trace(source_name, "refresh")
|
||||
for i = 1, #all_states, 1 do
|
||||
local state = all_states[i]
|
||||
if state.tabid == current_tabid and state.path and renderer.window_exists(state) then
|
||||
local success, err = pcall(M.navigate, state, state.path, nil, callback)
|
||||
if not success then
|
||||
log.error(err)
|
||||
end
|
||||
else
|
||||
state.dirty = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.reveal_current_file = function(source_name, callback, force_cwd)
|
||||
log.trace("Revealing current file")
|
||||
local state = M.get_state(source_name)
|
||||
state.current_position = nil
|
||||
|
||||
-- When events trigger that try to restore the position of the cursor in the tree window,
|
||||
-- we want them to ignore this "iteration" as the user is trying to explicitly focus a
|
||||
-- (potentially) different position/node
|
||||
state.position.is.restorable = false
|
||||
|
||||
require("neo-tree").close_all_except(source_name)
|
||||
local path = M.get_path_to_reveal()
|
||||
if not path then
|
||||
M.focus(source_name)
|
||||
return
|
||||
end
|
||||
local cwd = state.path
|
||||
if cwd == nil then
|
||||
cwd = M.get_cwd(state)
|
||||
end
|
||||
if force_cwd then
|
||||
if not utils.is_subpath(cwd, path) then
|
||||
state.path, _ = utils.split_path(path)
|
||||
end
|
||||
elseif not utils.is_subpath(cwd, path) then
|
||||
cwd, _ = utils.split_path(path)
|
||||
inputs.confirm("File not in cwd. Change cwd to " .. cwd .. "?", function(response)
|
||||
if response == true then
|
||||
state.path = cwd
|
||||
M.focus(source_name, path, callback)
|
||||
else
|
||||
M.focus(source_name, nil, callback)
|
||||
end
|
||||
end)
|
||||
return
|
||||
end
|
||||
if path then
|
||||
if not renderer.focus_node(state, path) then
|
||||
M.focus(source_name, path, callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.reveal_in_split = function(source_name, callback)
|
||||
local state = M.get_state(source_name, nil, vim.api.nvim_get_current_win())
|
||||
state.current_position = "current"
|
||||
local path_to_reveal = M.get_path_to_reveal()
|
||||
if not path_to_reveal then
|
||||
M.navigate(state, nil, nil, callback)
|
||||
return
|
||||
end
|
||||
local cwd = state.path
|
||||
if cwd == nil then
|
||||
cwd = M.get_cwd(state)
|
||||
end
|
||||
if cwd and not utils.is_subpath(cwd, path_to_reveal) then
|
||||
state.path, _ = utils.split_path(path_to_reveal)
|
||||
end
|
||||
M.navigate(state, state.path, path_to_reveal, callback)
|
||||
end
|
||||
|
||||
---Opens the tree and displays the current path or cwd, without focusing it.
|
||||
M.show = function(source_name)
|
||||
local state = M.get_state(source_name)
|
||||
state.current_position = nil
|
||||
if not renderer.window_exists(state) then
|
||||
local current_win = vim.api.nvim_get_current_win()
|
||||
M.navigate(source_name, state.path, nil, function()
|
||||
vim.api.nvim_set_current_win(current_win)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
M.show_in_split = function(source_name, callback)
|
||||
local state = M.get_state(source_name, nil, vim.api.nvim_get_current_win())
|
||||
state.current_position = "current"
|
||||
M.navigate(state, state.path, nil, callback)
|
||||
end
|
||||
|
||||
M.validate_source = function(source_name, module)
|
||||
if source_name == nil then
|
||||
error("register_source: source_name cannot be nil")
|
||||
end
|
||||
if module == nil then
|
||||
error("register_source: module cannot be nil")
|
||||
end
|
||||
if type(module) ~= "table" then
|
||||
error("register_source: module must be a table")
|
||||
end
|
||||
local required_functions = {
|
||||
"navigate",
|
||||
"setup",
|
||||
}
|
||||
for _, name in ipairs(required_functions) do
|
||||
if type(module[name]) ~= "function" then
|
||||
error("Source " .. source_name .. " must have a " .. name .. " function")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Configures the plugin, should be called before the plugin is used.
|
||||
---@param source_name string Name of the source.
|
||||
---@param config table Configuration table containing merged configuration for the source.
|
||||
---@param global_config table Global configuration table, shared between all sources.
|
||||
---@param module table Module containing the source's code.
|
||||
M.setup = function(source_name, config, global_config, module)
|
||||
log.debug(source_name, " setup ", config)
|
||||
M.unsubscribe_all(source_name)
|
||||
M.set_default_config(source_name, config)
|
||||
if module == nil then
|
||||
module = require("neo-tree.sources." .. source_name)
|
||||
end
|
||||
local success, err = pcall(M.validate_source, source_name, module)
|
||||
if success then
|
||||
success, err = pcall(module.setup, config, global_config)
|
||||
if success then
|
||||
get_source_data(source_name).module = module
|
||||
else
|
||||
log.error("Source " .. source_name .. " setup failed: " .. err)
|
||||
end
|
||||
else
|
||||
log.error("Source " .. source_name .. " is invalid: " .. err)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
271
bundle/neo-tree.nvim/lua/neo-tree/ui/highlights.lua
vendored
Normal file
271
bundle/neo-tree.nvim/lua/neo-tree/ui/highlights.lua
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
local log = require("neo-tree.log")
|
||||
local utils = require("neo-tree.utils")
|
||||
local vim = vim
|
||||
local M = {}
|
||||
|
||||
---@type integer
|
||||
M.ns_id = vim.api.nvim_create_namespace("neo-tree.nvim")
|
||||
|
||||
M.BUFFER_NUMBER = "NeoTreeBufferNumber"
|
||||
M.CURSOR_LINE = "NeoTreeCursorLine"
|
||||
M.DIM_TEXT = "NeoTreeDimText"
|
||||
M.DIRECTORY_ICON = "NeoTreeDirectoryIcon"
|
||||
M.DIRECTORY_NAME = "NeoTreeDirectoryName"
|
||||
M.DOTFILE = "NeoTreeDotfile"
|
||||
M.FADE_TEXT_1 = "NeoTreeFadeText1"
|
||||
M.FADE_TEXT_2 = "NeoTreeFadeText2"
|
||||
M.FILE_ICON = "NeoTreeFileIcon"
|
||||
M.FILE_NAME = "NeoTreeFileName"
|
||||
M.FILE_NAME_OPENED = "NeoTreeFileNameOpened"
|
||||
M.FILTER_TERM = "NeoTreeFilterTerm"
|
||||
M.FLOAT_BORDER = "NeoTreeFloatBorder"
|
||||
M.FLOAT_NORMAL = "NeoTreeFloatNormal"
|
||||
M.FLOAT_TITLE = "NeoTreeFloatTitle"
|
||||
M.GIT_ADDED = "NeoTreeGitAdded"
|
||||
M.GIT_CONFLICT = "NeoTreeGitConflict"
|
||||
M.GIT_DELETED = "NeoTreeGitDeleted"
|
||||
M.GIT_IGNORED = "NeoTreeGitIgnored"
|
||||
M.GIT_MODIFIED = "NeoTreeGitModified"
|
||||
M.GIT_RENAMED = "NeoTreeGitRenamed"
|
||||
M.GIT_STAGED = "NeoTreeGitStaged"
|
||||
M.GIT_UNTRACKED = "NeoTreeGitUntracked"
|
||||
M.GIT_UNSTAGED = "NeoTreeGitUnstaged"
|
||||
M.HIDDEN_BY_NAME = "NeoTreeHiddenByName"
|
||||
M.INDENT_MARKER = "NeoTreeIndentMarker"
|
||||
M.MESSAGE = "NeoTreeMessage"
|
||||
M.MODIFIED = "NeoTreeModified"
|
||||
M.NORMAL = "NeoTreeNormal"
|
||||
M.NORMALNC = "NeoTreeNormalNC"
|
||||
M.SIGNCOLUMN = "NeoTreeSignColumn"
|
||||
M.STATUS_LINE = "NeoTreeStatusLine"
|
||||
M.STATUS_LINE_NC = "NeoTreeStatusLineNC"
|
||||
M.TAB_ACTIVE = "NeoTreeTabActive"
|
||||
M.TAB_INACTIVE = "NeoTreeTabInactive"
|
||||
M.TAB_SEPARATOR_ACTIVE = "NeoTreeTabSeparatorActive"
|
||||
M.TAB_SEPARATOR_INACTIVE = "NeoTreeTabSeparatorInactive"
|
||||
M.VERTSPLIT = "NeoTreeVertSplit"
|
||||
M.WINSEPARATOR = "NeoTreeWinSeparator"
|
||||
M.END_OF_BUFFER = "NeoTreeEndOfBuffer"
|
||||
M.ROOT_NAME = "NeoTreeRootName"
|
||||
M.SYMBOLIC_LINK_TARGET = "NeoTreeSymbolicLinkTarget"
|
||||
M.TITLE_BAR = "NeoTreeTitleBar"
|
||||
M.INDENT_MARKER = "NeoTreeIndentMarker"
|
||||
M.EXPANDER = "NeoTreeExpander"
|
||||
M.WINDOWS_HIDDEN = "NeoTreeWindowsHidden"
|
||||
M.PREVIEW = "NeoTreePreview"
|
||||
|
||||
local function dec_to_hex(n, chars)
|
||||
chars = chars or 6
|
||||
local hex = string.format("%0" .. chars .. "x", n)
|
||||
while #hex < chars do
|
||||
hex = "0" .. hex
|
||||
end
|
||||
return hex
|
||||
end
|
||||
|
||||
---If the given highlight group is not defined, define it.
|
||||
---@param hl_group_name string The name of the highlight group.
|
||||
---@param link_to_if_exists table A list of highlight groups to link to, in
|
||||
--order of priority. The first one that exists will be used.
|
||||
---@param background string|nil The background color to use, in hex, if the highlight group
|
||||
--is not defined and it is not linked to another group.
|
||||
---@param foreground string|nil The foreground color to use, in hex, if the highlight group
|
||||
--is not defined and it is not linked to another group.
|
||||
---@gui string|nil The gui to use, if the highlight group is not defined and it is not linked
|
||||
--to another group.
|
||||
---@return table table The highlight group values.
|
||||
M.create_highlight_group = function(hl_group_name, link_to_if_exists, background, foreground, gui)
|
||||
local success, hl_group = pcall(vim.api.nvim_get_hl_by_name, hl_group_name, true)
|
||||
if not success or not hl_group.foreground or not hl_group.background then
|
||||
for _, link_to in ipairs(link_to_if_exists) do
|
||||
success, hl_group = pcall(vim.api.nvim_get_hl_by_name, link_to, true)
|
||||
if success then
|
||||
local new_group_has_settings = background or foreground or gui
|
||||
local link_to_has_settings = hl_group.foreground or hl_group.background
|
||||
if link_to_has_settings or not new_group_has_settings then
|
||||
vim.cmd("highlight default link " .. hl_group_name .. " " .. link_to)
|
||||
return hl_group
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if type(background) == "number" then
|
||||
background = dec_to_hex(background)
|
||||
end
|
||||
if type(foreground) == "number" then
|
||||
foreground = dec_to_hex(foreground)
|
||||
end
|
||||
|
||||
local cmd = "highlight default " .. hl_group_name
|
||||
if background then
|
||||
cmd = cmd .. " guibg=#" .. background
|
||||
end
|
||||
if foreground then
|
||||
cmd = cmd .. " guifg=#" .. foreground
|
||||
else
|
||||
cmd = cmd .. " guifg=NONE"
|
||||
end
|
||||
if gui then
|
||||
cmd = cmd .. " gui=" .. gui
|
||||
end
|
||||
vim.cmd(cmd)
|
||||
|
||||
return {
|
||||
background = background and tonumber(background, 16) or nil,
|
||||
foreground = foreground and tonumber(foreground, 16) or nil,
|
||||
}
|
||||
end
|
||||
return hl_group
|
||||
end
|
||||
|
||||
local faded_highlight_group_cache = {}
|
||||
M.get_faded_highlight_group = function(hl_group_name, fade_percentage)
|
||||
if type(hl_group_name) ~= "string" then
|
||||
error("hl_group_name must be a string")
|
||||
end
|
||||
if type(fade_percentage) ~= "number" then
|
||||
error("hl_group_name must be a number")
|
||||
end
|
||||
if fade_percentage < 0 or fade_percentage > 1 then
|
||||
error("fade_percentage must be between 0 and 1")
|
||||
end
|
||||
|
||||
local key = hl_group_name .. "_" .. tostring(math.floor(fade_percentage * 100))
|
||||
if faded_highlight_group_cache[key] then
|
||||
return faded_highlight_group_cache[key]
|
||||
end
|
||||
|
||||
local normal = vim.api.nvim_get_hl_by_name("Normal", true)
|
||||
if type(normal.foreground) ~= "number" then
|
||||
if vim.api.nvim_get_option("background") == "dark" then
|
||||
normal.foreground = 0xffffff
|
||||
else
|
||||
normal.foreground = 0x000000
|
||||
end
|
||||
end
|
||||
if type(normal.background) ~= "number" then
|
||||
if vim.api.nvim_get_option("background") == "dark" then
|
||||
normal.background = 0x000000
|
||||
else
|
||||
normal.background = 0xffffff
|
||||
end
|
||||
end
|
||||
local foreground = dec_to_hex(normal.foreground)
|
||||
local background = dec_to_hex(normal.background)
|
||||
|
||||
local hl_group = vim.api.nvim_get_hl_by_name(hl_group_name, true)
|
||||
if type(hl_group.foreground) == "number" then
|
||||
foreground = dec_to_hex(hl_group.foreground)
|
||||
end
|
||||
if type(hl_group.background) == "number" then
|
||||
background = dec_to_hex(hl_group.background)
|
||||
end
|
||||
|
||||
local gui = {}
|
||||
if hl_group.bold then
|
||||
table.insert(gui, "bold")
|
||||
end
|
||||
if hl_group.italic then
|
||||
table.insert(gui, "italic")
|
||||
end
|
||||
if hl_group.underline then
|
||||
table.insert(gui, "underline")
|
||||
end
|
||||
if hl_group.undercurl then
|
||||
table.insert(gui, "undercurl")
|
||||
end
|
||||
if #gui > 0 then
|
||||
gui = table.concat(gui, ",")
|
||||
else
|
||||
gui = nil
|
||||
end
|
||||
|
||||
local f_red = tonumber(foreground:sub(1, 2), 16)
|
||||
local f_green = tonumber(foreground:sub(3, 4), 16)
|
||||
local f_blue = tonumber(foreground:sub(5, 6), 16)
|
||||
|
||||
local b_red = tonumber(background:sub(1, 2), 16)
|
||||
local b_green = tonumber(background:sub(3, 4), 16)
|
||||
local b_blue = tonumber(background:sub(5, 6), 16)
|
||||
|
||||
local red = (f_red * fade_percentage) + (b_red * (1 - fade_percentage))
|
||||
local green = (f_green * fade_percentage) + (b_green * (1 - fade_percentage))
|
||||
local blue = (f_blue * fade_percentage) + (b_blue * (1 - fade_percentage))
|
||||
|
||||
local new_foreground =
|
||||
string.format("%s%s%s", dec_to_hex(red, 2), dec_to_hex(green, 2), dec_to_hex(blue, 2))
|
||||
|
||||
M.create_highlight_group(key, {}, hl_group.background, new_foreground, gui)
|
||||
faded_highlight_group_cache[key] = key
|
||||
return key
|
||||
end
|
||||
|
||||
M.setup = function()
|
||||
-- Reset this here in case of color scheme change
|
||||
faded_highlight_group_cache = {}
|
||||
|
||||
local normal_hl = M.create_highlight_group(M.NORMAL, { "Normal" })
|
||||
local normalnc_hl = M.create_highlight_group(M.NORMALNC, { "NormalNC", M.NORMAL })
|
||||
|
||||
M.create_highlight_group(M.SIGNCOLUMN, { "SignColumn", M.NORMAL })
|
||||
|
||||
M.create_highlight_group(M.STATUS_LINE, { "StatusLine" })
|
||||
M.create_highlight_group(M.STATUS_LINE_NC, { "StatusLineNC" })
|
||||
|
||||
M.create_highlight_group(M.VERTSPLIT, { "VertSplit" })
|
||||
M.create_highlight_group(M.WINSEPARATOR, { "WinSeparator" })
|
||||
|
||||
M.create_highlight_group(M.END_OF_BUFFER, { "EndOfBuffer" })
|
||||
|
||||
local float_border_hl =
|
||||
M.create_highlight_group(M.FLOAT_BORDER, { "FloatBorder" }, normalnc_hl.background, "444444")
|
||||
|
||||
M.create_highlight_group(M.FLOAT_NORMAL, { "NormalFloat", M.NORMAL })
|
||||
|
||||
M.create_highlight_group(M.FLOAT_TITLE, {}, float_border_hl.background, normal_hl.foreground)
|
||||
|
||||
local title_fg = normal_hl.background
|
||||
if title_fg == float_border_hl.foreground then
|
||||
title_fg = normal_hl.foreground
|
||||
end
|
||||
M.create_highlight_group(M.TITLE_BAR, {}, float_border_hl.foreground, title_fg)
|
||||
|
||||
M.create_highlight_group(M.BUFFER_NUMBER, { "SpecialChar" })
|
||||
M.create_highlight_group(M.DIM_TEXT, {}, nil, "505050")
|
||||
M.create_highlight_group(M.MESSAGE, {}, nil, "505050", "italic")
|
||||
M.create_highlight_group(M.FADE_TEXT_1, {}, nil, "626262")
|
||||
M.create_highlight_group(M.FADE_TEXT_2, {}, nil, "444444")
|
||||
M.create_highlight_group(M.DOTFILE, {}, nil, "626262")
|
||||
M.create_highlight_group(M.HIDDEN_BY_NAME, { M.DOTFILE }, nil, nil)
|
||||
M.create_highlight_group(M.CURSOR_LINE, { "CursorLine" }, nil, nil, "bold")
|
||||
M.create_highlight_group(M.DIRECTORY_NAME, { "Directory" }, "NONE", "NONE")
|
||||
M.create_highlight_group(M.DIRECTORY_ICON, { "Directory" }, nil, "73cef4")
|
||||
M.create_highlight_group(M.FILE_ICON, { M.DIRECTORY_ICON })
|
||||
M.create_highlight_group(M.FILE_NAME, {}, "NONE", "NONE")
|
||||
M.create_highlight_group(M.FILE_NAME_OPENED, {}, nil, nil, "bold")
|
||||
M.create_highlight_group(M.SYMBOLIC_LINK_TARGET, { M.FILE_NAME })
|
||||
M.create_highlight_group(M.FILTER_TERM, { "SpecialChar", "Normal" })
|
||||
M.create_highlight_group(M.ROOT_NAME, {}, nil, nil, "bold,italic")
|
||||
M.create_highlight_group(M.INDENT_MARKER, { M.DIM_TEXT })
|
||||
M.create_highlight_group(M.EXPANDER, { M.DIM_TEXT })
|
||||
M.create_highlight_group(M.MODIFIED, {}, nil, "d7d787")
|
||||
M.create_highlight_group(M.WINDOWS_HIDDEN, { M.DOTFILE }, nil, nil)
|
||||
M.create_highlight_group(M.PREVIEW, { "Search" }, nil, nil)
|
||||
|
||||
M.create_highlight_group(M.GIT_ADDED, { "GitGutterAdd", "GitSignsAdd" }, nil, "5faf5f")
|
||||
M.create_highlight_group(M.GIT_DELETED, { "GitGutterDelete", "GitSignsDelete" }, nil, "ff5900")
|
||||
M.create_highlight_group(M.GIT_MODIFIED, { "GitGutterChange", "GitSignsChange" }, nil, "d7af5f")
|
||||
local conflict = M.create_highlight_group(M.GIT_CONFLICT, {}, nil, "ff8700", "italic,bold")
|
||||
M.create_highlight_group(M.GIT_IGNORED, { M.DOTFILE }, nil, nil)
|
||||
M.create_highlight_group(M.GIT_RENAMED, { M.GIT_MODIFIED }, nil, nil)
|
||||
M.create_highlight_group(M.GIT_STAGED, { M.GIT_ADDED }, nil, nil)
|
||||
M.create_highlight_group(M.GIT_UNSTAGED, { M.GIT_CONFLICT }, nil, nil)
|
||||
M.create_highlight_group(M.GIT_UNTRACKED, {}, nil, conflict.foreground, "italic")
|
||||
|
||||
M.create_highlight_group(M.TAB_ACTIVE, {}, nil, nil, "bold")
|
||||
M.create_highlight_group(M.TAB_INACTIVE, {}, "141414", "777777")
|
||||
M.create_highlight_group(M.TAB_SEPARATOR_ACTIVE, {}, nil, "0a0a0a")
|
||||
M.create_highlight_group(M.TAB_SEPARATOR_INACTIVE, {}, "141414", "101010")
|
||||
end
|
||||
|
||||
return M
|
80
bundle/neo-tree.nvim/lua/neo-tree/ui/inputs.lua
vendored
Normal file
80
bundle/neo-tree.nvim/lua/neo-tree/ui/inputs.lua
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
local vim = vim
|
||||
local Input = require("nui.input")
|
||||
local popups = require("neo-tree.ui.popups")
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {}
|
||||
|
||||
local should_use_popup_input = function()
|
||||
local nt = require("neo-tree")
|
||||
return utils.get_value(nt.config, "use_popups_for_input", true, false)
|
||||
end
|
||||
|
||||
M.show_input = function(input, callback)
|
||||
input:mount()
|
||||
|
||||
input:map("i", "<esc>", function()
|
||||
vim.cmd("stopinsert")
|
||||
input:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
input:map("i", "<C-w>", "<C-S-w>", { noremap = true })
|
||||
|
||||
local event = require("nui.utils.autocmd").event
|
||||
input:on({ event.BufLeave, event.BufDelete }, function()
|
||||
input:unmount()
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end, { once = true })
|
||||
end
|
||||
|
||||
M.input = function(message, default_value, callback, options, completion)
|
||||
if should_use_popup_input() then
|
||||
local popup_options = popups.popup_options(message, 10, options)
|
||||
|
||||
local input = Input(popup_options, {
|
||||
prompt = " ",
|
||||
default_value = default_value,
|
||||
on_submit = callback,
|
||||
})
|
||||
|
||||
M.show_input(input)
|
||||
else
|
||||
local opts = {
|
||||
prompt = message .. " ",
|
||||
default = default_value,
|
||||
}
|
||||
if completion then
|
||||
opts.completion = completion
|
||||
end
|
||||
vim.ui.input(opts, callback)
|
||||
end
|
||||
end
|
||||
|
||||
M.confirm = function(message, callback)
|
||||
if should_use_popup_input() then
|
||||
local popup_options = popups.popup_options(message, 10)
|
||||
|
||||
local input = Input(popup_options, {
|
||||
prompt = " y/n: ",
|
||||
on_close = function()
|
||||
callback(false)
|
||||
end,
|
||||
on_submit = function(value)
|
||||
callback(value == "y" or value == "Y")
|
||||
end,
|
||||
})
|
||||
|
||||
M.show_input(input)
|
||||
else
|
||||
local opts = {
|
||||
prompt = message .. " y/n: ",
|
||||
}
|
||||
vim.ui.input(opts, function(value)
|
||||
callback(value == "y" or value == "Y")
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
127
bundle/neo-tree.nvim/lua/neo-tree/ui/popups.lua
vendored
Normal file
127
bundle/neo-tree.nvim/lua/neo-tree/ui/popups.lua
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
local vim = vim
|
||||
local Input = require("nui.input")
|
||||
local NuiText = require("nui.text")
|
||||
local NuiPopup = require("nui.popup")
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local log = require("neo-tree.log")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.popup_options = function(title, min_width, override_options)
|
||||
local min_width = min_width or 30
|
||||
local width = string.len(title) + 2
|
||||
|
||||
local nt = require("neo-tree")
|
||||
local popup_border_style = nt.config.popup_border_style
|
||||
local popup_border_text = NuiText(" " .. title .. " ", highlights.FLOAT_TITLE)
|
||||
local col = 0
|
||||
-- fix popup position when using multigrid
|
||||
local popup_last_col = vim.api.nvim_win_get_position(0)[2] + width + 2
|
||||
if popup_last_col >= vim.o.columns then
|
||||
col = vim.o.columns - popup_last_col
|
||||
end
|
||||
local popup_options = {
|
||||
ns_id = highlights.ns_id,
|
||||
relative = "cursor",
|
||||
position = {
|
||||
row = 1,
|
||||
col = col,
|
||||
},
|
||||
size = width,
|
||||
border = {
|
||||
text = {
|
||||
top = popup_border_text,
|
||||
},
|
||||
style = popup_border_style,
|
||||
highlight = highlights.FLOAT_BORDER,
|
||||
},
|
||||
win_options = {
|
||||
winhighlight = "Normal:"
|
||||
.. highlights.FLOAT_NORMAL
|
||||
.. ",FloatBorder:"
|
||||
.. highlights.FLOAT_BORDER,
|
||||
},
|
||||
buf_options = {
|
||||
bufhidden = "delete",
|
||||
buflisted = false,
|
||||
filetype = "neo-tree-popup",
|
||||
},
|
||||
}
|
||||
|
||||
if popup_border_style == "NC" then
|
||||
local blank = NuiText(" ", highlights.TITLE_BAR)
|
||||
popup_border_text = NuiText(" " .. title .. " ", highlights.TITLE_BAR)
|
||||
popup_options.border = {
|
||||
style = { "▕", blank, "▏", "▏", " ", "▔", " ", "▕" },
|
||||
highlight = highlights.FLOAT_BORDER,
|
||||
text = {
|
||||
top = popup_border_text,
|
||||
top_align = "left",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
if override_options then
|
||||
return vim.tbl_extend("force", popup_options, override_options)
|
||||
else
|
||||
return popup_options
|
||||
end
|
||||
end
|
||||
|
||||
M.alert = function(title, message, size)
|
||||
local lines = {}
|
||||
local max_line_width = title:len()
|
||||
local add_line = function(line)
|
||||
if not type(line) == "string" then
|
||||
line = tostring(line)
|
||||
end
|
||||
if line:len() > max_line_width then
|
||||
max_line_width = line:len()
|
||||
end
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
if type(message) == "table" then
|
||||
for _, v in ipairs(message) do
|
||||
add_line(v)
|
||||
end
|
||||
else
|
||||
add_line(message)
|
||||
end
|
||||
|
||||
add_line("")
|
||||
add_line(" Press <Escape> or <Enter> to close")
|
||||
|
||||
local win_options = M.popup_options(title, 80)
|
||||
win_options.zindex = 60
|
||||
win_options.size = {
|
||||
width = max_line_width + 4,
|
||||
height = #lines + 1,
|
||||
}
|
||||
local win = NuiPopup(win_options)
|
||||
win:mount()
|
||||
|
||||
local success, msg = pcall(vim.api.nvim_buf_set_lines, win.bufnr, 0, 0, false, lines)
|
||||
if success then
|
||||
win:map("n", "<esc>", function(bufnr)
|
||||
win:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
win:map("n", "<enter>", function(bufnr)
|
||||
win:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
local event = require("nui.utils.autocmd").event
|
||||
win:on({ event.BufLeave, event.BufDelete }, function()
|
||||
win:unmount()
|
||||
end, { once = true })
|
||||
|
||||
-- why is this necessary?
|
||||
vim.api.nvim_set_current_win(win.winid)
|
||||
else
|
||||
log.error(msg)
|
||||
win:unmount()
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
1253
bundle/neo-tree.nvim/lua/neo-tree/ui/renderer.lua
vendored
Normal file
1253
bundle/neo-tree.nvim/lua/neo-tree/ui/renderer.lua
vendored
Normal file
File diff suppressed because it is too large
Load Diff
432
bundle/neo-tree.nvim/lua/neo-tree/ui/selector.lua
vendored
Normal file
432
bundle/neo-tree.nvim/lua/neo-tree/ui/selector.lua
vendored
Normal file
@ -0,0 +1,432 @@
|
||||
local vim = vim
|
||||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
|
||||
local M = {}
|
||||
|
||||
---calc_click_id_from_source:
|
||||
-- Calculates click_id that stores information of the source and window id
|
||||
-- DANGER: Do not change this function unless you know what you are doing
|
||||
---@param winid integer: window id of the window source_selector is placed
|
||||
---@param source_index integer: index of the source
|
||||
---@return integer
|
||||
local calc_click_id_from_source = function(winid, source_index)
|
||||
local base_number = #require("neo-tree").config.source_selector.sources + 1
|
||||
return base_number * winid + source_index
|
||||
end
|
||||
|
||||
---calc_source_from_click_id:
|
||||
-- Calculates source index and window id from click_id. Paired with `M.calc_click_id_from_source`
|
||||
-- DANGER: Do not change this function unless you know what you are doing
|
||||
---@param click_id integer: click_id
|
||||
---@return integer, integer
|
||||
local calc_source_from_click_id = function(click_id)
|
||||
local base_number = #require("neo-tree").config.source_selector.sources + 1
|
||||
return math.floor(click_id / base_number), click_id % base_number
|
||||
end
|
||||
---sep_tbl:
|
||||
-- Returns table expression of separator.
|
||||
-- Converts to table expression if sep is string.
|
||||
---@param sep string | table:
|
||||
---@return table: `{ left = .., right = .., override = .. }`
|
||||
local sep_tbl = function(sep)
|
||||
if type(sep) == "nil" then
|
||||
return {}
|
||||
elseif type(sep) ~= "table" then
|
||||
return { left = sep, right = sep, override = "active" }
|
||||
end
|
||||
return sep
|
||||
end
|
||||
|
||||
-- Function below provided by @akinsho
|
||||
-- https://github.com/nvim-neo-tree/neo-tree.nvim/pull/427#discussion_r924947766
|
||||
|
||||
-- truncate a string based on number of display columns/cells it occupies
|
||||
-- so that multibyte characters are not broken up mid-character
|
||||
---@param str string
|
||||
---@param col_limit number
|
||||
---@return string
|
||||
local function truncate_by_cell(str, col_limit)
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
if str and str:len() == api.nvim_strwidth(str) then
|
||||
return fn.strcharpart(str, 0, col_limit)
|
||||
end
|
||||
local short = fn.strcharpart(str, 0, col_limit)
|
||||
if api.nvim_strwidth(short) > col_limit then
|
||||
while api.nvim_strwidth(short) > col_limit do
|
||||
short = fn.strcharpart(short, 0, fn.strchars(short) - 1)
|
||||
end
|
||||
end
|
||||
return short
|
||||
end
|
||||
|
||||
---get_separators
|
||||
-- Returns information about separator on each tab.
|
||||
---@param source_index integer: index of source
|
||||
---@param active_index integer: index of active source. used to check if source is active and when `override = "active"`
|
||||
---@param force_ignore_left boolean: overwrites calculated results with "" if set to true
|
||||
---@param force_ignore_right boolean: overwrites calculated results with "" if set to true
|
||||
---@return table: something like `{ left = "|", right = "|" }`
|
||||
local get_separators = function(source_index, active_index, force_ignore_left, force_ignore_right)
|
||||
local config = require("neo-tree").config
|
||||
local is_active = source_index == active_index
|
||||
local sep = sep_tbl(config.source_selector.separator)
|
||||
if is_active then
|
||||
sep = vim.tbl_deep_extend("force", sep, sep_tbl(config.source_selector.separator_active))
|
||||
end
|
||||
local show_left = sep.override == "left"
|
||||
or (sep.override == "active" and source_index <= active_index)
|
||||
or sep.override == nil
|
||||
local show_right = sep.override == "right"
|
||||
or (sep.override == "active" and source_index >= active_index)
|
||||
or sep.override == nil
|
||||
return {
|
||||
left = (show_left and not force_ignore_left) and sep.left or "",
|
||||
right = (show_right and not force_ignore_right) and sep.right or "",
|
||||
}
|
||||
end
|
||||
|
||||
---get_selector_tab_info:
|
||||
-- Returns information to create a tab
|
||||
---@param source_name string: name of source. should be same as names in `config.sources`
|
||||
---@param source_index integer: index of source_name
|
||||
---@param is_active boolean: whether this source is currently focused
|
||||
---@param separator table: `{ left = .., right = .. }`: output from `get_separators()`
|
||||
---@return table (see code): Note: `length`: length of whole tab (including seps), `text_length`: length of tab excluding seps
|
||||
local get_selector_tab_info = function(source_name, source_index, is_active, separator)
|
||||
local config = require("neo-tree").config
|
||||
local separator_config = utils.resolve_config_option(config, "source_selector", nil)
|
||||
if separator_config == nil then
|
||||
log.warn("Cannot find source_selector config. `get_selector` abort.")
|
||||
return {}
|
||||
end
|
||||
local source_config = config[source_name] or {}
|
||||
local get_strlen = vim.api.nvim_strwidth
|
||||
local text = separator_config.sources[source_index].display_name or source_config.display_name or source_name
|
||||
local text_length = get_strlen(text)
|
||||
if separator_config.tabs_min_width ~= nil and text_length < separator_config.tabs_min_width then
|
||||
text = M.text_layout(text, separator_config.content_layout, separator_config.tabs_min_width)
|
||||
text_length = separator_config.tabs_min_width
|
||||
end
|
||||
if separator_config.tabs_max_width ~= nil and text_length > separator_config.tabs_max_width then
|
||||
text = M.text_layout(text, separator_config.content_layout, separator_config.tabs_max_width)
|
||||
text_length = separator_config.tabs_max_width
|
||||
end
|
||||
local tab_hl = is_active and separator_config.highlight_tab_active
|
||||
or separator_config.highlight_tab
|
||||
local sep_hl = is_active and separator_config.highlight_separator_active
|
||||
or separator_config.highlight_separator
|
||||
return {
|
||||
index = source_index,
|
||||
is_active = is_active,
|
||||
left = separator.left,
|
||||
right = separator.right,
|
||||
text = text,
|
||||
tab_hl = tab_hl,
|
||||
sep_hl = sep_hl,
|
||||
length = text_length + get_strlen(separator.left) + get_strlen(separator.right),
|
||||
text_length = text_length,
|
||||
}
|
||||
end
|
||||
|
||||
---text_with_hl:
|
||||
-- Returns text with highlight syntax for winbar / statusline
|
||||
---@param text string: text to highlight
|
||||
---@param tab_hl string | nil: if nil, does nothing
|
||||
---@return string: e.g. "%#HiName#text"
|
||||
local text_with_hl = function(text, tab_hl)
|
||||
if tab_hl == nil then
|
||||
return text
|
||||
end
|
||||
return string.format("%%#%s#%s", tab_hl, text)
|
||||
end
|
||||
|
||||
---add_padding:
|
||||
-- Use for creating padding with highlight
|
||||
---@param padding_legth number: number of padding. if float, value is rounded with `math.floor`
|
||||
---@param padchar string | nil: if nil, " " (space) is used
|
||||
---@return string
|
||||
local add_padding = function(padding_legth, padchar)
|
||||
if padchar == nil then
|
||||
padchar = " "
|
||||
end
|
||||
return string.rep(padchar, math.floor(padding_legth))
|
||||
end
|
||||
|
||||
---text_layout:
|
||||
-- Add padding to fill `output_width`.
|
||||
-- If `output_width` is less than `text_length`, text is truncated to fit `output_width`.
|
||||
---@param text string:
|
||||
---@param content_layout string: `"start", "center", "end"`: see `config.source_selector.tabs_layout` for more details
|
||||
---@param output_width integer: exact `strdisplaywidth` of the output string
|
||||
---@param trunc_char string | nil: Character used to indicate truncation. If nil, "…" (ellipsis) is used.
|
||||
---@return string
|
||||
local text_layout = function(text, content_layout, output_width, trunc_char)
|
||||
if output_width < 1 then
|
||||
return ""
|
||||
end
|
||||
local text_length = vim.fn.strdisplaywidth(text)
|
||||
local pad_length = output_width - text_length
|
||||
local left_pad, right_pad = 0, 0
|
||||
if pad_length < 0 then
|
||||
if output_width < 4 then
|
||||
return truncate_by_cell(text, output_width)
|
||||
else
|
||||
return truncate_by_cell(text, output_width - 1) .. trunc_char
|
||||
end
|
||||
elseif content_layout == "start" then
|
||||
left_pad, right_pad = 0, pad_length
|
||||
elseif content_layout == "end" then
|
||||
left_pad, right_pad = pad_length, 0
|
||||
elseif content_layout == "center" then
|
||||
left_pad, right_pad = pad_length / 2, math.ceil(pad_length / 2)
|
||||
end
|
||||
return add_padding(left_pad) .. text .. add_padding(right_pad)
|
||||
end
|
||||
|
||||
---render_tab:
|
||||
-- Renders string to express one tab for winbar / statusline.
|
||||
---@param left_sep string: left separator
|
||||
---@param right_sep string: right separator
|
||||
---@param sep_hl string: highlight of separators
|
||||
---@param text string: text, mostly name of source in this case
|
||||
---@param tab_hl string: highlight of text
|
||||
---@param click_id integer: id passed to `___neotree_selector_click`, should be calculated with `M.calc_click_id_from_source`
|
||||
---@return string: complete string to render one tab
|
||||
local render_tab = function(left_sep, right_sep, sep_hl, text, tab_hl, click_id)
|
||||
local res = "%" .. click_id .. "@v:lua.___neotree_selector_click@"
|
||||
if left_sep ~= nil then
|
||||
res = res .. text_with_hl(left_sep, sep_hl)
|
||||
end
|
||||
res = res .. text_with_hl(text, tab_hl)
|
||||
if right_sep ~= nil then
|
||||
res = res .. text_with_hl(right_sep, sep_hl)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
M.get_scrolled_off_node_text = function(state)
|
||||
if state == nil then
|
||||
state = require("neo-tree.sources.manager").get_state_for_window()
|
||||
if state == nil then
|
||||
return
|
||||
end
|
||||
end
|
||||
local win_top_line = vim.fn.line("w0")
|
||||
if win_top_line == nil or win_top_line == 1 then
|
||||
return
|
||||
end
|
||||
local node = state.tree:get_node(win_top_line)
|
||||
return " " .. vim.fn.fnamemodify(node.path, ":~:h")
|
||||
end
|
||||
|
||||
M.get = function()
|
||||
local state = require("neo-tree.sources.manager").get_state_for_window()
|
||||
if state == nil then
|
||||
return
|
||||
else
|
||||
local config = require("neo-tree").config
|
||||
local scrolled_off =
|
||||
utils.resolve_config_option(config, "source_selector.show_scrolled_off_parent_node", false)
|
||||
if scrolled_off then
|
||||
local node_text = M.get_scrolled_off_node_text(state)
|
||||
if node_text ~= nil then
|
||||
return node_text
|
||||
end
|
||||
end
|
||||
return M.get_selector(state, vim.api.nvim_win_get_width(0))
|
||||
end
|
||||
end
|
||||
|
||||
---get_selector:
|
||||
-- Does everything to generate the string for source_selector in winbar / statusline.
|
||||
---@param state table:
|
||||
---@param width integer: width of the entire window where the source_selector is displayed
|
||||
---@return string | nil
|
||||
M.get_selector = function(state, width)
|
||||
local config = require("neo-tree").config
|
||||
if config == nil then
|
||||
log.warn("Cannot find config. `get_selector` abort.")
|
||||
return nil
|
||||
end
|
||||
local winid = state.winid or vim.api.nvim_get_current_win()
|
||||
|
||||
-- load padding from config
|
||||
local padding = config.source_selector.padding
|
||||
if type(padding) == "number" then
|
||||
padding = { left = padding, right = padding }
|
||||
end
|
||||
width = math.floor(width - padding.left - padding.right)
|
||||
|
||||
-- generate information of each tab (look `get_selector_tab_info` for type hint)
|
||||
local tabs = {}
|
||||
local sources = config.source_selector.sources
|
||||
local active_index = #sources
|
||||
local length_sum, length_active, length_separators = 0, 0, 0
|
||||
for i, source_info in ipairs(sources) do
|
||||
local is_active = source_info.source == state.name
|
||||
if is_active then
|
||||
active_index = i
|
||||
end
|
||||
local separator = get_separators(
|
||||
i,
|
||||
active_index,
|
||||
config.source_selector.show_separator_on_edge == false and i == 1,
|
||||
config.source_selector.show_separator_on_edge == false and i == #sources
|
||||
)
|
||||
local element = get_selector_tab_info(source_info.source, i, is_active, separator)
|
||||
length_sum = length_sum + element.length
|
||||
length_separators = length_separators + element.length - element.text_length
|
||||
if is_active then
|
||||
length_active = element.length
|
||||
end
|
||||
table.insert(tabs, element)
|
||||
end
|
||||
|
||||
-- start creating string to display
|
||||
local tabs_layout = config.source_selector.tabs_layout
|
||||
local content_layout = config.source_selector.content_layout or "center"
|
||||
local hl_background = config.source_selector.highlight_background
|
||||
local trunc_char = config.source_selector.truncation_character or "…"
|
||||
local remaining_width = width - length_separators
|
||||
local return_string = text_with_hl(add_padding(padding.left), hl_background)
|
||||
if width < length_sum and config.source_selector.text_trunc_to_fit then -- not enough width
|
||||
local each_width = math.floor(remaining_width / #tabs)
|
||||
local remaining = remaining_width % each_width
|
||||
tabs_layout = "start"
|
||||
length_sum = width
|
||||
for _, tab in ipairs(tabs) do
|
||||
tab.text = text_layout( -- truncate text and pass it to "start"
|
||||
tab.text,
|
||||
"center",
|
||||
each_width + (tab.is_active and remaining or 0),
|
||||
trunc_char
|
||||
)
|
||||
end
|
||||
end
|
||||
if tabs_layout == "active" then
|
||||
local active_tab_length = width - length_sum + length_active
|
||||
for _, tab in ipairs(tabs) do
|
||||
return_string = return_string
|
||||
.. render_tab(
|
||||
tab.left,
|
||||
tab.right,
|
||||
tab.sep_hl,
|
||||
text_layout(
|
||||
tab.text,
|
||||
tab.is_active and content_layout or nil,
|
||||
active_tab_length,
|
||||
trunc_char
|
||||
),
|
||||
tab.tab_hl,
|
||||
calc_click_id_from_source(winid, tab.index)
|
||||
)
|
||||
.. text_with_hl("", hl_background)
|
||||
end
|
||||
elseif tabs_layout == "equal" then
|
||||
for _, tab in ipairs(tabs) do
|
||||
return_string = return_string
|
||||
.. render_tab(
|
||||
tab.left,
|
||||
tab.right,
|
||||
tab.sep_hl,
|
||||
text_layout(tab.text, content_layout, math.floor(remaining_width / #tabs), trunc_char),
|
||||
tab.tab_hl,
|
||||
calc_click_id_from_source(winid, tab.index)
|
||||
)
|
||||
.. text_with_hl("", hl_background)
|
||||
end
|
||||
else -- config.source_selector.tab_labels == "start", "end", "center"
|
||||
-- calculate padding based on tabs_layout
|
||||
local pad_length = width - length_sum
|
||||
local left_pad, right_pad = 0, 0
|
||||
if pad_length > 0 then
|
||||
if tabs_layout == "start" then
|
||||
left_pad, right_pad = 0, pad_length
|
||||
elseif tabs_layout == "end" then
|
||||
left_pad, right_pad = pad_length, 0
|
||||
elseif tabs_layout == "center" then
|
||||
left_pad, right_pad = pad_length / 2, math.ceil(pad_length / 2)
|
||||
end
|
||||
end
|
||||
|
||||
for i, tab in ipairs(tabs) do
|
||||
if width == 0 then
|
||||
break
|
||||
end
|
||||
|
||||
-- only render trunc_char if there is no space for the tab
|
||||
local sep_length = tab.length - tab.text_length
|
||||
if width <= sep_length + 1 then
|
||||
return_string = return_string
|
||||
.. text_with_hl(trunc_char .. add_padding(width - 1), hl_background)
|
||||
width = 0
|
||||
break
|
||||
end
|
||||
|
||||
-- tab_length should not exceed width
|
||||
local tab_length = width < tab.length and width or tab.length
|
||||
width = width - tab_length
|
||||
|
||||
-- add padding for first and last tab
|
||||
local tab_text = tab.text
|
||||
if i == 1 then
|
||||
tab_text = add_padding(left_pad) .. tab_text
|
||||
tab_length = tab_length + left_pad
|
||||
end
|
||||
if i == #tabs then
|
||||
tab_text = tab_text .. add_padding(right_pad)
|
||||
tab_length = tab_length + right_pad
|
||||
end
|
||||
|
||||
return_string = return_string
|
||||
.. render_tab(
|
||||
tab.left,
|
||||
tab.right,
|
||||
tab.sep_hl,
|
||||
text_layout(tab_text, tabs_layout, tab_length - sep_length, trunc_char),
|
||||
tab.tab_hl,
|
||||
calc_click_id_from_source(winid, tab.index)
|
||||
)
|
||||
end
|
||||
end
|
||||
return return_string .. "%<%0@v:lua.___neotree_selector_click@"
|
||||
end
|
||||
|
||||
---set_source_selector:
|
||||
-- (public): Directly set source_selector to current window's winbar / statusline
|
||||
---@param state table: state
|
||||
---@return nil
|
||||
M.set_source_selector = function(state)
|
||||
local sel_config = utils.resolve_config_option(require("neo-tree").config, "source_selector", {})
|
||||
if sel_config and sel_config.winbar then
|
||||
vim.wo[state.winid].winbar = "%{%v:lua.require'neo-tree.ui.selector'.get()%}"
|
||||
end
|
||||
if sel_config and sel_config.statusline then
|
||||
vim.wo[state.winid].statusline = "%{%v:lua.require'neo-tree.ui.selector'.get()%}"
|
||||
end
|
||||
end
|
||||
|
||||
-- @v:lua@ in the tabline only supports global functions, so this is
|
||||
-- the only way to add click handlers without autoloaded vimscript functions
|
||||
_G.___neotree_selector_click = function(id, _, _, _)
|
||||
if id < 1 then
|
||||
return
|
||||
end
|
||||
local sources = require("neo-tree").config.source_selector.sources
|
||||
local winid, source_index = calc_source_from_click_id(id)
|
||||
local state = manager.get_state_for_window(winid)
|
||||
if state == nil then
|
||||
log.warn("state not found for window ", winid, "; ignoring click")
|
||||
return
|
||||
end
|
||||
require("neo-tree.command").execute({
|
||||
source = sources[source_index].source,
|
||||
position = state.current_position,
|
||||
action = "focus",
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
1088
bundle/neo-tree.nvim/lua/neo-tree/utils.lua
vendored
Normal file
1088
bundle/neo-tree.nvim/lua/neo-tree/utils.lua
vendored
Normal file
File diff suppressed because it is too large
Load Diff
28
bundle/neo-tree.nvim/plugin/neo-tree.vim
vendored
Normal file
28
bundle/neo-tree.nvim/plugin/neo-tree.vim
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
if exists('g:loaded_neo_tree')
|
||||
finish
|
||||
endif
|
||||
let g:loaded_neo_tree = 1
|
||||
|
||||
if !exists('g:neo_tree_remove_legacy_commands')
|
||||
command! -nargs=? NeoTreeClose lua require("neo-tree").close_all("<args>")
|
||||
command! -nargs=? NeoTreeFloat lua require("neo-tree").float("<args>")
|
||||
command! -nargs=? NeoTreeFocus lua require("neo-tree").focus("<args>")
|
||||
command! -nargs=? NeoTreeShow lua require("neo-tree").show("<args>", true)
|
||||
command! -bang NeoTreeReveal lua require("neo-tree").reveal_current_file("filesystem", false, "<bang>" == "!")
|
||||
command! NeoTreeRevealInSplit lua require("neo-tree").reveal_in_split("filesystem", false)
|
||||
command! NeoTreeShowInSplit lua require("neo-tree").show_in_split("filesystem", false)
|
||||
|
||||
command! -nargs=? NeoTreeFloatToggle lua require("neo-tree").float("<args>", true)
|
||||
command! -nargs=? NeoTreeFocusToggle lua require("neo-tree").focus("<args>", true, true)
|
||||
command! -nargs=? NeoTreeShowToggle lua require("neo-tree").show("<args>", true, true, true)
|
||||
command! -bang NeoTreeRevealToggle lua require("neo-tree").reveal_current_file("filesystem", true, "<bang>" == "!")
|
||||
command! NeoTreeRevealInSplitToggle lua require("neo-tree").reveal_in_split("filesystem", true)
|
||||
command! NeoTreeShowInSplitToggle lua require("neo-tree").show_in_split("filesystem", true)
|
||||
|
||||
command! NeoTreePasteConfig lua require("neo-tree").paste_default_config()
|
||||
command! -nargs=? NeoTreeSetLogLevel lua require("neo-tree").set_log_level("<args>")
|
||||
command! NeoTreeLogs lua require("neo-tree").show_logs()
|
||||
endif
|
||||
|
||||
command! -nargs=* -complete=custom,v:lua.require'neo-tree.command'.complete_args
|
||||
\ Neotree lua require("neo-tree.command")._command(<f-args>)
|
38
bundle/neo-tree.nvim/release.sh
vendored
Normal file
38
bundle/neo-tree.nvim/release.sh
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
#/bin/bash
|
||||
REPO="nvim-neo-tree/neo-tree.nvim"
|
||||
LAST_VERSION=$(curl --silent "https://api.github.com/repos/$REPO/releases/latest" | jq -r .tag_name)
|
||||
echo "LAST_VERSION=$LAST_VERSION"
|
||||
MAJOR=$(cut -d. -f1 <<<"$LAST_VERSION")
|
||||
MINOR=$(cut -d. -f2 <<<"$LAST_VERSION")
|
||||
echo
|
||||
|
||||
RELEASE_BRANCH="${1:-v${MAJOR}.x}"
|
||||
echo "RELEASE_BRANCH=$RELEASE_BRANCH"
|
||||
NEXT_VERSION=$MAJOR.$((MINOR+1))
|
||||
NEW_VERSION="${2:-${NEXT_VERSION}}"
|
||||
echo "NEW_VERSION=$NEW_VERSION"
|
||||
echo
|
||||
|
||||
read -p "Are you sure you want to publish this release? " -n 1 -r
|
||||
echo # (optional) move to a new line
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]
|
||||
then
|
||||
[[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell
|
||||
fi
|
||||
|
||||
git fetch
|
||||
git checkout main
|
||||
git pull
|
||||
echo "Merging to ${RELEASE_BRANCH}"
|
||||
git checkout $RELEASE_BRANCH
|
||||
git pull
|
||||
if git merge --ff-only origin/main; then
|
||||
git push
|
||||
git tag -a $NEW_VERSION -m "Release ${NEW_VERSION}"
|
||||
git push origin $NEW_VERSION
|
||||
echo "Creating Release"
|
||||
gh release create $NEW_VERSION --generate-notes
|
||||
else
|
||||
echo "RELEASE FAILED! Could not fast-forward release to $RELEASE_BRANCH"
|
||||
fi
|
||||
git checkout main
|
85
bundle/neo-tree.nvim/scripts/test.sh
vendored
Normal file
85
bundle/neo-tree.nvim/scripts/test.sh
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
luacov_dir=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "${1}" in
|
||||
--clean)
|
||||
shift
|
||||
echo "[test] cleaning up environment"
|
||||
rm -rf ./.testcache
|
||||
echo "[test] envionment cleaned"
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
function setup_environment() {
|
||||
echo
|
||||
echo "[test] setting up environment"
|
||||
echo
|
||||
|
||||
local plugins_dir="./.testcache/site/pack/vendor/start"
|
||||
if [[ ! -d "${plugins_dir}" ]]; then
|
||||
mkdir -p "${plugins_dir}"
|
||||
fi
|
||||
|
||||
if [[ ! -d "${plugins_dir}/nui.nvim" ]]; then
|
||||
echo "[plugins] nui.nvim: installing..."
|
||||
git clone https://github.com/MunifTanjim/nui.nvim "${plugins_dir}/nui.nvim"
|
||||
echo "[plugins] nui.nvim: installed"
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -d "${plugins_dir}/nvim-web-devicons" ]]; then
|
||||
echo "[plugins] nvim-web-devicons: installing..."
|
||||
git clone https://github.com/nvim-tree/nvim-web-devicons "${plugins_dir}/nvim-web-devicons"
|
||||
echo "[plugins] nvim-web-devicons: installed"
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -d "${plugins_dir}/plenary.nvim" ]]; then
|
||||
echo "[plugins] plenary.nvim: installing..."
|
||||
git clone https://github.com/nvim-lua/plenary.nvim "${plugins_dir}/plenary.nvim"
|
||||
# this commit broke luacov
|
||||
#git -C "${plugins_dir}/plenary.nvim" revert --no-commit 9069d14a120cadb4f6825f76821533f2babcab92
|
||||
echo "[plugins] plenary.nvim: installed"
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "[test] environment ready"
|
||||
echo
|
||||
}
|
||||
|
||||
function luacov_start() {
|
||||
luacov_dir="$(dirname "$(luarocks which luacov 2>/dev/null | head -1)")"
|
||||
if [[ "${luacov_dir}" == "." ]]; then
|
||||
luacov_dir=""
|
||||
fi
|
||||
|
||||
if test -n "${luacov_dir}"; then
|
||||
rm -f luacov.*.out
|
||||
export LUA_PATH=";;${luacov_dir}/?.lua"
|
||||
fi
|
||||
}
|
||||
|
||||
function luacov_end() {
|
||||
if test -n "${luacov_dir}"; then
|
||||
luacov
|
||||
|
||||
echo
|
||||
tail -n +$(($(grep -n "^Summary$" luacov.report.out | cut -d":" -f1) - 1)) luacov.report.out
|
||||
fi
|
||||
}
|
||||
|
||||
setup_environment
|
||||
|
||||
#luacov_start
|
||||
|
||||
make test
|
||||
|
||||
#luacov_end
|
30
bundle/neo-tree.nvim/tests/mininit.lua
vendored
Normal file
30
bundle/neo-tree.nvim/tests/mininit.lua
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
-- Need the absolute path as when doing the testing we will issue things like `tcd` to change directory
|
||||
-- to where our temporary filesystem lives
|
||||
local root_dir = vim.fn.fnamemodify(vim.trim(vim.fn.system("git rev-parse --show-toplevel")), ":p")
|
||||
|
||||
package.path = string.format("%s;%s?.lua;%s?/init.lua", package.path, root_dir, root_dir)
|
||||
|
||||
vim.opt.packpath:prepend(string.format("%s", root_dir .. ".testcache/site"))
|
||||
|
||||
vim.opt.rtp = {
|
||||
root_dir,
|
||||
vim.env.VIMRUNTIME,
|
||||
}
|
||||
|
||||
vim.cmd([[
|
||||
filetype on
|
||||
packadd plenary.nvim
|
||||
packadd nui.nvim
|
||||
packadd nvim-web-devicons
|
||||
]])
|
||||
|
||||
vim.opt.swapfile = false
|
||||
|
||||
vim.cmd([[
|
||||
runtime plugin/neo-tree.vim
|
||||
]])
|
||||
|
||||
-- For debugging
|
||||
P = function(...)
|
||||
print(unpack(vim.tbl_map(vim.inspect, { ... })))
|
||||
end
|
102
bundle/neo-tree.nvim/tests/neo-tree/command/command_current_spec.lua
vendored
Normal file
102
bundle/neo-tree.nvim/tests/neo-tree/command/command_current_spec.lua
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
pcall(require, "luacov")
|
||||
|
||||
local Path = require("plenary.path")
|
||||
local u = require("tests.utils")
|
||||
local verify = require("tests.utils.verify")
|
||||
|
||||
local run_in_current_command = function(command, expected_tree_node)
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
|
||||
vim.cmd(command)
|
||||
verify.window_handle_is(winid)
|
||||
verify.buf_name_endswith(string.format("neo-tree filesystem [%s]", winid), 1000)
|
||||
if expected_tree_node then
|
||||
verify.filesystem_tree_node_is(expected_tree_node, winid)
|
||||
end
|
||||
end
|
||||
|
||||
describe("Command", function()
|
||||
local test = u.fs.init_test({
|
||||
items = {
|
||||
{
|
||||
name = "foo",
|
||||
type = "dir",
|
||||
items = {
|
||||
{
|
||||
name = "bar",
|
||||
type = "dir",
|
||||
items = {
|
||||
{ name = "baz1.txt", type = "file" },
|
||||
{ name = "baz2.txt", type = "file", id = "deepfile2" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name = "topfile1.txt", type = "file", id = "topfile1" },
|
||||
{ name = "topfile2.txt", type = "file", id = "topfile2" },
|
||||
},
|
||||
})
|
||||
|
||||
test.setup()
|
||||
|
||||
local fs_tree = test.fs_tree
|
||||
|
||||
after_each(function()
|
||||
u.clear_environment()
|
||||
end)
|
||||
|
||||
describe("netrw style:", function()
|
||||
it("`:Neotree current` should show neo-tree in current window", function()
|
||||
local cmd = "Neotree current"
|
||||
run_in_current_command(cmd)
|
||||
end)
|
||||
|
||||
it(
|
||||
"`:Neotree current reveal` should show neo-tree and reveal file in current window",
|
||||
function()
|
||||
local cmd = "Neotree current reveal"
|
||||
local testfile = fs_tree.lookup["topfile1"].abspath
|
||||
u.editfile(testfile)
|
||||
run_in_current_command(cmd, testfile)
|
||||
end
|
||||
)
|
||||
|
||||
it("`:Neotree current reveal toggle` should toggle neo-tree in current window", function()
|
||||
local cmd = "Neotree current reveal toggle"
|
||||
local testfile = fs_tree.lookup["topfile1"].abspath
|
||||
u.editfile(testfile)
|
||||
local tree_winid = vim.api.nvim_get_current_win()
|
||||
|
||||
-- toggle OPEN
|
||||
run_in_current_command(cmd, testfile)
|
||||
|
||||
-- toggle CLOSE
|
||||
vim.cmd(cmd)
|
||||
verify.window_handle_is(tree_winid)
|
||||
verify.buf_name_is(testfile)
|
||||
end)
|
||||
|
||||
it(
|
||||
"`:Neotree current reveal_force_cwd reveal_file=xyz` should reveal file current window if cwd is not a parent of file",
|
||||
function()
|
||||
vim.cmd("cd ~")
|
||||
local testfile = fs_tree.lookup["deepfile2"].abspath
|
||||
local cmd = "Neotree current reveal_force_cwd reveal_file=" .. testfile
|
||||
run_in_current_command(cmd, testfile)
|
||||
end
|
||||
)
|
||||
|
||||
it(
|
||||
"`:Neotree current reveal_force_cwd reveal_file=xyz` should reveal file current window if cwd is a parent of file",
|
||||
function()
|
||||
local testfile = fs_tree.lookup["deepfile2"].abspath
|
||||
local testfile_dir = Path:new(testfile):parent().filename
|
||||
vim.cmd(string.format("cd %s", testfile_dir))
|
||||
local cmd = "Neotree current reveal_force_cwd reveal_file=" .. testfile
|
||||
run_in_current_command(cmd, testfile)
|
||||
end
|
||||
)
|
||||
end)
|
||||
|
||||
test.teardown()
|
||||
end)
|
206
bundle/neo-tree.nvim/tests/neo-tree/command/command_spec.lua
vendored
Normal file
206
bundle/neo-tree.nvim/tests/neo-tree/command/command_spec.lua
vendored
Normal file
@ -0,0 +1,206 @@
|
||||
pcall(require, "luacov")
|
||||
|
||||
local u = require("tests.utils")
|
||||
local verify = require("tests.utils.verify")
|
||||
|
||||
local run_focus_command = function(command, expected_tree_node)
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
|
||||
vim.cmd(command)
|
||||
verify.window_handle_is_not(winid)
|
||||
verify.buf_name_endswith("neo-tree filesystem [1]")
|
||||
if expected_tree_node then
|
||||
verify.filesystem_tree_node_is(expected_tree_node)
|
||||
end
|
||||
end
|
||||
|
||||
local run_show_command = function(command, expected_tree_node)
|
||||
local starting_winid = vim.api.nvim_get_current_win()
|
||||
local starting_bufname = vim.api.nvim_buf_get_name(0)
|
||||
local expected_num_windows = #vim.api.nvim_list_wins() + 1
|
||||
|
||||
vim.cmd(command)
|
||||
verify.eventually(500, function()
|
||||
if #vim.api.nvim_list_wins() ~= expected_num_windows then
|
||||
return false
|
||||
end
|
||||
if vim.api.nvim_get_current_win() ~= starting_winid then
|
||||
return false
|
||||
end
|
||||
if vim.api.nvim_buf_get_name(0) ~= starting_bufname then
|
||||
return false
|
||||
end
|
||||
if expected_tree_node then
|
||||
verify.filesystem_tree_node_is(expected_tree_node)
|
||||
end
|
||||
return true
|
||||
end, "Expected to see a new window without focusing it.")
|
||||
end
|
||||
|
||||
describe("Command", function()
|
||||
local test = u.fs.init_test({
|
||||
items = {
|
||||
{
|
||||
name = "foo",
|
||||
type = "dir",
|
||||
items = {
|
||||
{
|
||||
name = "bar",
|
||||
type = "dir",
|
||||
items = {
|
||||
{ name = "baz1.txt", type = "file" },
|
||||
{ name = "baz2.txt", type = "file", id = "deepfile2" },
|
||||
},
|
||||
},
|
||||
{ name = "foofile1.txt", type = "file" },
|
||||
},
|
||||
},
|
||||
{ name = "topfile1.txt", type = "file", id = "topfile1" },
|
||||
},
|
||||
})
|
||||
|
||||
test.setup()
|
||||
|
||||
local fs_tree = test.fs_tree
|
||||
|
||||
after_each(function()
|
||||
u.clear_environment()
|
||||
end)
|
||||
|
||||
describe("with reveal:", function()
|
||||
it("`:Neotree float reveal` should reveal the current file in the floating window", function()
|
||||
local cmd = "Neotree float reveal"
|
||||
local testfile = fs_tree.lookup["./foo/bar/baz1.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd, testfile)
|
||||
end)
|
||||
|
||||
it("`:Neotree reveal toggle` should toggle the reveal-state of the tree", function()
|
||||
local cmd = "Neotree reveal toggle"
|
||||
local testfile = fs_tree.lookup["./foo/foofile1.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
|
||||
-- toggle OPEN
|
||||
run_focus_command(cmd, testfile)
|
||||
local tree_winid = vim.api.nvim_get_current_win()
|
||||
|
||||
-- toggle CLOSE
|
||||
vim.cmd(cmd)
|
||||
verify.window_handle_is_not(tree_winid)
|
||||
verify.buf_name_is(testfile)
|
||||
|
||||
-- toggle OPEN with a different file
|
||||
testfile = fs_tree.lookup["./foo/bar/baz1.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd, testfile)
|
||||
end)
|
||||
|
||||
it(
|
||||
"`:Neotree float reveal toggle` should toggle the reveal-state of the floating window",
|
||||
function()
|
||||
local cmd = "Neotree float reveal toggle"
|
||||
local testfile = fs_tree.lookup["./foo/foofile1.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
|
||||
-- toggle OPEN
|
||||
run_focus_command(cmd, testfile)
|
||||
local tree_winid = vim.api.nvim_get_current_win()
|
||||
|
||||
-- toggle CLOSE
|
||||
vim.cmd("Neotree float reveal toggle")
|
||||
verify.window_handle_is_not(tree_winid)
|
||||
verify.buf_name_is(testfile)
|
||||
|
||||
-- toggle OPEN
|
||||
testfile = fs_tree.lookup["./foo/bar/baz2.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd, testfile)
|
||||
end
|
||||
)
|
||||
|
||||
it("`:Neotree reveal` should reveal the current file in the sidebar", function()
|
||||
local cmd = "Neotree reveal"
|
||||
local testfile = fs_tree.lookup["topfile1"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd, testfile)
|
||||
end)
|
||||
end)
|
||||
|
||||
for _, follow_current_file in ipairs({ true, false }) do
|
||||
require("neo-tree").setup({
|
||||
filesystem = {
|
||||
follow_current_file = follow_current_file,
|
||||
},
|
||||
})
|
||||
|
||||
describe(string.format("w/ follow_current_file=%s", follow_current_file), function()
|
||||
describe("with show :", function()
|
||||
it("`:Neotree show` should show the window without focusing", function()
|
||||
local cmd = "Neotree show"
|
||||
local testfile = fs_tree.lookup["topfile1"].abspath
|
||||
u.editfile(testfile)
|
||||
run_show_command(cmd)
|
||||
end)
|
||||
|
||||
it("`:Neotree show toggle` should retain the focused node on next show", function()
|
||||
local cmd = "Neotree show toggle"
|
||||
local topfile = fs_tree.lookup["topfile1"].abspath
|
||||
local baz = fs_tree.lookup["./foo/bar/baz1.txt"].abspath
|
||||
|
||||
-- focus a sub node to see if state is retained
|
||||
u.editfile(baz)
|
||||
run_focus_command(":Neotree reveal", baz)
|
||||
local expected_tree_node = baz
|
||||
|
||||
verify.after(500, function()
|
||||
-- toggle CLOSE
|
||||
vim.cmd(cmd)
|
||||
|
||||
-- toggle OPEN
|
||||
u.editfile(topfile)
|
||||
if follow_current_file then
|
||||
expected_tree_node = topfile
|
||||
end
|
||||
run_show_command(cmd, expected_tree_node)
|
||||
return true
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("with focus :", function()
|
||||
it("`:Neotree focus` should show the window and focus it", function()
|
||||
local cmd = "Neotree focus"
|
||||
local testfile = fs_tree.lookup["topfile1"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd)
|
||||
end)
|
||||
|
||||
it("`:Neotree focus toggle` should retain the focused node on next focus", function()
|
||||
local cmd = "Neotree focus toggle"
|
||||
local topfile = fs_tree.lookup["topfile1"].abspath
|
||||
local baz = fs_tree.lookup["./foo/bar/baz1.txt"].abspath
|
||||
|
||||
-- focus a sub node to see if state is retained
|
||||
u.editfile(baz)
|
||||
run_focus_command("Neotree reveal", baz)
|
||||
local expected_tree_node = baz
|
||||
|
||||
verify.after(500, function()
|
||||
-- toggle CLOSE
|
||||
vim.cmd(cmd)
|
||||
|
||||
-- toggle OPEN
|
||||
u.editfile(topfile)
|
||||
if follow_current_file then
|
||||
expected_tree_node = topfile
|
||||
end
|
||||
run_focus_command(cmd, expected_tree_node)
|
||||
return true
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test.teardown()
|
||||
end)
|
28
bundle/neo-tree.nvim/tests/neo-tree/events/queue_spec.lua
vendored
Normal file
28
bundle/neo-tree.nvim/tests/neo-tree/events/queue_spec.lua
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
pcall(require, "luacov")
|
||||
|
||||
describe("Event queue", function()
|
||||
it("should return data when handled = true", function()
|
||||
local events = require("neo-tree.events")
|
||||
events.subscribe({
|
||||
event = "test",
|
||||
handler = function()
|
||||
return { data = "first" }
|
||||
end,
|
||||
})
|
||||
events.subscribe({
|
||||
event = "test",
|
||||
handler = function()
|
||||
return { handled = true, data = "second" }
|
||||
end,
|
||||
})
|
||||
events.subscribe({
|
||||
event = "test",
|
||||
handler = function()
|
||||
return { data = "third" }
|
||||
end,
|
||||
})
|
||||
local result = events.fire_event("test") or {}
|
||||
local data = result.data
|
||||
assert.are.same("second", data)
|
||||
end)
|
||||
end)
|
146
bundle/neo-tree.nvim/tests/neo-tree/sources/container_spec.lua
vendored
Normal file
146
bundle/neo-tree.nvim/tests/neo-tree/sources/container_spec.lua
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
pcall(require, "luacov")
|
||||
|
||||
local ns_id = require("neo-tree.ui.highlights").ns_id
|
||||
local u = require("tests.utils")
|
||||
|
||||
local config = {
|
||||
renderers = {
|
||||
directory = {
|
||||
{
|
||||
"container",
|
||||
content = {
|
||||
{ "indent", zindex = 10 },
|
||||
{ "icon", zindex = 10 },
|
||||
{ "name", zindex = 10 },
|
||||
{ "name", zindex = 5, align = "right" },
|
||||
},
|
||||
},
|
||||
},
|
||||
file = {
|
||||
{
|
||||
"container",
|
||||
content = {
|
||||
{ "indent", zindex = 10 },
|
||||
{ "icon", zindex = 10 },
|
||||
{ "name", zindex = 10 },
|
||||
{ "name", zindex = 20, align = "right" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
window = {
|
||||
width = 40,
|
||||
},
|
||||
}
|
||||
|
||||
local config_right = {
|
||||
renderers = {
|
||||
directory = {
|
||||
{
|
||||
"container",
|
||||
enable_character_fade = false,
|
||||
content = {
|
||||
{ "indent", zindex = 10, align = "right" },
|
||||
{ "icon", zindex = 10, align = "right" },
|
||||
{ "name", zindex = 10, align = "right" },
|
||||
},
|
||||
},
|
||||
},
|
||||
file = {
|
||||
{
|
||||
"container",
|
||||
enable_character_fade = false,
|
||||
content = {
|
||||
{ "indent", zindex = 10, align = "right" },
|
||||
{ "icon", zindex = 10, align = "right" },
|
||||
{ "name", zindex = 10, align = "right" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
window = {
|
||||
width = 40,
|
||||
},
|
||||
}
|
||||
|
||||
local test_dir = {
|
||||
items = {
|
||||
{
|
||||
name = "foo",
|
||||
type = "dir",
|
||||
items = {
|
||||
{
|
||||
name = "bar",
|
||||
type = "dir",
|
||||
items = {
|
||||
{ name = "bar1.txt", type = "file" },
|
||||
{ name = "bar2.txt", type = "file" },
|
||||
},
|
||||
},
|
||||
{ name = "foo1.lua", type = "file" },
|
||||
},
|
||||
},
|
||||
{ name = "bazbazbazbazbazbazbazbazbazbazbazbazbazbazbazbazbaz", type = "dir" },
|
||||
{ name = "1.md", type = "file" },
|
||||
},
|
||||
}
|
||||
|
||||
describe("sources/components/container", function()
|
||||
local req_switch = u.get_require_switch()
|
||||
|
||||
local test = u.fs.init_test(test_dir)
|
||||
test.setup()
|
||||
|
||||
after_each(function()
|
||||
if req_switch then
|
||||
req_switch.restore()
|
||||
end
|
||||
|
||||
u.clear_environment()
|
||||
end)
|
||||
|
||||
describe("should expand to width", function()
|
||||
for pow = 4, 8 do
|
||||
it(2 ^ pow, function()
|
||||
config.window.width = 2 ^ pow
|
||||
require("neo-tree").setup(config)
|
||||
vim.cmd([[Neotree focus]])
|
||||
u.wait_for(function()
|
||||
return vim.bo.filetype == "neo-tree"
|
||||
end)
|
||||
|
||||
assert.equals(vim.bo.filetype, "neo-tree")
|
||||
|
||||
local width = vim.api.nvim_win_get_width(0)
|
||||
local lines = vim.api.nvim_buf_get_lines(0, 2, -1, false)
|
||||
for _, line in ipairs(lines) do
|
||||
assert.is_true(#line >= width)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("right-align should matches width", function()
|
||||
for pow = 4, 8 do
|
||||
it(2 ^ pow, function()
|
||||
config_right.window.width = 2 ^ pow
|
||||
require("neo-tree").setup(config_right)
|
||||
vim.cmd([[Neotree focus]])
|
||||
u.wait_for(function()
|
||||
return vim.bo.filetype == "neo-tree"
|
||||
end)
|
||||
|
||||
assert.equals(vim.bo.filetype, "neo-tree")
|
||||
|
||||
local width = vim.api.nvim_win_get_width(0)
|
||||
local lines = vim.api.nvim_buf_get_lines(0, 1, -1, false)
|
||||
for _, line in ipairs(lines) do
|
||||
line = vim.fn.trim(line, " ", 2)
|
||||
assert.equals(width, vim.fn.strchars(line))
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
test.teardown()
|
||||
end)
|
204
bundle/neo-tree.nvim/tests/neo-tree/sources/filesystem/filesystem_command_spec.lua
vendored
Normal file
204
bundle/neo-tree.nvim/tests/neo-tree/sources/filesystem/filesystem_command_spec.lua
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
pcall(require, "luacov")
|
||||
|
||||
local u = require("tests.utils")
|
||||
local verify = require("tests.utils.verify")
|
||||
|
||||
local run_focus_command = function(command, expected_tree_node)
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
|
||||
vim.cmd(command)
|
||||
verify.window_handle_is_not(winid)
|
||||
verify.buf_name_endswith("neo-tree filesystem [1]")
|
||||
if expected_tree_node then
|
||||
verify.filesystem_tree_node_is(expected_tree_node)
|
||||
end
|
||||
end
|
||||
|
||||
local run_show_command = function(command, expected_tree_node)
|
||||
local starting_winid = vim.api.nvim_get_current_win()
|
||||
local starting_bufname = vim.api.nvim_buf_get_name(0)
|
||||
local expected_num_windows = #vim.api.nvim_list_wins() + 1
|
||||
|
||||
vim.cmd(command)
|
||||
verify.eventually(500, function()
|
||||
if #vim.api.nvim_list_wins() ~= expected_num_windows then
|
||||
return false
|
||||
end
|
||||
if vim.api.nvim_get_current_win() ~= starting_winid then
|
||||
return false
|
||||
end
|
||||
if vim.api.nvim_buf_get_name(0) ~= starting_bufname then
|
||||
return false
|
||||
end
|
||||
if expected_tree_node then
|
||||
verify.filesystem_tree_node_is(expected_tree_node)
|
||||
end
|
||||
return true
|
||||
end, "Expected to see a new window without focusing it.")
|
||||
end
|
||||
|
||||
describe("Filesystem", function()
|
||||
local test = u.fs.init_test({
|
||||
items = {
|
||||
{
|
||||
name = "foo",
|
||||
type = "dir",
|
||||
items = {
|
||||
{
|
||||
name = "bar",
|
||||
type = "dir",
|
||||
items = {
|
||||
{ name = "baz1.txt", type = "file" },
|
||||
{ name = "baz2.txt", type = "file", id = "deepfile2" },
|
||||
},
|
||||
},
|
||||
{ name = "foofile1.txt", type = "file" },
|
||||
},
|
||||
},
|
||||
{ name = "topfile1.txt", type = "file", id = "topfile1" },
|
||||
},
|
||||
})
|
||||
|
||||
test.setup()
|
||||
|
||||
local fs_tree = test.fs_tree
|
||||
|
||||
after_each(function()
|
||||
u.clear_environment()
|
||||
end)
|
||||
|
||||
describe("reveal command", function()
|
||||
it("should reveal the current file in the sidebar", function()
|
||||
local cmd = "NeoTreeReveal"
|
||||
local testfile = fs_tree.lookup["topfile1"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd, testfile)
|
||||
end)
|
||||
|
||||
it("should reveal the current file in the floating window", function()
|
||||
local cmd = "NeoTreeFloat"
|
||||
local testfile = fs_tree.lookup["./foo/bar/baz1.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd, testfile)
|
||||
end)
|
||||
|
||||
it("should toggle the reveal-state of the tree", function()
|
||||
local cmd = "NeoTreeRevealToggle"
|
||||
local testfile = fs_tree.lookup["./foo/foofile1.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
|
||||
-- toggle OPEN
|
||||
run_focus_command(cmd, testfile)
|
||||
local tree_winid = vim.api.nvim_get_current_win()
|
||||
|
||||
-- toggle CLOSE
|
||||
vim.cmd(cmd)
|
||||
verify.window_handle_is_not(tree_winid)
|
||||
verify.buf_name_is(testfile)
|
||||
|
||||
-- toggle OPEN with a different file
|
||||
testfile = fs_tree.lookup["./foo/bar/baz1.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd, testfile)
|
||||
end)
|
||||
|
||||
it("should toggle the reveal-state of the floating window", function()
|
||||
local cmd = "NeoTreeFloatToggle"
|
||||
local testfile = fs_tree.lookup["./foo/foofile1.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
|
||||
-- toggle OPEN
|
||||
run_focus_command(cmd, testfile)
|
||||
local tree_winid = vim.api.nvim_get_current_win()
|
||||
|
||||
-- toggle CLOSE
|
||||
vim.cmd("NeoTreeRevealToggle")
|
||||
verify.window_handle_is_not(tree_winid)
|
||||
verify.buf_name_is(testfile)
|
||||
vim.wait(1000)
|
||||
|
||||
-- toggle OPEN
|
||||
testfile = fs_tree.lookup["./foo/bar/baz2.txt"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd, testfile)
|
||||
end)
|
||||
end)
|
||||
|
||||
for _, follow_current_file in ipairs({ true, false }) do
|
||||
require("neo-tree").setup({
|
||||
filesystem = {
|
||||
follow_current_file = follow_current_file,
|
||||
},
|
||||
})
|
||||
|
||||
describe(string.format("w/ follow_current_file=%s", follow_current_file), function()
|
||||
describe("show command", function()
|
||||
it("should show the window without focusing", function()
|
||||
local cmd = "NeoTreeShow"
|
||||
local testfile = fs_tree.lookup["topfile1"].abspath
|
||||
u.editfile(testfile)
|
||||
run_show_command(cmd)
|
||||
end)
|
||||
|
||||
it("should retain the focused node on next show", function()
|
||||
local cmd = "NeoTreeShowToggle"
|
||||
local topfile = fs_tree.lookup["topfile1"].abspath
|
||||
local baz = fs_tree.lookup["./foo/bar/baz1.txt"].abspath
|
||||
|
||||
-- focus a sub node to see if state is retained
|
||||
u.editfile(baz)
|
||||
run_focus_command("NeoTreeReveal", baz)
|
||||
local expected_tree_node = baz
|
||||
|
||||
verify.after(500, function()
|
||||
-- toggle CLOSE
|
||||
vim.cmd(cmd)
|
||||
|
||||
-- toggle OPEN
|
||||
u.editfile(topfile)
|
||||
if follow_current_file then
|
||||
expected_tree_node = topfile
|
||||
end
|
||||
run_show_command(cmd, expected_tree_node)
|
||||
return true
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("focus command", function()
|
||||
it("should show the window and focus it", function()
|
||||
local cmd = "NeoTreeFocus"
|
||||
local testfile = fs_tree.lookup["topfile1"].abspath
|
||||
u.editfile(testfile)
|
||||
run_focus_command(cmd)
|
||||
end)
|
||||
|
||||
it("should retain the focused node on next focus", function()
|
||||
local cmd = "NeoTreeFocusToggle"
|
||||
local topfile = fs_tree.lookup["topfile1"].abspath
|
||||
local baz = fs_tree.lookup["./foo/bar/baz1.txt"].abspath
|
||||
|
||||
-- focus a sub node to see if state is retained
|
||||
u.editfile(baz)
|
||||
run_focus_command("NeoTreeReveal", baz)
|
||||
local expected_tree_node = baz
|
||||
|
||||
verify.after(500, function()
|
||||
-- toggle CLOSE
|
||||
vim.cmd(cmd)
|
||||
|
||||
-- toggle OPEN
|
||||
u.editfile(topfile)
|
||||
if follow_current_file then
|
||||
expected_tree_node = topfile
|
||||
end
|
||||
run_focus_command(cmd, expected_tree_node)
|
||||
return true
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test.teardown()
|
||||
end)
|
92
bundle/neo-tree.nvim/tests/neo-tree/sources/filesystem/filesystem_netrw_hijack_spec.lua
vendored
Normal file
92
bundle/neo-tree.nvim/tests/neo-tree/sources/filesystem/filesystem_netrw_hijack_spec.lua
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
pcall(require, "luacov")
|
||||
|
||||
local u = require("tests.utils")
|
||||
local verify = require("tests.utils.verify")
|
||||
|
||||
describe("Filesystem netrw hijack", function()
|
||||
after_each(function()
|
||||
u.clear_environment()
|
||||
end)
|
||||
|
||||
it("does not interfere with netrw when disabled", function()
|
||||
require("neo-tree").setup({
|
||||
filesystem = {
|
||||
hijack_netrw_behavior = "disabled",
|
||||
window = {
|
||||
position = "left",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
vim.cmd("edit .")
|
||||
|
||||
assert(#vim.api.nvim_list_wins() == 1, "there should only be one window")
|
||||
|
||||
verify.after(100, function()
|
||||
local name = vim.api.nvim_buf_get_name(0)
|
||||
return name ~= "neo-tree filesystem [1]"
|
||||
end, "the buffer should not be neo-tree")
|
||||
end)
|
||||
|
||||
it("opens in sidebar when behavior is open_default", function()
|
||||
local file = "Makefile"
|
||||
vim.cmd("edit " .. file)
|
||||
|
||||
require("neo-tree").setup({
|
||||
filesystem = {
|
||||
hijack_netrw_behavior = "open_default",
|
||||
window = {
|
||||
position = "left",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
vim.cmd("edit .")
|
||||
|
||||
verify.eventually(200, function()
|
||||
return #vim.api.nvim_list_wins() == 2
|
||||
end, "there should be two windows")
|
||||
|
||||
verify.buf_name_endswith("neo-tree filesystem [1]")
|
||||
|
||||
verify.eventually(100, function()
|
||||
local expected_buf_name = "Makefile"
|
||||
local buf_at_2 = vim.api.nvim_win_get_buf(vim.fn.win_getid(2))
|
||||
local name_at_2 = vim.api.nvim_buf_get_name(buf_at_2)
|
||||
if name_at_2:sub(-#expected_buf_name) == expected_buf_name then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end, file .. " is not at window 2")
|
||||
end)
|
||||
|
||||
it("opens in in splits when behavior is open_current", function()
|
||||
local file = "Makefile"
|
||||
vim.cmd("edit " .. file)
|
||||
|
||||
require("neo-tree").setup({
|
||||
filesystem = {
|
||||
hijack_netrw_behavior = "open_current",
|
||||
},
|
||||
})
|
||||
|
||||
assert(#vim.api.nvim_list_wins() == 1, "Test should start with one window")
|
||||
|
||||
vim.cmd("edit .")
|
||||
|
||||
verify.eventually(200, function()
|
||||
assert(#vim.api.nvim_list_wins() == 1, "`edit .` should not open a new window")
|
||||
return vim.api.nvim_buf_get_option(0, "filetype") == "neo-tree"
|
||||
end, "neotree is not the only window")
|
||||
|
||||
vim.cmd("split .")
|
||||
|
||||
verify.eventually(200, function()
|
||||
if #vim.api.nvim_list_wins() ~= 2 then
|
||||
return false
|
||||
end
|
||||
return vim.api.nvim_buf_get_option(0, "filetype") == "neo-tree"
|
||||
end, "neotree is not in the second window")
|
||||
end)
|
||||
end)
|
125
bundle/neo-tree.nvim/tests/neo-tree/sources/manager_spec.lua
vendored
Normal file
125
bundle/neo-tree.nvim/tests/neo-tree/sources/manager_spec.lua
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
pcall(require, "luacov")
|
||||
|
||||
local u = require("tests.utils")
|
||||
local verify = require("tests.utils.verify")
|
||||
|
||||
local manager = require('neo-tree.sources.manager')
|
||||
|
||||
local get_dirs = function(winid)
|
||||
winid = winid or vim.api.nvim_get_current_win()
|
||||
local tabnr = vim.api.nvim_tabpage_get_number(vim.api.nvim_win_get_tabpage(winid))
|
||||
local winnr = vim.api.nvim_win_get_number(winid)
|
||||
return {
|
||||
win = vim.fn.getcwd(winnr),
|
||||
tab = vim.fn.getcwd(-1, tabnr),
|
||||
global = vim.fn.getcwd(-1, -1),
|
||||
}
|
||||
end
|
||||
|
||||
local get_state_for_tab = function(tabid)
|
||||
for _, state in ipairs(manager._get_all_states()) do
|
||||
if state.tabid == tabid then
|
||||
return state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local get_tabnr = function(tabid)
|
||||
return vim.api.nvim_tabpage_get_number(tabid or vim.api.nvim_get_current_tabpage())
|
||||
end
|
||||
|
||||
describe("Manager", function()
|
||||
local test = u.fs.init_test({
|
||||
items = {
|
||||
{
|
||||
name = "foo",
|
||||
type = "dir",
|
||||
items = {
|
||||
{ name = "foofile1.txt", type = "file" },
|
||||
},
|
||||
},
|
||||
{ name = "topfile1.txt", type = "file", id = "topfile1" },
|
||||
},
|
||||
})
|
||||
|
||||
test.setup()
|
||||
|
||||
local fs_tree = test.fs_tree
|
||||
|
||||
-- Just make sure we start all tests in the expected state
|
||||
before_each(function()
|
||||
u.eq(1, #vim.api.nvim_list_wins())
|
||||
u.eq(1, #vim.api.nvim_list_tabpages())
|
||||
vim.cmd.lcd(fs_tree.abspath)
|
||||
vim.cmd.tcd(fs_tree.abspath)
|
||||
vim.cmd.cd(fs_tree.abspath)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
u.clear_environment()
|
||||
end)
|
||||
|
||||
local setup_2_tabs = function()
|
||||
-- create 2 tabs
|
||||
local tab1 = vim.api.nvim_get_current_tabpage()
|
||||
local win1 = vim.api.nvim_get_current_win()
|
||||
vim.cmd.tabnew()
|
||||
local tab2 = vim.api.nvim_get_current_tabpage()
|
||||
local win2 = vim.api.nvim_get_current_win()
|
||||
u.neq(tab2, tab1)
|
||||
u.neq(win2, win1)
|
||||
|
||||
-- set different directories
|
||||
vim.api.nvim_set_current_tabpage(tab2)
|
||||
local base_dir = vim.fn.getcwd()
|
||||
vim.cmd.tcd('foo')
|
||||
local new_dir = vim.fn.getcwd()
|
||||
|
||||
-- open neo-tree
|
||||
vim.api.nvim_set_current_tabpage(tab1)
|
||||
vim.cmd.Neotree('show')
|
||||
vim.api.nvim_set_current_tabpage(tab2)
|
||||
vim.cmd.Neotree('show')
|
||||
|
||||
return {
|
||||
tab1 = tab1,
|
||||
tab2 = tab2,
|
||||
win1 = win1,
|
||||
win2 = win2,
|
||||
tab1_dir = base_dir,
|
||||
tab2_dir = new_dir,
|
||||
}
|
||||
end
|
||||
|
||||
it("should respect changed tab cwd", function()
|
||||
local ctx = setup_2_tabs()
|
||||
|
||||
local state1 = get_state_for_tab(ctx.tab1)
|
||||
local state2 = get_state_for_tab(ctx.tab2)
|
||||
u.eq(ctx.tab1_dir, manager.get_cwd(state1))
|
||||
u.eq(ctx.tab2_dir, manager.get_cwd(state2))
|
||||
end)
|
||||
|
||||
it("should have correct tab cwd after tabs order is changed", function()
|
||||
local ctx = setup_2_tabs()
|
||||
|
||||
-- tab numbers should be the same as ids
|
||||
u.eq(1, get_tabnr(ctx.tab1))
|
||||
u.eq(2, get_tabnr(ctx.tab2))
|
||||
|
||||
-- swap tabs
|
||||
vim.cmd.tabfirst()
|
||||
vim.cmd.tabmove('+1')
|
||||
|
||||
-- make sure tabs have been swapped
|
||||
u.eq(2, get_tabnr(ctx.tab1))
|
||||
u.eq(1, get_tabnr(ctx.tab2))
|
||||
|
||||
-- verify that tab dirs are the same as nvim tab cwd
|
||||
local state1 = get_state_for_tab(ctx.tab1)
|
||||
local state2 = get_state_for_tab(ctx.tab2)
|
||||
u.eq(get_dirs(ctx.win1).tab, manager.get_cwd(state1))
|
||||
u.eq(get_dirs(ctx.win2).tab, manager.get_cwd(state2))
|
||||
end)
|
||||
end)
|
||||
|
235
bundle/neo-tree.nvim/tests/neo-tree/ui/icons_spec.lua
vendored
Normal file
235
bundle/neo-tree.nvim/tests/neo-tree/ui/icons_spec.lua
vendored
Normal file
@ -0,0 +1,235 @@
|
||||
pcall(require, "luacov")
|
||||
|
||||
local ns_id = require("neo-tree.ui.highlights").ns_id
|
||||
local u = require("tests.utils")
|
||||
|
||||
describe("ui/icons", function()
|
||||
local req_switch = u.get_require_switch()
|
||||
|
||||
local test = u.fs.init_test({
|
||||
items = {
|
||||
{
|
||||
name = "foo",
|
||||
type = "dir",
|
||||
items = {
|
||||
{
|
||||
name = "bar",
|
||||
type = "dir",
|
||||
items = {
|
||||
{ name = "bar1.txt", type = "file" },
|
||||
{ name = "bar2.txt", type = "file" },
|
||||
},
|
||||
},
|
||||
{ name = "foo1.lua", type = "file" },
|
||||
},
|
||||
},
|
||||
{ name = "baz", type = "dir" },
|
||||
{ name = "1.md", type = "file" },
|
||||
},
|
||||
})
|
||||
|
||||
test.setup()
|
||||
|
||||
local fs_tree = test.fs_tree
|
||||
|
||||
after_each(function()
|
||||
if req_switch then
|
||||
req_switch.restore()
|
||||
end
|
||||
|
||||
u.clear_environment()
|
||||
end)
|
||||
|
||||
describe("w/ default_config", function()
|
||||
before_each(function()
|
||||
require("neo-tree").setup({})
|
||||
end)
|
||||
|
||||
it("works w/o nvim-web-devicons", function()
|
||||
req_switch.disable_package("nvim-web-devicons")
|
||||
|
||||
vim.cmd([[:Neotree focus]])
|
||||
u.wait_for(function()
|
||||
return vim.bo.filetype == "neo-tree"
|
||||
end)
|
||||
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local bufnr = vim.api.nvim_win_get_buf(winid)
|
||||
|
||||
u.assert_buf_lines(bufnr, {
|
||||
string.format(" %s", fs_tree.abspath):sub(1, 42),
|
||||
" baz",
|
||||
" foo",
|
||||
" * 1.md",
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 0 })
|
||||
u.feedkeys("<CR>")
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 3, 0 })
|
||||
u.feedkeys("<CR>")
|
||||
|
||||
vim.wait(100)
|
||||
|
||||
u.assert_buf_lines(bufnr, {
|
||||
string.format(" %s", fs_tree.abspath):sub(1, 42),
|
||||
" ﰊ baz",
|
||||
" foo",
|
||||
" │ bar",
|
||||
" └ * foo1.lua",
|
||||
" * 1.md",
|
||||
})
|
||||
|
||||
u.assert_highlight(bufnr, ns_id, 1, " ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 2, "ﰊ ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 4, " ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 5, "* ", "NeoTreeFileIcon")
|
||||
end)
|
||||
|
||||
it("works w/ nvim-web-devicons", function()
|
||||
vim.cmd([[:Neotree focus]])
|
||||
u.wait_for(function()
|
||||
return vim.bo.filetype == "neo-tree"
|
||||
end)
|
||||
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local bufnr = vim.api.nvim_win_get_buf(winid)
|
||||
|
||||
u.assert_buf_lines(bufnr, {
|
||||
vim.fn.strcharpart(string.format(" %s", fs_tree.abspath), 0, 40),
|
||||
" baz",
|
||||
" foo",
|
||||
" 1.md",
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 0 })
|
||||
u.feedkeys("<CR>")
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 3, 0 })
|
||||
u.feedkeys("<CR>")
|
||||
|
||||
vim.wait(100)
|
||||
|
||||
u.assert_buf_lines(bufnr, {
|
||||
vim.fn.strcharpart(string.format(" %s", fs_tree.abspath), 0, 40),
|
||||
" ﰊ baz",
|
||||
" foo",
|
||||
" │ bar",
|
||||
" └ foo1.lua",
|
||||
" 1.md",
|
||||
})
|
||||
|
||||
u.assert_highlight(bufnr, ns_id, 1, " ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 2, "ﰊ ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 4, " ", "NeoTreeDirectoryIcon")
|
||||
|
||||
local extmarks = u.get_text_extmarks(bufnr, ns_id, 5, " ")
|
||||
u.eq(#extmarks, 1)
|
||||
u.neq(extmarks[1][4].hl_group, "NeoTreeFileIcon")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("custom config", function()
|
||||
local config
|
||||
before_each(function()
|
||||
config = {
|
||||
default_component_configs = {
|
||||
icon = {
|
||||
folder_closed = "c",
|
||||
folder_open = "o",
|
||||
folder_empty = "e",
|
||||
default = "f",
|
||||
highlight = "TestNeoTreeFileIcon",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require("neo-tree").setup(config)
|
||||
end)
|
||||
|
||||
it("works w/o nvim-web-devicons", function()
|
||||
req_switch.disable_package("nvim-web-devicons")
|
||||
|
||||
vim.cmd([[:Neotree focus]])
|
||||
u.wait_for(function()
|
||||
return vim.bo.filetype == "neo-tree"
|
||||
end)
|
||||
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local bufnr = vim.api.nvim_win_get_buf(winid)
|
||||
|
||||
u.assert_buf_lines(bufnr, {
|
||||
string.format(" o %s", fs_tree.abspath):sub(1, 40),
|
||||
" c baz",
|
||||
" c foo",
|
||||
" f 1.md",
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 0 })
|
||||
u.feedkeys("<CR>")
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 3, 0 })
|
||||
u.feedkeys("<CR>")
|
||||
|
||||
vim.wait(100)
|
||||
|
||||
u.assert_buf_lines(bufnr, {
|
||||
string.format(" o %s", fs_tree.abspath):sub(1, 40),
|
||||
" e baz",
|
||||
" o foo",
|
||||
" │ c bar",
|
||||
" └ f foo1.lua",
|
||||
" f 1.md",
|
||||
})
|
||||
|
||||
u.assert_highlight(bufnr, ns_id, 1, "o ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 2, "e ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 4, "c ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 5, "f ", config.default_component_configs.icon.highlight)
|
||||
end)
|
||||
|
||||
it("works w/ nvim-web-devicons", function()
|
||||
vim.cmd([[:Neotree focus]])
|
||||
u.wait_for(function()
|
||||
return vim.bo.filetype == "neo-tree"
|
||||
end)
|
||||
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local bufnr = vim.api.nvim_win_get_buf(winid)
|
||||
|
||||
u.assert_buf_lines(bufnr, {
|
||||
vim.fn.strcharpart(string.format(" o %s", fs_tree.abspath), 0, 40),
|
||||
" c baz",
|
||||
" c foo",
|
||||
" 1.md",
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 0 })
|
||||
u.feedkeys("<CR>")
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 3, 0 })
|
||||
u.feedkeys("<CR>")
|
||||
|
||||
vim.wait(100)
|
||||
|
||||
u.assert_buf_lines(bufnr, {
|
||||
vim.fn.strcharpart(string.format(" o %s", fs_tree.abspath), 0, 40),
|
||||
" e baz",
|
||||
" o foo",
|
||||
" │ c bar",
|
||||
" └ foo1.lua",
|
||||
" 1.md",
|
||||
})
|
||||
|
||||
u.assert_highlight(bufnr, ns_id, 1, "o ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 2, "e ", "NeoTreeDirectoryIcon")
|
||||
u.assert_highlight(bufnr, ns_id, 4, "c ", "NeoTreeDirectoryIcon")
|
||||
|
||||
local extmarks = u.get_text_extmarks(bufnr, ns_id, 5, " ")
|
||||
u.eq(#extmarks, 1)
|
||||
u.neq(extmarks[1][4].hl_group, config.default_component_configs.icon.highlight)
|
||||
end)
|
||||
end)
|
||||
|
||||
test.teardown()
|
||||
end)
|
94
bundle/neo-tree.nvim/tests/utils/fs.lua
vendored
Normal file
94
bundle/neo-tree.nvim/tests/utils/fs.lua
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
local Path = require("plenary.path")
|
||||
|
||||
local fs = {}
|
||||
|
||||
function fs.create_temp_dir()
|
||||
-- Resolve for two reasons.
|
||||
-- 1. Follow any symlinks which make comparing paths fail. (on macOS, TMPDIR can be under /var which is symlinked to
|
||||
-- /private/var)
|
||||
-- 2. Remove any double separators (on macOS TMPDIR can end in a trailing / which absolute doesn't remove, this should
|
||||
-- be coverted by https://github.com/nvim-lua/plenary.nvim/issues/330).
|
||||
local temp_dir = vim.fn.resolve(
|
||||
Path:new(
|
||||
vim.fn.fnamemodify(vim.fn.tempname(), ":h"),
|
||||
string.format("neo-tree-test-%s", vim.fn.rand())
|
||||
):absolute()
|
||||
)
|
||||
vim.fn.mkdir(temp_dir, "p")
|
||||
return temp_dir
|
||||
end
|
||||
|
||||
function fs.create_dir(path)
|
||||
local abspath = Path:new(path):absolute()
|
||||
vim.fn.mkdir(abspath, "p")
|
||||
end
|
||||
|
||||
function fs.remove_dir(dir, recursive)
|
||||
if vim.fn.isdirectory(dir) == 1 then
|
||||
return vim.fn.delete(dir, recursive and "rf" or "d") == 0
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function fs.write_file(path, content)
|
||||
local abspath = Path:new(path):absolute()
|
||||
fs.create_dir(vim.fn.fnamemodify(abspath, ":h"))
|
||||
vim.fn.writefile(content or {}, abspath)
|
||||
end
|
||||
|
||||
function fs.create_fs_tree(fs_tree)
|
||||
local function create_items(items, basedir, relative_root_path)
|
||||
relative_root_path = relative_root_path or "."
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
local relative_path = relative_root_path .. "/" .. item.name
|
||||
|
||||
-- create lookups
|
||||
fs_tree.lookup[relative_path] = item
|
||||
if item.id then
|
||||
fs_tree.lookup[item.id] = item
|
||||
end
|
||||
|
||||
-- create actual files and directories
|
||||
if item.type == "dir" then
|
||||
item.abspath = Path:new(basedir, item.name):absolute()
|
||||
fs.create_dir(item.abspath)
|
||||
if item.items then
|
||||
create_items(item.items, item.abspath, relative_path)
|
||||
end
|
||||
elseif item.type == "file" then
|
||||
item.abspath = Path:new(basedir, item.name):absolute()
|
||||
fs.write_file(item.abspath)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
create_items(fs_tree.items, fs_tree.abspath)
|
||||
|
||||
return fs_tree
|
||||
end
|
||||
|
||||
function fs.init_test(fs_tree)
|
||||
fs_tree.lookup = {}
|
||||
if not fs_tree.abspath then
|
||||
fs_tree.abspath = fs.create_temp_dir()
|
||||
end
|
||||
|
||||
local function setup()
|
||||
fs.remove_dir(fs_tree.abspath, true)
|
||||
fs.create_fs_tree(fs_tree)
|
||||
vim.cmd("tcd " .. fs_tree.abspath)
|
||||
end
|
||||
|
||||
local function teardown()
|
||||
fs.remove_dir(fs_tree.abspath, true)
|
||||
end
|
||||
|
||||
return {
|
||||
fs_tree = fs_tree,
|
||||
setup = setup,
|
||||
teardown = teardown,
|
||||
}
|
||||
end
|
||||
|
||||
return fs
|
183
bundle/neo-tree.nvim/tests/utils/init.lua
vendored
Normal file
183
bundle/neo-tree.nvim/tests/utils/init.lua
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
local mod = {
|
||||
fs = require("tests.utils.fs"),
|
||||
}
|
||||
|
||||
function mod.clear_environment()
|
||||
-- Create fresh window
|
||||
vim.cmd("top new | wincmd o")
|
||||
local keepbufnr = vim.api.nvim_get_current_buf()
|
||||
-- Clear ALL neo-tree state
|
||||
require("neo-tree.sources.manager")._clear_state()
|
||||
-- Cleanup any remaining buffers
|
||||
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
|
||||
if bufnr ~= keepbufnr then
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end
|
||||
end
|
||||
assert(#vim.api.nvim_tabpage_list_wins(0) == 1, "Failed to properly clear tab")
|
||||
assert(#vim.api.nvim_list_bufs() == 1, "Failed to properly clear buffers")
|
||||
end
|
||||
|
||||
mod.editfile = function(testfile)
|
||||
vim.cmd("e " .. testfile)
|
||||
assert.are.same(
|
||||
vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":p"),
|
||||
vim.fn.fnamemodify(testfile, ":p")
|
||||
)
|
||||
end
|
||||
|
||||
function mod.eq(...)
|
||||
return assert.are.same(...)
|
||||
end
|
||||
|
||||
function mod.neq(...)
|
||||
return assert["not"].are.same(...)
|
||||
end
|
||||
|
||||
---@param keys string
|
||||
---@param mode? string
|
||||
function mod.feedkeys(keys, mode)
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(keys, true, false, true), mode or "x", true)
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param keys string[]
|
||||
function mod.tbl_pick(tbl, keys)
|
||||
if not keys or #keys == 0 then
|
||||
return tbl
|
||||
end
|
||||
|
||||
local new_tbl = {}
|
||||
for _, key in ipairs(keys) do
|
||||
new_tbl[key] = tbl[key]
|
||||
end
|
||||
return new_tbl
|
||||
end
|
||||
|
||||
local orig_require = _G.require
|
||||
-- can be used to enable/disable package
|
||||
-- for specific tests
|
||||
function mod.get_require_switch()
|
||||
local disabled_packages = {}
|
||||
|
||||
local function fake_require(name)
|
||||
if vim.tbl_contains(disabled_packages, name) then
|
||||
return error("test: package disabled")
|
||||
end
|
||||
|
||||
return orig_require(name)
|
||||
end
|
||||
|
||||
return {
|
||||
disable_package = function(name)
|
||||
_G.require = fake_require
|
||||
package.loaded[name] = nil
|
||||
table.insert(disabled_packages, name)
|
||||
end,
|
||||
enable_package = function(name)
|
||||
_G.require = fake_require
|
||||
disabled_packages = vim.tbl_filter(function(package_name)
|
||||
return package_name ~= name
|
||||
end, disabled_packages)
|
||||
end,
|
||||
restore = function()
|
||||
disabled_packages = {}
|
||||
_G.require = orig_require
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param lines string[]
|
||||
---@param linenr_start? integer (1-indexed)
|
||||
---@param linenr_end? integer (1-indexed, inclusive)
|
||||
function mod.assert_buf_lines(bufnr, lines, linenr_start, linenr_end)
|
||||
mod.eq(
|
||||
vim.api.nvim_buf_get_lines(
|
||||
bufnr,
|
||||
linenr_start and linenr_start - 1 or 0,
|
||||
linenr_end or -1,
|
||||
false
|
||||
),
|
||||
lines
|
||||
)
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param ns_id integer
|
||||
---@param linenr integer (1-indexed)
|
||||
---@param byte_start? integer (0-indexed)
|
||||
---@param byte_end? integer (0-indexed, inclusive)
|
||||
function mod.get_line_extmarks(bufnr, ns_id, linenr, byte_start, byte_end)
|
||||
return vim.api.nvim_buf_get_extmarks(
|
||||
bufnr,
|
||||
ns_id,
|
||||
{ linenr - 1, byte_start or 0 },
|
||||
{ linenr - 1, byte_end and byte_end + 1 or -1 },
|
||||
{ details = true }
|
||||
)
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param ns_id integer
|
||||
---@param linenr integer (1-indexed)
|
||||
---@param text string
|
||||
---@return table[]
|
||||
---@return { byte_start: integer, byte_end: integer } info (byte range: 0-indexed, inclusive)
|
||||
function mod.get_text_extmarks(bufnr, ns_id, linenr, text)
|
||||
local line = vim.api.nvim_buf_get_lines(bufnr, linenr - 1, linenr, false)[1]
|
||||
|
||||
local byte_start = string.find(line, text) -- 1-indexed
|
||||
byte_start = byte_start - 1 -- 0-indexed
|
||||
local byte_end = byte_start + #text - 1 -- inclusive
|
||||
|
||||
local extmarks = vim.api.nvim_buf_get_extmarks(
|
||||
bufnr,
|
||||
ns_id,
|
||||
{ linenr - 1, byte_start },
|
||||
{ linenr - 1, byte_end },
|
||||
{ details = true }
|
||||
)
|
||||
|
||||
return extmarks, { byte_start = byte_start, byte_end = byte_end }
|
||||
end
|
||||
|
||||
---@param extmark table
|
||||
---@param linenr number (1-indexed)
|
||||
---@param text string
|
||||
---@param hl_group string
|
||||
function mod.assert_extmark(extmark, linenr, text, hl_group)
|
||||
mod.eq(extmark[2], linenr - 1)
|
||||
|
||||
if text then
|
||||
local start_col = extmark[3]
|
||||
mod.eq(extmark[4].end_col - start_col, #text)
|
||||
end
|
||||
|
||||
mod.eq(mod.tbl_pick(extmark[4], { "end_row", "hl_group" }), {
|
||||
end_row = linenr - 1,
|
||||
hl_group = hl_group,
|
||||
})
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param ns_id integer
|
||||
---@param linenr integer (1-indexed)
|
||||
---@param text string
|
||||
---@param hl_group string
|
||||
function mod.assert_highlight(bufnr, ns_id, linenr, text, hl_group)
|
||||
local extmarks, info = mod.get_text_extmarks(bufnr, ns_id, linenr, text)
|
||||
|
||||
mod.eq(#extmarks, 1)
|
||||
mod.eq(extmarks[1][3], info.byte_start)
|
||||
mod.assert_extmark(extmarks[1], linenr, text, hl_group)
|
||||
end
|
||||
|
||||
---@param callback fun(): boolean
|
||||
---@param options? { interval?: integer, timeout?: integer }
|
||||
function mod.wait_for(callback, options)
|
||||
options = options or {}
|
||||
vim.wait(options.timeout or 1000, callback, options.interval or 100)
|
||||
end
|
||||
|
||||
return mod
|
106
bundle/neo-tree.nvim/tests/utils/verify.lua
vendored
Normal file
106
bundle/neo-tree.nvim/tests/utils/verify.lua
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
local verify = {}
|
||||
|
||||
verify.eventually = function(timeout, assertfunc, failmsg, ...)
|
||||
local success, args = false, { ... }
|
||||
vim.wait(timeout or 1000, function()
|
||||
success = assertfunc(unpack(args))
|
||||
return success
|
||||
end)
|
||||
assert(success, failmsg)
|
||||
end
|
||||
|
||||
verify.after = function(timeout, assertfunc, failmsg)
|
||||
vim.wait(timeout, function()
|
||||
return false
|
||||
end)
|
||||
assert(assertfunc(), failmsg)
|
||||
end
|
||||
|
||||
verify.bufnr_is = function(bufnr, timeout)
|
||||
verify.eventually(timeout or 500, function()
|
||||
return bufnr == vim.api.nvim_get_current_buf()
|
||||
end, string.format("Current buffer is expected to be '%s' but is not", bufnr))
|
||||
end
|
||||
|
||||
verify.bufnr_is_not = function(bufnr, timeout)
|
||||
verify.eventually(timeout or 500, function()
|
||||
return bufnr ~= vim.api.nvim_get_current_buf()
|
||||
end, string.format("Current buffer is '%s' when expected to not be", bufnr))
|
||||
end
|
||||
|
||||
verify.buf_name_endswith = function(buf_name, timeout)
|
||||
verify.eventually(
|
||||
timeout or 500,
|
||||
function()
|
||||
if buf_name == "" then
|
||||
return true
|
||||
end
|
||||
local n = vim.api.nvim_buf_get_name(0)
|
||||
if n:sub(-#buf_name) == buf_name then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end,
|
||||
string.format("Current buffer name is expected to be end with '%s' but it does not", buf_name)
|
||||
)
|
||||
end
|
||||
|
||||
verify.buf_name_is = function(buf_name, timeout)
|
||||
verify.eventually(timeout or 500, function()
|
||||
return buf_name == vim.api.nvim_buf_get_name(0)
|
||||
end, string.format("Current buffer name is expected to be '%s' but is not", buf_name))
|
||||
end
|
||||
|
||||
verify.tree_focused = function(timeout)
|
||||
verify.eventually(timeout or 1000, function()
|
||||
return vim.api.nvim_buf_get_option(0, "filetype") == "neo-tree"
|
||||
end, "Current buffer is not a 'neo-tree' filetype")
|
||||
end
|
||||
|
||||
verify.tree_node_is = function(source_name, expected_node_id, winid, timeout)
|
||||
verify.eventually(timeout or 500, function()
|
||||
local state = require("neo-tree.sources.manager").get_state(source_name, nil, winid)
|
||||
if not state.tree then
|
||||
return false
|
||||
end
|
||||
local success, node = pcall(state.tree.get_node, state.tree)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
if not node then
|
||||
return false
|
||||
end
|
||||
local node_id = node:get_id()
|
||||
if node_id == expected_node_id then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end, string.format("Tree node '%s' not focused", expected_node_id))
|
||||
end
|
||||
|
||||
verify.filesystem_tree_node_is = function(expected_node_id, winid, timeout)
|
||||
verify.tree_node_is("filesystem", expected_node_id, winid, timeout)
|
||||
end
|
||||
|
||||
verify.buffers_tree_node_is = function(expected_node_id, winid, timeout)
|
||||
verify.tree_node_is("buffers", expected_node_id, winid, timeout)
|
||||
end
|
||||
|
||||
verify.git_status_tree_node_is = function(expected_node_id, winid, timeout)
|
||||
verify.tree_node_is("git_status", expected_node_id, winid, timeout)
|
||||
end
|
||||
|
||||
verify.window_handle_is = function(winid, timeout)
|
||||
verify.eventually(timeout or 500, function()
|
||||
return winid == vim.api.nvim_get_current_win()
|
||||
end, string.format("Current window handle is expected to be '%s' but is not", winid))
|
||||
end
|
||||
|
||||
verify.window_handle_is_not = function(winid, timeout)
|
||||
verify.eventually(timeout or 500, function()
|
||||
return winid ~= vim.api.nvim_get_current_win()
|
||||
end, string.format("Current window handle is not expected to be '%s' but it is", winid))
|
||||
end
|
||||
|
||||
return verify
|
10
bundle/nui.nvim/.codecov.yml
vendored
Normal file
10
bundle/nui.nvim/.codecov.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
only_pulls: true
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
only_pulls: true
|
122
bundle/nui.nvim/.github/workflows/ci.yml
vendored
Normal file
122
bundle/nui.nvim/.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: luacheck
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Get Cache Key
|
||||
id: luver-cache-key
|
||||
env:
|
||||
CI_RUNNER_OS: ${{ runner.os }}
|
||||
CI_SECRETS_CACHE_VERSION: ${{ secrets.CACHE_VERSION }}
|
||||
run: |
|
||||
echo "::set-output name=value::${CI_RUNNER_OS}-luver-${CI_SECRETS_CACHE_VERSION}-$(date -u +%Y-%m-%d)"
|
||||
shell: bash
|
||||
- name: Setup Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.local/share/luver
|
||||
key: ${{ steps.luver-cache-key.outputs.value }}
|
||||
- name: Setup Lua
|
||||
uses: MunifTanjim/luver-action@v1
|
||||
with:
|
||||
default: 5.1.5
|
||||
lua_versions: 5.1.5
|
||||
luarocks_versions: 5.1.5:3.8.0
|
||||
- name: Setup luacheck
|
||||
run: |
|
||||
luarocks install luacheck
|
||||
- name: Lint
|
||||
run: ./scripts/lint.sh --no-cache
|
||||
|
||||
format:
|
||||
name: stylua
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Check Format
|
||||
uses: JohnnyMorganz/stylua-action@1.0.0
|
||||
with:
|
||||
version: 0.13.1
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --color always --check lua/
|
||||
|
||||
test:
|
||||
name: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Get Cache Key
|
||||
id: luver-cache-key
|
||||
env:
|
||||
CI_RUNNER_OS: ${{ runner.os }}
|
||||
CI_SECRETS_CACHE_VERSION: ${{ secrets.CACHE_VERSION }}
|
||||
run: |
|
||||
echo "::set-output name=value::${CI_RUNNER_OS}-luver-${CI_SECRETS_CACHE_VERSION}-$(date -u +%Y-%m-%d)"
|
||||
shell: bash
|
||||
- name: Setup Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.local/share/luver
|
||||
key: ${{ steps.luver-cache-key.outputs.value }}
|
||||
- name: Setup Lua
|
||||
uses: MunifTanjim/luver-action@v1
|
||||
with:
|
||||
default: 5.1.5
|
||||
lua_versions: 5.1.5
|
||||
luarocks_versions: 5.1.5:3.8.0
|
||||
- name: Setup luacov
|
||||
run: |
|
||||
luarocks install luacov
|
||||
- name: Setup Neovim
|
||||
uses: MunifTanjim/setup-neovim-action@v1
|
||||
with:
|
||||
tag: nightly
|
||||
- name: Run Tests
|
||||
run: |
|
||||
nvim --version
|
||||
./scripts/test.sh
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
verbose: true
|
||||
|
||||
release:
|
||||
name: release
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
needs:
|
||||
- lint
|
||||
- format
|
||||
- test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@v3
|
||||
id: release
|
||||
with:
|
||||
release-type: simple
|
||||
package-name: nui.nvim
|
||||
bump-minor-pre-major: true
|
||||
pull-request-title-pattern: "chore: release ${version}"
|
||||
include-v-in-tag: false
|
||||
- name: Trigger Publish
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG_NAME: ${{ steps.release.outputs.tag_name }}
|
||||
run: |
|
||||
gh workflow run --repo ${GITHUB_REPOSITORY} publish.yml -f version=${TAG_NAME}
|
33
bundle/nui.nvim/.github/workflows/publish.yml
vendored
Normal file
33
bundle/nui.nvim/.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-1].[0-9]+.[0-9]+"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version to publish
|
||||
required: false
|
||||
type: string
|
||||
force:
|
||||
description: Force publish
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: LuaRocks Publish
|
||||
uses: MunifTanjim/luarocks-publish-action@v1
|
||||
with:
|
||||
lua_version: 5.1.5
|
||||
luarocks_version: 3.9.1
|
||||
version: ${{ inputs.version }}
|
||||
api_key: ${{ secrets.LUAROCKS_API_KEY }}
|
||||
force: ${{ inputs.force }}
|
5
bundle/nui.nvim/.gitignore
vendored
Normal file
5
bundle/nui.nvim/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.luacheckcache
|
||||
|
||||
luacov.*.out
|
||||
|
||||
.testcache
|
16
bundle/nui.nvim/.luacheckrc
vendored
Normal file
16
bundle/nui.nvim/.luacheckrc
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
cache = ".luacheckcache"
|
||||
-- https://luacheck.readthedocs.io/en/stable/warnings.html
|
||||
ignore = {
|
||||
"211/_.*",
|
||||
"212/_.*",
|
||||
"213/_.*",
|
||||
}
|
||||
include_files = { "*.luacheckrc", "lua/**/*.lua", "tests/**/*.lua" }
|
||||
globals = { "vim" }
|
||||
std = "luajit"
|
||||
|
||||
files["tests/helpers/**/*.lua"] = {
|
||||
read_globals = { "assert", "describe" },
|
||||
}
|
||||
|
||||
-- vim: set filetype=lua :
|
3
bundle/nui.nvim/.luacov
vendored
Normal file
3
bundle/nui.nvim/.luacov
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
include = {
|
||||
"lua%/nui",
|
||||
}
|
6
bundle/nui.nvim/.stylua.toml
vendored
Normal file
6
bundle/nui.nvim/.stylua.toml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
column_width = 120
|
||||
line_endings = "Unix"
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
quote_style = "AutoPreferDouble"
|
||||
no_call_parentheses = false
|
234
bundle/nui.nvim/CHANGELOG.md
vendored
Normal file
234
bundle/nui.nvim/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.0 (2023-05-27)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **line:** change parameter order for methods.
|
||||
* **text:** change parameter order for methods.
|
||||
|
||||
### Features
|
||||
|
||||
* accept multiple keys for keymap ([4998347](https://github.com/MunifTanjim/nui.nvim/commit/4998347f02fde115ad0c023b90be9c5654834635))
|
||||
* add internal utils._.calculate_gap_width ([90d7285](https://github.com/MunifTanjim/nui.nvim/commit/90d7285c182b396c21ba69684068fd7d5d6eba79))
|
||||
* add util to clear namespace for buffer ([6c63bf5](https://github.com/MunifTanjim/nui.nvim/commit/6c63bf5fd51076455d1b04f5c09d9841ab081263))
|
||||
* **bar:** add lower-level `core.add_highlight` function ([20385a6](https://github.com/MunifTanjim/nui.nvim/commit/20385a698e8a5dd98ee7e63f16b700a10b921098))
|
||||
* **bar:** add some lower-level `core.add_*` functions ([5d1ca66](https://github.com/MunifTanjim/nui.nvim/commit/5d1ca66829d8fac9965cd18fcc2cd9aa49ba1ea5))
|
||||
* **bar:** initial implementation ([35758e9](https://github.com/MunifTanjim/nui.nvim/commit/35758e946a64376e0e9625a27469410b3d1f9223))
|
||||
* **bar:** remove module ([0dc148c](https://github.com/MunifTanjim/nui.nvim/commit/0dc148c6ec06577fcf06cbab3b7dac96d48ba6be))
|
||||
* **input:** add component Input ([7307c94](https://github.com/MunifTanjim/nui.nvim/commit/7307c94a7a0954f1c717e094c39772f28bb9d13f))
|
||||
* **input:** move internal default_value and prompt prop ([595a2ea](https://github.com/MunifTanjim/nui.nvim/commit/595a2ea90f7d31bc23386ba67939117d45771035))
|
||||
* **input:** support nui.text for options.prompt ([c380563](https://github.com/MunifTanjim/nui.nvim/commit/c38056355f5a72c7a1a005f7125afd62ed7b2083))
|
||||
* **layout:** add method layout:update ([3f611a6](https://github.com/MunifTanjim/nui.nvim/commit/3f611a674254370d6d73f9a35a3eedbc8d07ee3b))
|
||||
* **layout:** add some util for size and position ([cc3b970](https://github.com/MunifTanjim/nui.nvim/commit/cc3b970c1537de9562f82e5fa6aa126a629242d1))
|
||||
* **layout:** feature guard lua autocmd api usage ([3dc6b89](https://github.com/MunifTanjim/nui.nvim/commit/3dc6b89bda8f1616a2638727eeb75a4a770e1c21))
|
||||
* **layout:** initial implementation ([716c3f9](https://github.com/MunifTanjim/nui.nvim/commit/716c3f9d857b62a086b4f9123d0d34d1b7733922))
|
||||
* **layout:** introduce layout type ([a5fd005](https://github.com/MunifTanjim/nui.nvim/commit/a5fd005263d238d2fbd6ee335e06139645f11fa9))
|
||||
* **layout:** introduce split layout ([042cceb](https://github.com/MunifTanjim/nui.nvim/commit/042cceb497cc4cfa3ae735a5e7bc01b4b6f19ef1))
|
||||
* **layout:** make window transparent ([2d33512](https://github.com/MunifTanjim/nui.nvim/commit/2d33512836cb603d58cb538e716a02c44547c6ab))
|
||||
* **layout:** re-use windows for unchanged split layout box ([71ddaaf](https://github.com/MunifTanjim/nui.nvim/commit/71ddaafadfd8b3c21f809ea26fb4fe2a110bbf54))
|
||||
* **layout:** support :show and :hide for float layout ([f572782](https://github.com/MunifTanjim/nui.nvim/commit/f572782bd8cc6b6b8671566b09146c4c09f20ae2))
|
||||
* **layout:** support container component ([5bc7376](https://github.com/MunifTanjim/nui.nvim/commit/5bc737602e231111c44458c84e3eca05393f355f))
|
||||
* **layout:** support o.grow factor for layout.box ([796dc82](https://github.com/MunifTanjim/nui.nvim/commit/796dc8293be59acc0c0d53b99e7dca7c32db4413))
|
||||
* **layout:** support o.grow for layout.box ([2c6bac9](https://github.com/MunifTanjim/nui.nvim/commit/2c6bac942e3af35eb42165c923c0d3dc60a46430))
|
||||
* **layout:** throw for empty box at init ([6e872b3](https://github.com/MunifTanjim/nui.nvim/commit/6e872b3bf28aaf52fd1b831ea0690aea205d6464))
|
||||
* **layout:** tweak component wire up ([eed888e](https://github.com/MunifTanjim/nui.nvim/commit/eed888e47fa91980ce295dd347b213a102200b5a))
|
||||
* **layout:** use backported autocmd methods ([4e21085](https://github.com/MunifTanjim/nui.nvim/commit/4e21085cd1b6be12da7ff0e7168e9100c51c363a))
|
||||
* **layout:** wire up float layout components ([3aa617d](https://github.com/MunifTanjim/nui.nvim/commit/3aa617d9054e052cde68cd3141db4396af93a9cb))
|
||||
* **line:** accept initial nui.text objects ([2f44cc9](https://github.com/MunifTanjim/nui.nvim/commit/2f44cc941cf1a129b6b6069a7489672b24cf0015))
|
||||
* **line:** accept NuiText object as param for line:append() method ([34fd4bf](https://github.com/MunifTanjim/nui.nvim/commit/34fd4bfde84ff4b735a98dfb3508280d39e520f6))
|
||||
* **line:** add method :width ([80122e5](https://github.com/MunifTanjim/nui.nvim/commit/80122e542fcebc361c4ca585be40a071c6e360be))
|
||||
* **line:** add nui.line block ([1333fd0](https://github.com/MunifTanjim/nui.nvim/commit/1333fd07c57310d1421dcb337a21fbddb20d3c84))
|
||||
* **line:** make ns_id required, update method signature ([5695bde](https://github.com/MunifTanjim/nui.nvim/commit/5695bde7ac9b8bcbb1df237a05169bad99458090))
|
||||
* **line:** support nui.line in method :append ([401a69f](https://github.com/MunifTanjim/nui.nvim/commit/401a69f27ede2d5a9a725f4f45758708d3b72d09))
|
||||
* make components extendable ([c75976e](https://github.com/MunifTanjim/nui.nvim/commit/c75976e823085218aeef4f5312a8c88e7d333358))
|
||||
* **menu:** add component Menu ([dca0630](https://github.com/MunifTanjim/nui.nvim/commit/dca0630b0d7ab5dfcabaa70284c94c2e133b8200))
|
||||
* **menu:** expose .tree ([d12a697](https://github.com/MunifTanjim/nui.nvim/commit/d12a6977846b2fa978bff89b439e509320854e10))
|
||||
* **menu:** improve menu separator implementation ([0e05425](https://github.com/MunifTanjim/nui.nvim/commit/0e0542505369861fc22f6bff3ad2976bdef6d8f3))
|
||||
* **menu:** move internal props ([0373a94](https://github.com/MunifTanjim/nui.nvim/commit/0373a94bc79725726aadcc1e0e26de159e3152e1))
|
||||
* **menu:** pass self to on_change callback ([4438c5e](https://github.com/MunifTanjim/nui.nvim/commit/4438c5e53f8a5c834569309a5d3e276e6bf1abe9))
|
||||
* **menu:** rename method menu:init to menu:new ([2825c3d](https://github.com/MunifTanjim/nui.nvim/commit/2825c3d60438bbed9c04f18f030ab3505249e8a7))
|
||||
* **menu:** simplify automatic width calculation ([54cbaf5](https://github.com/MunifTanjim/nui.nvim/commit/54cbaf5d227cfedbb939d16b405116e4cea6e3fb))
|
||||
* **menu:** support arbritary props for Menu.item ([77cefa6](https://github.com/MunifTanjim/nui.nvim/commit/77cefa67df7f282db20aa9afb5925aff79455dc2))
|
||||
* **menu:** support nui.line for Menu.item ([51cbd0c](https://github.com/MunifTanjim/nui.nvim/commit/51cbd0ccc9410e317a947eea1e99966226a5f8b5))
|
||||
* **menu:** support nui.text as separator char ([3029554](https://github.com/MunifTanjim/nui.nvim/commit/30295541c1bda2ab171ab7b684f790be3fbb60a7))
|
||||
* **menu:** support nui.text for Menu.item ([13e557d](https://github.com/MunifTanjim/nui.nvim/commit/13e557d045b62efc6da87bdcc77503b5adf1db21))
|
||||
* **menu:** support options.on_change ([db06fed](https://github.com/MunifTanjim/nui.nvim/commit/db06feddf324d4c6c8763fe9fca43b9140de84a9))
|
||||
* **object:** add helper functions ([9531977](https://github.com/MunifTanjim/nui.nvim/commit/95319774b31558479c71e9b190870aae7bd8e49f))
|
||||
* **object:** initial implementation ([194837f](https://github.com/MunifTanjim/nui.nvim/commit/194837ffc4a9c77dfcc18809c7e43a02b4d11e03))
|
||||
* **popup:** add method .border:set_highlight ([acb72b1](https://github.com/MunifTanjim/nui.nvim/commit/acb72b150c7fdf01331b08460ec07fe7a81029b6))
|
||||
* **popup:** add method popup:set_layout(config) ([006711f](https://github.com/MunifTanjim/nui.nvim/commit/006711f3ab4e8626f47b2b903759a9ac0e232fbd))
|
||||
* **popup:** add method popup:set_position(position, relative) ([7ea1a6b](https://github.com/MunifTanjim/nui.nvim/commit/7ea1a6b910b1b33fceb33068df8f31e14ccceb1e))
|
||||
* **popup:** add method popup:set_size(size) ([07b6e9a](https://github.com/MunifTanjim/nui.nvim/commit/07b6e9a90b58af8ca9c09f3695aa14603c947b61))
|
||||
* **popup:** add method popup:unmap ([46bbf33](https://github.com/MunifTanjim/nui.nvim/commit/46bbf336e9068c73c4e810cb93c7a1f8197ebbc3))
|
||||
* **popup:** add method popup.border:set_text(...) ([b82a5d3](https://github.com/MunifTanjim/nui.nvim/commit/b82a5d3bda43b5cd9b9dca39b782e86d2735933f))
|
||||
* **popup:** add methods popup:hide() and popup:show() ([b3c706d](https://github.com/MunifTanjim/nui.nvim/commit/b3c706d4c30bd5cd8b05d10a2c8bf8fcd1f5cf7a))
|
||||
* **popup:** add methods popup:on(...) and popup:off(...) ([0eb57a2](https://github.com/MunifTanjim/nui.nvim/commit/0eb57a2bdd565dfad09e4bda4293ef11c17d741f))
|
||||
* **popup:** add option 'focusable' ([8339965](https://github.com/MunifTanjim/nui.nvim/commit/8339965e991ad54e6606ea22213996521701e293))
|
||||
* **popup:** add option padding ([596cd77](https://github.com/MunifTanjim/nui.nvim/commit/596cd77a875eef1c6629e12074139e2ca0033e38))
|
||||
* **popup:** add options 'buf_options' and 'win_options' ([163d99a](https://github.com/MunifTanjim/nui.nvim/commit/163d99a3fb9fe9a630c96bbf6159973885c3caf3))
|
||||
* **popup:** add options.ns_id ([6f165aa](https://github.com/MunifTanjim/nui.nvim/commit/6f165aad4d2ac5a5a279a695ed247ec954cdcc4b))
|
||||
* **popup:** add type annotation for bufnr,winid,ns_id ([c2d1f73](https://github.com/MunifTanjim/nui.nvim/commit/c2d1f73eb0ab02e90e8316a6e70158d2eec72153))
|
||||
* **popup:** add type annotation for win_config ([f8ccc5c](https://github.com/MunifTanjim/nui.nvim/commit/f8ccc5cf8e8aec7ff34572b80e7b5d92b2d07556))
|
||||
* **popup:** allow layout refresh when container size changes ([ee8d315](https://github.com/MunifTanjim/nui.nvim/commit/ee8d315456691fc1d05dcec465d95f8aec902541))
|
||||
* **popup:** change behavior of padding ([9f29df4](https://github.com/MunifTanjim/nui.nvim/commit/9f29df4153da1483ad784a473f5bbcf02fcbbe3b))
|
||||
* **popup:** clear namespace object on unmount ([58e06b0](https://github.com/MunifTanjim/nui.nvim/commit/58e06b0175cf22672d96a522343ec6ce017ba54c))
|
||||
* **popup:** create buffer on initialization ([6b1deda](https://github.com/MunifTanjim/nui.nvim/commit/6b1deda411b96f0d694dead7cc12ab9db1dd9b65))
|
||||
* **popup:** default border.text hl to FloatTitle ([4eaec2a](https://github.com/MunifTanjim/nui.nvim/commit/4eaec2ac66af2ca6ddddd3f665ad0909b90ae36a))
|
||||
* **popup:** feature guard lua autocmd api usage ([6028584](https://github.com/MunifTanjim/nui.nvim/commit/60285847a4df99c2ab0ece3020eb930c81b09c41))
|
||||
* **popup:** improve border highlight implementation ([2f58c40](https://github.com/MunifTanjim/nui.nvim/commit/2f58c406eb9cb5cedf5d82f9ce7c4f6efc418550))
|
||||
* **popup:** improve cleanup ([220d4a4](https://github.com/MunifTanjim/nui.nvim/commit/220d4a4bbaa8f7dc80c0aa37e8377c7c150c6384))
|
||||
* **popup:** merge internal position_meta into position ([bc2fc9c](https://github.com/MunifTanjim/nui.nvim/commit/bc2fc9c1b7b7241c4a0f34597f8ddc11c5f90844))
|
||||
* **popup:** move internal buf_options and win_options ([3148908](https://github.com/MunifTanjim/nui.nvim/commit/31489084b7d3363c9a6f8f4d435374a25f4f636b))
|
||||
* **popup:** move internal loading and mounted state ([4dc3214](https://github.com/MunifTanjim/nui.nvim/commit/4dc321448faaad171f0aab07d092a0e626fb1cbb))
|
||||
* **popup:** move internal position state ([d229bbb](https://github.com/MunifTanjim/nui.nvim/commit/d229bbb4846bd4268ca1f6f67543294237ee0029))
|
||||
* **popup:** move internal position_meta state ([cbbbe90](https://github.com/MunifTanjim/nui.nvim/commit/cbbbe90213091a462e71a1f102e0927ac11bac8a))
|
||||
* **popup:** move internal size prop ([0a2fced](https://github.com/MunifTanjim/nui.nvim/commit/0a2fcedb97673ba478e26a0f59edbca950d15d31))
|
||||
* **popup:** move internal win_enter prop ([f7736c9](https://github.com/MunifTanjim/nui.nvim/commit/f7736c90db395adc276519bd871bcec088bed908))
|
||||
* **popup:** remove automatic cleanup ([06b48cf](https://github.com/MunifTanjim/nui.nvim/commit/06b48cf645971e1e1d598a083b98733890a82302))
|
||||
* **popup:** remove method popup:on(...) ([c24b131](https://github.com/MunifTanjim/nui.nvim/commit/c24b13195c3e8c7c42d64736d96ded0ce7cb13ed))
|
||||
* **popup:** rename method :set_layout to :update_layout ([90f59b0](https://github.com/MunifTanjim/nui.nvim/commit/90f59b035565e549e467fc414f1178d30c074ce9))
|
||||
* **popup:** rename window to popup ([7097509](https://github.com/MunifTanjim/nui.nvim/commit/7097509b4b0fd21d870cea19c51389043c408449))
|
||||
* **popup:** rework border internals ([8a776a2](https://github.com/MunifTanjim/nui.nvim/commit/8a776a2033220b16fa032b9d3590c75a178435ec))
|
||||
* **popup:** simplify border highlight mechanism ([0ec30d9](https://github.com/MunifTanjim/nui.nvim/commit/0ec30d912c473139832d5c0bfd8ccfa675a48974))
|
||||
* **popup:** simplify border.text ([674305a](https://github.com/MunifTanjim/nui.nvim/commit/674305a0cb1df2b2c0f51ade7081235755e93643))
|
||||
* **popup:** support nui.text for simple border ([5f5a2e5](https://github.com/MunifTanjim/nui.nvim/commit/5f5a2e5284a08f930098383a6d0a00b861fc58d2))
|
||||
* **popup:** support NuiText as border.text ([1248c67](https://github.com/MunifTanjim/nui.nvim/commit/1248c674a4a632ea7d45a76b1255df997379e116))
|
||||
* **popup:** support option 'anchor' ([a86c733](https://github.com/MunifTanjim/nui.nvim/commit/a86c733e7d30596d80927b5eaf6869aa9a3d30af))
|
||||
* **popup:** support set_size when unmounted ([fa86f85](https://github.com/MunifTanjim/nui.nvim/commit/fa86f8539373e1b76b93f95dfa36a5793ed4fe35))
|
||||
* **popup:** support unmanaged buffer ([335415a](https://github.com/MunifTanjim/nui.nvim/commit/335415af52ea23d07433aa1f72f7c0d56c219316))
|
||||
* **popup:** use backported autocmd methods ([372369d](https://github.com/MunifTanjim/nui.nvim/commit/372369dea4c059f831e540e34b9a49bf183c245b))
|
||||
* **split:** add component Split ([bcb7382](https://github.com/MunifTanjim/nui.nvim/commit/bcb73828d54169f5ae9d141bca05fecb6aec5ec5))
|
||||
* **split:** add method :update_layout ([32f44a6](https://github.com/MunifTanjim/nui.nvim/commit/32f44a610691ff2e22e60a8040801e047112806f))
|
||||
* **split:** add method split:unmap ([e0444e0](https://github.com/MunifTanjim/nui.nvim/commit/e0444e020fd3ed336e2b6ba998ff7903ddb68ddd))
|
||||
* **split:** create buffer on initialization ([0e36b78](https://github.com/MunifTanjim/nui.nvim/commit/0e36b78b836200ef83ef723c6a0ebb753ac8a11e))
|
||||
* **split:** feature guard lua autocmd api usage ([d14daab](https://github.com/MunifTanjim/nui.nvim/commit/d14daab6710d1ebd7ec868f15bddde2bbd2c186f))
|
||||
* **split:** improve cleanup ([36b0649](https://github.com/MunifTanjim/nui.nvim/commit/36b0649d7df3d1899d5aed8b27d22159cf058a2e))
|
||||
* **split:** move internal buf_options and win_options ([6c9a3ee](https://github.com/MunifTanjim/nui.nvim/commit/6c9a3ee9fdb2c7a48cf13d9c82ee56822c5bfdb8))
|
||||
* **split:** move internal loading and mounted state ([653199c](https://github.com/MunifTanjim/nui.nvim/commit/653199c635ad56c1e313e0890c6a72da8d3ee3bd))
|
||||
* **split:** move internal props ([25e51eb](https://github.com/MunifTanjim/nui.nvim/commit/25e51eba14cdf2d445d0c0efde78d808741483cb))
|
||||
* **split:** set buffer after .winid is set ([35091ca](https://github.com/MunifTanjim/nui.nvim/commit/35091ca0f8c766ea3542508f3ab9217a746cc5a0))
|
||||
* **split:** store id internally ([96ef1cb](https://github.com/MunifTanjim/nui.nvim/commit/96ef1cb4e3c830dc79e18acac209ea5f7eb78829))
|
||||
* **split:** support o.enter ([b75e2e6](https://github.com/MunifTanjim/nui.nvim/commit/b75e2e6d2a86a1105a756b894385d5fe836c3821))
|
||||
* **split:** support o.ns_id ([28cafab](https://github.com/MunifTanjim/nui.nvim/commit/28cafab82f5d5ffc4321b9f82bae7b38951434d1))
|
||||
* **split:** support o.relative.winid ([82851af](https://github.com/MunifTanjim/nui.nvim/commit/82851af021d651bb6bf6da0991c79f4c529ff37e))
|
||||
* **split:** tweak split size handling for new window ([b681ab2](https://github.com/MunifTanjim/nui.nvim/commit/b681ab2a8a8750b37dcece4e87105a5671c39745))
|
||||
* **split:** use backported autocmd methods ([6bd1d8a](https://github.com/MunifTanjim/nui.nvim/commit/6bd1d8af326c654a8b9d122c32a62fed5e55fe89))
|
||||
* **text:** add method :new ([4ad7811](https://github.com/MunifTanjim/nui.nvim/commit/4ad781109a44bc00fa7d1208576f4086add27ad3))
|
||||
* **text:** add method text:set(content, highlight?) ([eaf4844](https://github.com/MunifTanjim/nui.nvim/commit/eaf4844e9e84994705acc0347673a6b15e7c030f))
|
||||
* **text:** add nui.text block ([a6df800](https://github.com/MunifTanjim/nui.nvim/commit/a6df800df514c2f0a9dd88d8db267ec5b8c8d68d))
|
||||
* **text:** change highlight table key group->hl_group ([a8aaca1](https://github.com/MunifTanjim/nui.nvim/commit/a8aaca1578dff3504e78538b477910de344af0f8))
|
||||
* **text:** make ns_id required, update method signature ([b63d199](https://github.com/MunifTanjim/nui.nvim/commit/b63d199ddfdc89457ae401ed0a5659fa1055c37c))
|
||||
* **text:** preserve own extmark id ([26622d1](https://github.com/MunifTanjim/nui.nvim/commit/26622d147762f2212bf30e0792df1d0164a73cd9))
|
||||
* **text:** remove method :new ([18a0390](https://github.com/MunifTanjim/nui.nvim/commit/18a0390d5caca31b310af41787e5ddd407c68783))
|
||||
* **text:** return self from method text:set ([c5971ed](https://github.com/MunifTanjim/nui.nvim/commit/c5971ed28773cf9b6ce4ed8bfbc20266aa05b2a1))
|
||||
* **text:** support cloning and use extmarks ([281d453](https://github.com/MunifTanjim/nui.nvim/commit/281d4535d046b1dd09d45bca8b0739bb16461b75))
|
||||
* **text:** support extmark override when cloning ([878dfaf](https://github.com/MunifTanjim/nui.nvim/commit/878dfaf85fa19ff6811f19f1dad0bbfda77d5bbf))
|
||||
* **tree:** add method node:get_child_ids ([bc05620](https://github.com/MunifTanjim/nui.nvim/commit/bc056204ff06b205dd3804474fc155180e704d47))
|
||||
* **tree:** add method tree:get_nodes ([f85aedc](https://github.com/MunifTanjim/nui.nvim/commit/f85aedc1378acb375e4da0ec2220c1df0929e843))
|
||||
* **tree:** add method tree:set_nodes(nodes, parent_id?) ([b25fab5](https://github.com/MunifTanjim/nui.nvim/commit/b25fab59d997cd9793bf7f42a1e41b9b7684d987))
|
||||
* **tree:** add nui.tree block ([3bdfa78](https://github.com/MunifTanjim/nui.nvim/commit/3bdfa780fdba053358b85fd1f276052ae1455b61))
|
||||
* **tree:** add o.bufnr and deprecate o.winid ([ce4869f](https://github.com/MunifTanjim/nui.nvim/commit/ce4869f97e4f3d4f9eb64d021fff775684ae8859))
|
||||
* **tree:** clear namespace before render ([1f66cc7](https://github.com/MunifTanjim/nui.nvim/commit/1f66cc794bb6cad23f97b71e099cc32dd63c0614))
|
||||
* **tree:** make node:has_children method work before init ([96f600b](https://github.com/MunifTanjim/nui.nvim/commit/96f600b58f128bde4a78a56c0a64d55664a43955))
|
||||
* **tree:** move internal buf_options and win_options ([3a7c0c4](https://github.com/MunifTanjim/nui.nvim/commit/3a7c0c48b279ee0495c5ec61ca312fddd052195c))
|
||||
* **tree:** move internal get_node_id and prepare_node functions ([1b9e046](https://github.com/MunifTanjim/nui.nvim/commit/1b9e04685097b93a4f8d01002adaaa5dcfb327af))
|
||||
* **tree:** pass parent_node to prepare_node function ([559d33d](https://github.com/MunifTanjim/nui.nvim/commit/559d33dcace8016603129e63e5cb605aaa059c10))
|
||||
* **tree:** rename options.ns to options.ns_id ([5db3901](https://github.com/MunifTanjim/nui.nvim/commit/5db390110bf9944b678c84cd7bcd2a28af712481))
|
||||
* **tree:** return end linenr from tree:get_node method ([8ae5e31](https://github.com/MunifTanjim/nui.nvim/commit/8ae5e3106a0fa17144a0a086ccfce1e73a73f19f))
|
||||
* **tree:** return linenr from tree:get_node method ([3b746d7](https://github.com/MunifTanjim/nui.nvim/commit/3b746d7b6f16818a970d1c4810261d18b958a956))
|
||||
* **tree:** support linenr for method tree:get_node ([7fee7c6](https://github.com/MunifTanjim/nui.nvim/commit/7fee7c6c176e83806a144f7e0a8ac6251920a8a7))
|
||||
* **tree:** support linenr_start for method :render ([afb9e5b](https://github.com/MunifTanjim/nui.nvim/commit/afb9e5b4512e17d879fb069e77de4141472d0fb9))
|
||||
* **tree:** support multiline node ([4926ee9](https://github.com/MunifTanjim/nui.nvim/commit/4926ee9ba8fac49ad23cd695f1f5c0952c52dd4a))
|
||||
* **tree:** support nil return for o.prepare_node ([5a79b1b](https://github.com/MunifTanjim/nui.nvim/commit/5a79b1b3b8231cfa33290d8d3c56d36b6496499e))
|
||||
* use api-autocmd if available ([3f05d74](https://github.com/MunifTanjim/nui.nvim/commit/3f05d742b273ed4b48db4e5ab99c09b8b05cd537))
|
||||
* use native keymap callback if supported ([ad2c05c](https://github.com/MunifTanjim/nui.nvim/commit/ad2c05c983dda8d423d952fce55eb3cf3966a1ea))
|
||||
* **utils:** add autocmd ([1f51d5a](https://github.com/MunifTanjim/nui.nvim/commit/1f51d5a6735153ea5e3f9a1f68a12c9f1bce5681))
|
||||
* **utils:** add buf_storage ([6300e3b](https://github.com/MunifTanjim/nui.nvim/commit/6300e3bdcc2fc38e361a48b76247c4524a0fb27a))
|
||||
* **utils:** backport autocmd to nvim < 0.7.x ([587a49f](https://github.com/MunifTanjim/nui.nvim/commit/587a49f90fb036a6d94904851dafdd53d1327fe0))
|
||||
* **utils:** move keymap to utils ([56c2230](https://github.com/MunifTanjim/nui.nvim/commit/56c223041ad342b2a8d28d2bb1dbd88dc0d7839a))
|
||||
* **utils:** update autocmd.event ([6f9153c](https://github.com/MunifTanjim/nui.nvim/commit/6f9153cc8462a3bb0a8a883694befa6554d31403))
|
||||
* **window:** add method window:destroy() ([c51856d](https://github.com/MunifTanjim/nui.nvim/commit/c51856da53df1327a406e7983d5fd748486fc339))
|
||||
* **window:** add method window:map(...) ([f69ee04](https://github.com/MunifTanjim/nui.nvim/commit/f69ee0498ecc57268d23a4b51516002404c3def1))
|
||||
* **window:** add method window:on(event_name, handler) ([f36b496](https://github.com/MunifTanjim/nui.nvim/commit/f36b496b52aec95dc9e122830b0d2ff64eeb21f5))
|
||||
* **window:** add method window:render() ([d1a047d](https://github.com/MunifTanjim/nui.nvim/commit/d1a047d22d0794943a08a2de598ac57c61fc4978))
|
||||
* **window:** add option highlight ([c4bbe61](https://github.com/MunifTanjim/nui.nvim/commit/c4bbe6139f012aca971ea26721dc4c1942b71286))
|
||||
* **window:** change default border to none ([8db2faa](https://github.com/MunifTanjim/nui.nvim/commit/8db2faa3dfa9b196fe2fd11311c534b37db2428a))
|
||||
* **window:** enhanced border support ([a17070c](https://github.com/MunifTanjim/nui.nvim/commit/a17070c6b55ba36db82edef0c3af28ac0929e649))
|
||||
* **window:** initial implementation ([19e4bb6](https://github.com/MunifTanjim/nui.nvim/commit/19e4bb669d2fdd168a6d0a3edebaca150f93678f))
|
||||
* **window:** rename 'destroy' to 'unmount' ([c409518](https://github.com/MunifTanjim/nui.nvim/commit/c409518e6640e50dc754b199cd37246f60a3fdbc))
|
||||
* **window:** rename 'render' to 'mount' ([66190d2](https://github.com/MunifTanjim/nui.nvim/commit/66190d237fc61db2135c0c19eb9091091d9090dc))
|
||||
* **window:** simplify option relative ([5584892](https://github.com/MunifTanjim/nui.nvim/commit/55848921914083d57e178448f3714f3ba4d9fc75))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bar:** rename tabnr to tabid in context ([f220495](https://github.com/MunifTanjim/nui.nvim/commit/f2204952ba670372de507bdd446c6a14f821ac73))
|
||||
* **bar:** type for generator ([2a6533f](https://github.com/MunifTanjim/nui.nvim/commit/2a6533fb798efad7dd783311315bab8dc5eb381b))
|
||||
* **input:** escape multi-byte chars in default value ([698e758](https://github.com/MunifTanjim/nui.nvim/commit/698e75814cd7c56b0dd8af4936bcef2d13807f3c))
|
||||
* **input:** stopinsert on close ([971cca4](https://github.com/MunifTanjim/nui.nvim/commit/971cca41914aaefc9569b8a1904cf8c25cec5aa8))
|
||||
* **input:** try to keep stable cursor position on parent window ([6f803e8](https://github.com/MunifTanjim/nui.nvim/commit/6f803e88093573f73d4ee6c0dfe0575df3f97a9f))
|
||||
* **layout:** apply size for first child box in split layout ([dde3f89](https://github.com/MunifTanjim/nui.nvim/commit/dde3f89b74b726eacfa6bea82c895af265573fe4))
|
||||
* **layout:** float position calculation for child with complex border ([257da38](https://github.com/MunifTanjim/nui.nvim/commit/257da38029d3859ed111804f9d4e95b0fa993a31))
|
||||
* **layout:** focus on relative.win for split ':update' ([6e8f9a0](https://github.com/MunifTanjim/nui.nvim/commit/6e8f9a0f280fada3f8ce694a42f8376a31e9776e))
|
||||
* **layout:** o.relative for split layout ([bf5900f](https://github.com/MunifTanjim/nui.nvim/commit/bf5900f1b60bf6499755ac92315181a24a87a577))
|
||||
* **layout:** preserve split win_options 'winfixheight' and 'winfixwidth' ([ecd9def](https://github.com/MunifTanjim/nui.nvim/commit/ecd9def93891b9260b15b5fcef542eaabf4145c9))
|
||||
* **layout:** process float layout box change ([51721a4](https://github.com/MunifTanjim/nui.nvim/commit/51721a409794bf2e432e99acc21b635102fedcea))
|
||||
* **layout:** process split layout box change ([b12db53](https://github.com/MunifTanjim/nui.nvim/commit/b12db5321c194c10eb34e610fb76ce2c058853fc))
|
||||
* **layout:** typo in update_layout_config util ([d5d3d6c](https://github.com/MunifTanjim/nui.nvim/commit/d5d3d6ce542b3921f8a8da213ef7b0841f6a4adc))
|
||||
* luacheck lint warnings ([e7dd31c](https://github.com/MunifTanjim/nui.nvim/commit/e7dd31c4135389e460a686d48d10ed532ca5234e))
|
||||
* **menu:** error with fallback separator char ([76fc8ed](https://github.com/MunifTanjim/nui.nvim/commit/76fc8edf771adff74aba513bd0f62e984996ef88))
|
||||
* **popup:** add border.style default value ([a07b754](https://github.com/MunifTanjim/nui.nvim/commit/a07b754552008012f2d7d3602b7a233a29d92c66))
|
||||
* **popup:** border highlight for type:complex ([151d593](https://github.com/MunifTanjim/nui.nvim/commit/151d593b28911d61b10e1a8ba14f9e4c755141aa))
|
||||
* **popup:** border padding with style=shadow ([37e0511](https://github.com/MunifTanjim/nui.nvim/commit/37e0511f189cd19eabd0e71841f10c3a39bbb62d))
|
||||
* **popup:** check bufnr is valid before clearing namespace ([62facd3](https://github.com/MunifTanjim/nui.nvim/commit/62facd37e0dd8196212399a897374f689886f500))
|
||||
* **popup:** check if border bufnr is valid before clearing namespace ([b99e6cb](https://github.com/MunifTanjim/nui.nvim/commit/b99e6cb13dc51768abc1c4c8585045a0c0459ef1))
|
||||
* **popup:** do better mount/unmount handling ([7622fcf](https://github.com/MunifTanjim/nui.nvim/commit/7622fcf3dc4cffffc666d9f1f4e646168f640a2a))
|
||||
* **popup:** do buf_storage.cleanup after buffer wipeout ([644e595](https://github.com/MunifTanjim/nui.nvim/commit/644e595a3862ca45de71ff14fa465019ecfc17ad))
|
||||
* **popup:** do BufWinEnter autocmd after self.winid is set ([4c77e3a](https://github.com/MunifTanjim/nui.nvim/commit/4c77e3a064b7b0fdfb5f2729500a81b431ff86f8))
|
||||
* **popup:** do not reset win_options on update_layout call ([1f43b13](https://github.com/MunifTanjim/nui.nvim/commit/1f43b13d133eb4b4f53a4485379d9afa58808389))
|
||||
* **popup:** handle border padding without text ([1f9aebc](https://github.com/MunifTanjim/nui.nvim/commit/1f9aebcaca1f43c311ba16a2aad9170d597640a8))
|
||||
* **popup:** handle border:set_text properly ([e78c822](https://github.com/MunifTanjim/nui.nvim/commit/e78c822378c102978792465dbf93b8ffb27a4cc8))
|
||||
* **popup:** handle map as border.style ([26a0eea](https://github.com/MunifTanjim/nui.nvim/commit/26a0eeaca8890b74d53a91f84520abea94fcf8ee))
|
||||
* **popup:** handle various mix of border and padding ([49a155f](https://github.com/MunifTanjim/nui.nvim/commit/49a155f3bbacb90a8b2df320f5b127b568083cd6))
|
||||
* **popup:** highlight for simple border ([3ff3d26](https://github.com/MunifTanjim/nui.nvim/commit/3ff3d2628f3c14633792f702b3cfea377addcf47))
|
||||
* **popup:** ignore WinClosed from other popup ([a501202](https://github.com/MunifTanjim/nui.nvim/commit/a5012020a48f5740bf30bb3468ca532cb7627997))
|
||||
* **popup:** manual doautocmd BufWinEnter ([0807a9c](https://github.com/MunifTanjim/nui.nvim/commit/0807a9ca3274d5f89d02802aa00dac9fc2649864))
|
||||
* **popup:** position relative to buffer position ([f369333](https://github.com/MunifTanjim/nui.nvim/commit/f36933397a0689a96106d9aa74db320286f5ffda))
|
||||
* **popup:** properly apply winhighlight ([4396e44](https://github.com/MunifTanjim/nui.nvim/commit/4396e4427dc7ec80c58575096fdcad46d336a7ac))
|
||||
* **popup:** remove mutation in border:get() ([1179f2e](https://github.com/MunifTanjim/nui.nvim/commit/1179f2e3f5245ab585e1154478f45bab1cf41867))
|
||||
* **popup:** respect 'enter' option on subsequent mounts ([602e4d8](https://github.com/MunifTanjim/nui.nvim/commit/602e4d885fda5da9f015345fecb8c745ffedbf52))
|
||||
* **popup:** set noautocmd for opening border window ([4715f60](https://github.com/MunifTanjim/nui.nvim/commit/4715f6092443f0b8fb9a3bcb0cfd03202bb03477))
|
||||
* **popup:** set win_config.win explicitly if applicable ([d50d84a](https://github.com/MunifTanjim/nui.nvim/commit/d50d84a340a3088b8560bf74fa3d6f6ad0556134))
|
||||
* **popup:** store winid for parent window ([03131f8](https://github.com/MunifTanjim/nui.nvim/commit/03131f8aa1873d08009ca214727a2c699d73dfe6))
|
||||
* **popup:** take border size_delta into account when calculating position ([e67310b](https://github.com/MunifTanjim/nui.nvim/commit/e67310b23d21ebe8b12d9dbadb3dfa562dda5057))
|
||||
* **popup:** track mounted state ([c1db7f8](https://github.com/MunifTanjim/nui.nvim/commit/c1db7f8377dfce4f724f35eec74868a15443dccf))
|
||||
* **popup:** update position handling ([d87b561](https://github.com/MunifTanjim/nui.nvim/commit/d87b56193991e102516689f394101d1367bf8ef5))
|
||||
* **popup:** update state and buffer handling for hide/show ([02e9262](https://github.com/MunifTanjim/nui.nvim/commit/02e9262d2d1820c2ed573f3a09848affc6526704))
|
||||
* **popup:** use copy of border styles table item ([abd1a4a](https://github.com/MunifTanjim/nui.nvim/commit/abd1a4a23d0bbc0d0d4bc8faaa240330b44a4d5c))
|
||||
* **popup:** use popup winhighlight for border ([cf67636](https://github.com/MunifTanjim/nui.nvim/commit/cf676363181aae149271226531abca341e583997))
|
||||
* **split:** check bufnr is valid before clearing namespace ([e9889bb](https://github.com/MunifTanjim/nui.nvim/commit/e9889bbd9919544697d497537acacd9c67d0de99))
|
||||
* **split:** do buf_storage.cleanup after buffer wipeout ([5bf5d62](https://github.com/MunifTanjim/nui.nvim/commit/5bf5d62531b473b01cae65ea389d4f788fb2341f))
|
||||
* **split:** do not update position when unchanged ([6d86148](https://github.com/MunifTanjim/nui.nvim/commit/6d86148ad43554f51caa82e99d63a57be1654182))
|
||||
* **split:** set size after open window ([c45ad68](https://github.com/MunifTanjim/nui.nvim/commit/c45ad685cb156174761f912c7697fdb6fd5a3b50))
|
||||
* **split:** skip manual buf/win removal when pending quit ([cc76e6f](https://github.com/MunifTanjim/nui.nvim/commit/cc76e6ff13629b18d3dedfadd4f52e35ff085700))
|
||||
* **split:** tweak container size relative to editor ([120fe69](https://github.com/MunifTanjim/nui.nvim/commit/120fe69bc4d96a13f89c2450ceb27fcd921b77ae))
|
||||
* **split:** use self.winid for WinClosed ([747c20d](https://github.com/MunifTanjim/nui.nvim/commit/747c20d10a42f22a0125ee2e7397aee4c2422ffe))
|
||||
* support nui.text for internal alignment util ([915fabe](https://github.com/MunifTanjim/nui.nvim/commit/915fabe334639c384ed5c66005046b96d4e8d30a))
|
||||
* **text:** fix :highlight extmark.end_col ([3775746](https://github.com/MunifTanjim/nui.nvim/commit/3775746f8324db1ff5068eaf10f321db5e566611))
|
||||
* **tree:** fix calculation for method :render ([70f2dad](https://github.com/MunifTanjim/nui.nvim/commit/70f2dadb73b5aa15727ec8f7a620818997505be5))
|
||||
* **tree:** pass ns_id correctly to nui.line in :render method ([70fc6b6](https://github.com/MunifTanjim/nui.nvim/commit/70fc6b66c651538a78d393cf9fc830c81e919ca8))
|
||||
* **tree:** remove children recursively in method remove_node ([4939282](https://github.com/MunifTanjim/nui.nvim/commit/4939282919885e1c83aff68ecb35b3cadf6015a9))
|
||||
* **tree:** remove id from .nodes.root_ids on tree:remove_node ([792caa3](https://github.com/MunifTanjim/nui.nvim/commit/792caa3c1dc3efb22385b3b363c32567ba9cb059))
|
||||
* **tree:** set default buf_options.bufhidden=hide ([7c7bdf4](https://github.com/MunifTanjim/nui.nvim/commit/7c7bdf40219bc274a849fa241daa47872c809fd8))
|
||||
* **tree:** set default buf_options.undolevels=0 ([42552b3](https://github.com/MunifTanjim/nui.nvim/commit/42552b3797c3452c5c94e0c84a04fbda9591b9d1))
|
||||
* vim.schedule QuitPre event callback ([d147222](https://github.com/MunifTanjim/nui.nvim/commit/d147222a1300901656f3ebd5b95f91732785a329))
|
||||
* **window:** cleanup properly ([dbc8185](https://github.com/MunifTanjim/nui.nvim/commit/dbc81850faeeb70c303cdf3c50e0a4b6bbf154bd))
|
||||
* **window:** set zindex properly ([2d427f7](https://github.com/MunifTanjim/nui.nvim/commit/2d427f7f9bac670523de2906776117192f243d8c))
|
||||
* **window:** use 0-indexed position ([fbf96df](https://github.com/MunifTanjim/nui.nvim/commit/fbf96df95e437687206b9213924372c13c328b7a))
|
||||
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
* introduce automated release ([70560c4](https://github.com/MunifTanjim/nui.nvim/commit/70560c4e7b36ff974e7136d08aa4022e79a002f4))
|
21
bundle/nui.nvim/LICENSE
vendored
Normal file
21
bundle/nui.nvim/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Munif Tanjim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
345
bundle/nui.nvim/README.md
vendored
Normal file
345
bundle/nui.nvim/README.md
vendored
Normal file
@ -0,0 +1,345 @@
|
||||

|
||||
[](https://codecov.io/gh/MunifTanjim/nui.nvim)
|
||||
[](https://luarocks.org/modules/MunifTanjim/nui.nvim)
|
||||

|
||||
|
||||
# nui.nvim
|
||||
|
||||
UI Component Library for Neovim.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Neovim 0.5.0](https://github.com/neovim/neovim/releases/tag/v0.5.0)
|
||||
|
||||
## Installation
|
||||
|
||||
Install the plugins with your preferred plugin manager. For example, with [`vim-plug`](https://github.com/junegunn/vim-plug):
|
||||
|
||||
```vim
|
||||
Plug 'MunifTanjim/nui.nvim'
|
||||
```
|
||||
|
||||
## Blocks
|
||||
|
||||
### [NuiText](lua/nui/text)
|
||||
|
||||
Quickly add highlighted text on the buffer.
|
||||
|
||||
**[Check Detailed Documentation for `nui.text`](lua/nui/text)**
|
||||
|
||||
**[Check Wiki Page for `nui.text`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.text)**
|
||||
|
||||
### [NuiLine](lua/nui/line)
|
||||
|
||||
Quickly add line containing highlighted text chunks on the buffer.
|
||||
|
||||
**[Check Detailed Documentation for `nui.line`](lua/nui/line)**
|
||||
|
||||
**[Check Wiki Page for `nui.line`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.line)**
|
||||
|
||||
### [NuiTree](lua/nui/tree)
|
||||
|
||||
Quickly render tree-like structured content on the buffer.
|
||||
|
||||
**[Check Detailed Documentation for `nui.tree`](lua/nui/tree)**
|
||||
|
||||
**[Check Wiki Page for `nui.tree`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.tree)**
|
||||
|
||||
## Components
|
||||
|
||||
### [Layout](lua/nui/layout)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Popup = require("nui.popup")
|
||||
local Layout = require("nui.layout")
|
||||
|
||||
local popup_one, popup_two = Popup({
|
||||
enter = true,
|
||||
border = "single",
|
||||
}), Popup({
|
||||
border = "double",
|
||||
})
|
||||
|
||||
local layout = Layout(
|
||||
{
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 80,
|
||||
height = "60%",
|
||||
},
|
||||
},
|
||||
Layout.Box({
|
||||
Layout.Box(popup_one, { size = "40%" }),
|
||||
Layout.Box(popup_two, { size = "60%" }),
|
||||
}, { dir = "row" })
|
||||
)
|
||||
|
||||
local current_dir = "row"
|
||||
|
||||
popup_one:map("n", "r", function()
|
||||
if current_dir == "col" then
|
||||
layout:update(Layout.Box({
|
||||
Layout.Box(popup_one, { size = "40%" }),
|
||||
Layout.Box(popup_two, { size = "60%" }),
|
||||
}, { dir = "row" }))
|
||||
|
||||
current_dir = "row"
|
||||
else
|
||||
layout:update(Layout.Box({
|
||||
Layout.Box(popup_two, { size = "60%" }),
|
||||
Layout.Box(popup_one, { size = "40%" }),
|
||||
}, { dir = "col" }))
|
||||
|
||||
current_dir = "col"
|
||||
end
|
||||
end, {})
|
||||
|
||||
layout:mount()
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.layout`](lua/nui/layout)**
|
||||
|
||||
**[Check Wiki Page for `nui.layout`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.layout)**
|
||||
|
||||
### [Popup](lua/nui/popup)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Popup = require("nui.popup")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local popup = Popup({
|
||||
enter = true,
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
},
|
||||
position = "50%",
|
||||
size = {
|
||||
width = "80%",
|
||||
height = "60%",
|
||||
},
|
||||
})
|
||||
|
||||
-- mount/open the component
|
||||
popup:mount()
|
||||
|
||||
-- unmount component when cursor leaves buffer
|
||||
popup:on(event.BufLeave, function()
|
||||
popup:unmount()
|
||||
end)
|
||||
|
||||
-- set content
|
||||
vim.api.nvim_buf_set_lines(popup.bufnr, 0, 1, false, { "Hello World" })
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.popup`](lua/nui/popup)**
|
||||
|
||||
**[Check Wiki Page for `nui.popup`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.popup)**
|
||||
|
||||
### [Input](lua/nui/input)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Input = require("nui.input")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local input = Input({
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 20,
|
||||
},
|
||||
border = {
|
||||
style = "single",
|
||||
text = {
|
||||
top = "[Howdy?]",
|
||||
top_align = "center",
|
||||
},
|
||||
},
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal,FloatBorder:Normal",
|
||||
},
|
||||
}, {
|
||||
prompt = "> ",
|
||||
default_value = "Hello",
|
||||
on_close = function()
|
||||
print("Input Closed!")
|
||||
end,
|
||||
on_submit = function(value)
|
||||
print("Input Submitted: " .. value)
|
||||
end,
|
||||
})
|
||||
|
||||
-- mount/open the component
|
||||
input:mount()
|
||||
|
||||
-- unmount component when cursor leaves buffer
|
||||
input:on(event.BufLeave, function()
|
||||
input:unmount()
|
||||
end)
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.input`](lua/nui/input)**
|
||||
|
||||
**[Check Wiki Page for `nui.input`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.input)**
|
||||
|
||||
### [Menu](lua/nui/menu)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Menu = require("nui.menu")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local menu = Menu({
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 25,
|
||||
height = 5,
|
||||
},
|
||||
border = {
|
||||
style = "single",
|
||||
text = {
|
||||
top = "[Choose-an-Element]",
|
||||
top_align = "center",
|
||||
},
|
||||
},
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal,FloatBorder:Normal",
|
||||
},
|
||||
}, {
|
||||
lines = {
|
||||
Menu.item("Hydrogen (H)"),
|
||||
Menu.item("Carbon (C)"),
|
||||
Menu.item("Nitrogen (N)"),
|
||||
Menu.separator("Noble-Gases", {
|
||||
char = "-",
|
||||
text_align = "right",
|
||||
}),
|
||||
Menu.item("Helium (He)"),
|
||||
Menu.item("Neon (Ne)"),
|
||||
Menu.item("Argon (Ar)"),
|
||||
},
|
||||
max_width = 20,
|
||||
keymap = {
|
||||
focus_next = { "j", "<Down>", "<Tab>" },
|
||||
focus_prev = { "k", "<Up>", "<S-Tab>" },
|
||||
close = { "<Esc>", "<C-c>" },
|
||||
submit = { "<CR>", "<Space>" },
|
||||
},
|
||||
on_close = function()
|
||||
print("Menu Closed!")
|
||||
end,
|
||||
on_submit = function(item)
|
||||
print("Menu Submitted: ", item.text)
|
||||
end,
|
||||
})
|
||||
|
||||
-- mount the component
|
||||
menu:mount()
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.menu`](lua/nui/menu)**
|
||||
|
||||
**[Check Wiki Page for `nui.menu`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.menu)**
|
||||
|
||||
### [Split](lua/nui/split)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Split = require("nui.split")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local split = Split({
|
||||
relative = "editor",
|
||||
position = "bottom",
|
||||
size = "20%",
|
||||
})
|
||||
|
||||
-- mount/open the component
|
||||
split:mount()
|
||||
|
||||
-- unmount component when cursor leaves buffer
|
||||
split:on(event.BufLeave, function()
|
||||
split:unmount()
|
||||
end)
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.split`](lua/nui/split)**
|
||||
|
||||
**[Check Wiki Page for `nui.split`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.split)**
|
||||
|
||||
## Extendibility
|
||||
|
||||
Each of the [blocks](#blocks) and [components](#components) can be extended to add new
|
||||
methods or change their behaviors.
|
||||
|
||||
```lua
|
||||
local Timer = Popup:extend("Timer")
|
||||
|
||||
function Timer:init(popup_options)
|
||||
local options = vim.tbl_deep_extend("force", popup_options or {}, {
|
||||
border = "double",
|
||||
focusable = false,
|
||||
position = { row = 0, col = "100%" },
|
||||
size = { width = 10, height = 1 },
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal,FloatBorder:SpecialChar",
|
||||
},
|
||||
})
|
||||
|
||||
Timer.super.init(self, options)
|
||||
end
|
||||
|
||||
function Timer:countdown(time, step, format)
|
||||
local function draw_content(text)
|
||||
local gap_width = 10 - vim.api.nvim_strwidth(text)
|
||||
vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, {
|
||||
string.format(
|
||||
"%s%s%s",
|
||||
string.rep(" ", math.floor(gap_width / 2)),
|
||||
text,
|
||||
string.rep(" ", math.ceil(gap_width / 2))
|
||||
),
|
||||
})
|
||||
end
|
||||
|
||||
self:mount()
|
||||
|
||||
local remaining_time = time
|
||||
|
||||
draw_content(format(remaining_time))
|
||||
|
||||
vim.fn.timer_start(step, function()
|
||||
remaining_time = remaining_time - step
|
||||
|
||||
draw_content(format(remaining_time))
|
||||
|
||||
if remaining_time <= 0 then
|
||||
self:unmount()
|
||||
end
|
||||
end, { ["repeat"] = math.ceil(remaining_time / step) })
|
||||
end
|
||||
|
||||
local timer = Timer()
|
||||
|
||||
timer:countdown(10000, 1000, function(time)
|
||||
return tostring(time / 1000) .. "s"
|
||||
end)
|
||||
```
|
||||
|
||||
#### `nui.object`
|
||||
|
||||
A small object library is bundled with `nui.nvim`. It is, more or less, a clone of the
|
||||
[`kikito/middleclass`](https://github.com/kikito/middleclass) library.
|
||||
|
||||
[Check Wiki Page for `nui.object`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.object)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the MIT License. Check the [LICENSE](./LICENSE) file for details.
|
102
bundle/nui.nvim/lua/nui/input/README.md
vendored
Normal file
102
bundle/nui.nvim/lua/nui/input/README.md
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
# Input
|
||||
|
||||
Input is an abstraction layer on top of Popup.
|
||||
|
||||
It uses prompt buffer (check `:h prompt-buffer`) for its popup window.
|
||||
|
||||
```lua
|
||||
local Input = require("nui.input")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local popup_options = {
|
||||
relative = "cursor",
|
||||
position = {
|
||||
row = 1,
|
||||
col = 0,
|
||||
},
|
||||
size = 20,
|
||||
border = {
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = "[Input]",
|
||||
top_align = "left",
|
||||
},
|
||||
},
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal",
|
||||
},
|
||||
}
|
||||
|
||||
local input = Input(popup_options, {
|
||||
prompt = "> ",
|
||||
default_value = "42",
|
||||
on_close = function()
|
||||
print("Input closed!")
|
||||
end,
|
||||
on_submit = function(value)
|
||||
print("Value submitted: ", value)
|
||||
end,
|
||||
on_change = function(value)
|
||||
print("Value changed: ", value)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
If you provide the `on_change` function, it'll be run everytime value changes.
|
||||
|
||||
Pressing `<CR>` runs the `on_submit` callback function and closes the window.
|
||||
Pressing `<C-c>` runs the `on_close` callback function and closes the window.
|
||||
|
||||
Of course, you can override the default keymaps and add more. For example:
|
||||
|
||||
```lua
|
||||
-- unmount input by pressing `<Esc>` in normal mode
|
||||
input:map("n", "<Esc>", function()
|
||||
input:unmount()
|
||||
end, { noremap = true })
|
||||
```
|
||||
|
||||
You can manipulate the assocciated buffer and window using the
|
||||
`split.bufnr` and `split.winid` properties.
|
||||
|
||||
**NOTE**: the first argument accepts options for `nui.popup` component.
|
||||
|
||||
## Options
|
||||
|
||||
### `prompt`
|
||||
|
||||
**Type:** `string` or `NuiText`
|
||||
|
||||
Prefix in the input.
|
||||
|
||||
### `default_value`
|
||||
|
||||
**Type:** `string`
|
||||
|
||||
Default value placed in the input on mount
|
||||
|
||||
### `on_close`
|
||||
|
||||
Callback function, called when input is closed.
|
||||
|
||||
### `on_submit`
|
||||
|
||||
Callback function, called when input value is submitted.
|
||||
|
||||
### `on_change`
|
||||
|
||||
Callback function, called when input value is changed.
|
||||
|
||||
### `disable_cursor_position_patch`
|
||||
|
||||
By default, `nui.input` will try to make sure the cursor on parent window is not
|
||||
moved after input is submitted/closed. If you want to disable this behavior
|
||||
for some reason, you can set `disable_cursor_position_patch` to `true`.
|
||||
|
||||
## Methods
|
||||
|
||||
Methods from `nui.popup` are also available for `nui.input`.
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.input wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.input).
|
143
bundle/nui.nvim/lua/nui/input/init.lua
vendored
Normal file
143
bundle/nui.nvim/lua/nui/input/init.lua
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
local Popup = require("nui.popup")
|
||||
local Text = require("nui.text")
|
||||
local defaults = require("nui.utils").defaults
|
||||
local is_type = require("nui.utils").is_type
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
-- exiting insert mode places cursor one character backward,
|
||||
-- so patch the cursor position to one character forward
|
||||
-- when unmounting input.
|
||||
---@param target_cursor number[]
|
||||
---@param force? boolean
|
||||
local function patch_cursor_position(target_cursor, force)
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
|
||||
if target_cursor[2] == cursor[2] and force then
|
||||
-- didn't exit insert mode yet, but it's gonna
|
||||
vim.api.nvim_win_set_cursor(0, { cursor[1], cursor[2] + 1 })
|
||||
elseif target_cursor[2] - 1 == cursor[2] then
|
||||
-- already exited insert mode
|
||||
vim.api.nvim_win_set_cursor(0, { cursor[1], cursor[2] + 1 })
|
||||
end
|
||||
end
|
||||
|
||||
---@alias nui_input_internal nui_popup_internal|{ default_value: string, prompt: NuiText }
|
||||
|
||||
---@class NuiInput: NuiPopup
|
||||
---@field private _ nui_input_internal
|
||||
local Input = Popup:extend("NuiInput")
|
||||
|
||||
---@param popup_options table
|
||||
---@param options table
|
||||
function Input:init(popup_options, options)
|
||||
popup_options.enter = true
|
||||
|
||||
popup_options.buf_options = defaults(popup_options.buf_options, {})
|
||||
popup_options.buf_options.buftype = "prompt"
|
||||
|
||||
if not is_type("table", popup_options.size) then
|
||||
popup_options.size = {
|
||||
width = popup_options.size,
|
||||
}
|
||||
end
|
||||
|
||||
popup_options.size.height = 1
|
||||
|
||||
Input.super.init(self, popup_options)
|
||||
|
||||
self._.default_value = defaults(options.default_value, "")
|
||||
self._.prompt = Text(defaults(options.prompt, ""))
|
||||
self._.disable_cursor_position_patch = defaults(options.disable_cursor_position_patch, false)
|
||||
|
||||
local props = {}
|
||||
|
||||
self.input_props = props
|
||||
|
||||
props.on_submit = function(value)
|
||||
local target_cursor = vim.api.nvim_win_get_cursor(self._.position.win)
|
||||
|
||||
local prompt_normal_mode = vim.fn.mode() == "n"
|
||||
|
||||
self:unmount()
|
||||
|
||||
vim.schedule(function()
|
||||
if prompt_normal_mode then
|
||||
-- NOTE: on prompt-buffer normal mode <CR> causes neovim to enter insert mode.
|
||||
-- ref: https://github.com/neovim/neovim/blob/d8f5f4d09078/src/nvim/normal.c#L5327-L5333
|
||||
vim.api.nvim_command("stopinsert")
|
||||
end
|
||||
|
||||
if not self._.disable_cursor_position_patch then
|
||||
patch_cursor_position(target_cursor, prompt_normal_mode)
|
||||
end
|
||||
|
||||
if options.on_submit then
|
||||
options.on_submit(value)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
props.on_close = function()
|
||||
local target_cursor = vim.api.nvim_win_get_cursor(self._.position.win)
|
||||
|
||||
self:unmount()
|
||||
|
||||
vim.schedule(function()
|
||||
if vim.fn.mode() == "i" then
|
||||
vim.api.nvim_command("stopinsert")
|
||||
end
|
||||
|
||||
if not self._.disable_cursor_position_patch then
|
||||
patch_cursor_position(target_cursor)
|
||||
end
|
||||
|
||||
if options.on_close then
|
||||
options.on_close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if options.on_change then
|
||||
props.on_change = function()
|
||||
local value_with_prompt = vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1]
|
||||
local value = string.sub(value_with_prompt, self._.prompt:length() + 1)
|
||||
options.on_change(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Input:mount()
|
||||
local props = self.input_props
|
||||
|
||||
Input.super.mount(self)
|
||||
|
||||
if props.on_change then
|
||||
vim.api.nvim_buf_attach(self.bufnr, false, {
|
||||
on_lines = props.on_change,
|
||||
})
|
||||
end
|
||||
|
||||
if #self._.default_value then
|
||||
self:on(event.InsertEnter, function()
|
||||
vim.api.nvim_feedkeys(self._.default_value, "t", true)
|
||||
end, { once = true })
|
||||
end
|
||||
|
||||
vim.fn.prompt_setprompt(self.bufnr, self._.prompt:content())
|
||||
if self._.prompt:length() > 0 then
|
||||
vim.schedule(function()
|
||||
self._.prompt:highlight(self.bufnr, self.ns_id, 1, 0)
|
||||
end)
|
||||
end
|
||||
|
||||
vim.fn.prompt_setcallback(self.bufnr, props.on_submit)
|
||||
vim.fn.prompt_setinterrupt(self.bufnr, props.on_close)
|
||||
|
||||
vim.api.nvim_command("startinsert!")
|
||||
end
|
||||
|
||||
---@alias NuiInput.constructor fun(popup_options: table, options: table): NuiInput
|
||||
---@type NuiInput|NuiInput.constructor
|
||||
local NuiInput = Input
|
||||
|
||||
return NuiInput
|
240
bundle/nui.nvim/lua/nui/layout/README.md
vendored
Normal file
240
bundle/nui.nvim/lua/nui/layout/README.md
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
# Layout
|
||||
|
||||
Layout is a helper component for creating complex layout by automatically
|
||||
handling the calculation for position and size of other components.
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
local Layout = require("nui.layout")
|
||||
local Popup = require("nui.popup")
|
||||
|
||||
local top_popup = Popup({ border = "single" })
|
||||
local bottom_popup = Popup({ border = "double" })
|
||||
|
||||
local layout = Layout(
|
||||
{
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 80,
|
||||
height = 40,
|
||||
},
|
||||
},
|
||||
Layout.Box({
|
||||
Layout.Box(top_popup, { size = "40%" }),
|
||||
Layout.Box(bottom_popup, { size = "60%" }),
|
||||
}, { dir = "col" })
|
||||
)
|
||||
|
||||
layout:mount()
|
||||
```
|
||||
|
||||
_Signature:_ `Layout(options, box)` or `Layout(component, box)`
|
||||
|
||||
`component` can be `Popup` or `Split`.
|
||||
|
||||
## Options
|
||||
|
||||
### `relative`
|
||||
|
||||
**Type:** `string` or `table`
|
||||
|
||||
This option affects how `position` and `size` are calculated.
|
||||
|
||||
**Examples**
|
||||
|
||||
Relative to cursor on current window:
|
||||
|
||||
```lua
|
||||
relative = "cursor",
|
||||
```
|
||||
|
||||
Relative to the current editor screen:
|
||||
|
||||
```lua
|
||||
relative = "editor",
|
||||
```
|
||||
|
||||
Relative to the current window (_default_):
|
||||
|
||||
```lua
|
||||
relative = "win",
|
||||
```
|
||||
|
||||
Relative to the window with specific id:
|
||||
|
||||
```lua
|
||||
relative = {
|
||||
type = "win",
|
||||
winid = 5,
|
||||
},
|
||||
```
|
||||
|
||||
Relative to the buffer position:
|
||||
|
||||
```lua
|
||||
relative = {
|
||||
type = "buf",
|
||||
-- zero-indexed
|
||||
position = {
|
||||
row = 5,
|
||||
col = 5,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `position`
|
||||
|
||||
**Type:** `number` or `percentage string` or `table`
|
||||
|
||||
Position is calculated from the top-left corner.
|
||||
|
||||
If `position` is `number` or `percentage string`, it applies to both `row` and `col`.
|
||||
Or you can pass a table to set them separately.
|
||||
|
||||
For `percentage string`, position is calculated according to the option `relative`.
|
||||
If `relative` is set to `"buf"` or `"cursor"`, `percentage string` is not allowed.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
position = 50,
|
||||
```
|
||||
|
||||
```lua
|
||||
position = "50%",
|
||||
```
|
||||
|
||||
```lua
|
||||
position = {
|
||||
row = 30,
|
||||
col = 20,
|
||||
},
|
||||
```
|
||||
|
||||
```lua
|
||||
position = {
|
||||
row = "20%",
|
||||
col = "50%",
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `size`
|
||||
|
||||
**Type:** `number` or `percentage string` or `table`
|
||||
|
||||
Determines the size of the layout.
|
||||
|
||||
If `size` is `number` or `percentage string`, it applies to both `width` and `height`.
|
||||
You can also pass a table to set them separately.
|
||||
|
||||
For `percentage string`, `size` is calculated according to the option `relative`.
|
||||
If `relative` is set to `"buf"` or `"cursor"`, window size is considered.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
size = 50,
|
||||
```
|
||||
|
||||
```lua
|
||||
size = "50%",
|
||||
```
|
||||
|
||||
```lua
|
||||
size = {
|
||||
width = 80,
|
||||
height = 40,
|
||||
},
|
||||
```
|
||||
|
||||
```lua
|
||||
size = {
|
||||
width = "80%",
|
||||
height = "60%",
|
||||
},
|
||||
```
|
||||
|
||||
## Layout.Box
|
||||
|
||||
_Signature:_ `Layout.Box(box, options)`
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | ------------------------------ | ----------------------------------------- |
|
||||
| `box` | `Layout.Box[]` / nui component | list of `Layout.Box` or any nui component |
|
||||
| `options` | `table` | box options |
|
||||
|
||||
`options` is a `table` having the following keys:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------ | ----------------------------- | ------------------------------------------------------ |
|
||||
| `dir` | `"col"` / `"row"` (_default_) | arrangement direction, only if `box` is `Layout.Box[]` |
|
||||
| `grow` | `number` | growth factor to fill up the box free space |
|
||||
| `size` | `number` / `string` / `table` | optional if `grow` is present |
|
||||
|
||||
## Methods
|
||||
|
||||
### `layout:mount`
|
||||
|
||||
_Signature:_ `layout:mount()`
|
||||
|
||||
Mounts the layout with all the components.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
layout:mount()
|
||||
```
|
||||
|
||||
### `layout:unmount`
|
||||
|
||||
_Signature:_ `layout:unmount()`
|
||||
|
||||
Unmounts the layout with all the components.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
layout:unmount()
|
||||
```
|
||||
|
||||
### `layout:hide`
|
||||
|
||||
_Signature:_ `layout:hide()`
|
||||
|
||||
Hides the layout with all the components. Preserves the buffer (related content, autocmds and keymaps).
|
||||
|
||||
### `layout:show`
|
||||
|
||||
_Signature:_ `layout:show()`
|
||||
|
||||
Shows the hidden layout with all the components.
|
||||
|
||||
### `layout:update`
|
||||
|
||||
_Signature:_ `layout:update(config, box?)` or `layout:update(box?)`
|
||||
|
||||
**Parameters**
|
||||
|
||||
`config` is a `table` having the following keys:
|
||||
|
||||
| Key | Type |
|
||||
| ---------- | ------------------ |
|
||||
| `relative` | `string` / `table` |
|
||||
| `position` | `string` / `table` |
|
||||
| `size` | `string` / `table` |
|
||||
|
||||
`box` is a `table` returned by `Layout.Box`.
|
||||
|
||||
They are the same options used for layout initialization.
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in
|
||||
[nui.layout wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.layout).
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user