Repository: ldelossa/litee.nvim Branch: main Commit: 4efaf373322d Files: 30 Total size: 125.1 KB Directory structure: gitextract_v9v813bt/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── README.md ├── doc/ │ ├── litee.txt │ └── tags └── lua/ └── litee/ └── lib/ ├── commands.lua ├── config.lua ├── details/ │ └── init.lua ├── highlights/ │ ├── auto.lua │ └── init.lua ├── icons/ │ └── init.lua ├── init.lua ├── jumps/ │ └── init.lua ├── lsp/ │ ├── hover.lua │ ├── init.lua │ └── wrappers.lua ├── navi/ │ └── init.lua ├── notify/ │ └── init.lua ├── panel/ │ ├── autocmds.lua │ └── init.lua ├── state/ │ ├── autocmds.lua │ └── init.lua ├── term/ │ └── init.lua ├── tree/ │ ├── init.lua │ ├── marshal/ │ │ └── init.lua │ └── node.lua └── util/ ├── buffer.lua ├── init.lua ├── path.lua └── window.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [ldelossa] ================================================ FILE: .gitignore ================================================ notes.md ================================================ FILE: README.md ================================================ ``` ██╗ ██╗████████╗███████╗███████╗ ███╗ ██╗██╗ ██╗██╗███╗ ███╗ ██║ ██║╚══██╔══╝██╔════╝██╔════╝ ████╗ ██║██║ ██║██║████╗ ████║ Lightweight ██║ ██║ ██║ █████╗ █████╗ ██╔██╗ ██║██║ ██║██║██╔████╔██║ Integrated ██║ ██║ ██║ ██╔══╝ ██╔══╝ ██║╚██╗██║╚██╗ ██╔╝██║██║╚██╔╝██║ Text ███████╗██║ ██║ ███████╗███████╗██╗██║ ╚████║ ╚████╔╝ ██║██║ ╚═╝ ██║ Editing ╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═══╝ ╚═╝╚═╝ ╚═╝ Environment ==================================================================================== ``` ![litee screenshot](./contrib/litee-screenshot.png) # litee.nvim Litee.nvim (pronounced lite) is a library for building "IDE-lite" experiences in Neovim. By utilizing the "litee" library plugin authors can achieve a consistent experience across separate plugins. There are several official litee plugins which can act as a reference for implementing additional. ## Calltree https://github.com/ldelossa/litee-calltree.nvim Analogous to VSCode's "Call Hierarchy" tool, this plugin exposes an explorable tree of incoming or outgoing calls for a given symbol. Unlike other Neovim plugins, the tree can be expanded and collapsed to discover "callers-of-callers" and "callees-of-callees" until you hit a leaf. ## Symboltree https://github.com/ldelossa/litee-symboltree.nvim Analogous to VSCode's "Outline" tool, this plugin exposes a live tree of document symbols for the current file. The tree is updated as you move around and change files. ## Filetree https://github.com/ldelossa/litee-filetree.nvim Analogous to VSCode's "Explorer", this plugin exposes a full feature file explorer which supports recursive copies, recursive moves, and proper renaming of a file (more on this in the appropriate section). ## Bookmarks https://github.com/ldelossa/litee-bookmarks.nvim This plugin exposes a way to create Bookmarks, pinnable areas of important code, and organize them into one or more Notebooks. Notebooks allow you to open and close sets of Bookmarks depending on what you're working on that day. # Usage litee.nvim is a library which other plugins can important and use. The library has it's own configuration and setup function which can be viewed in the `doc.txt`. An example of configuring the library is below: ``` require('litee.lib').setup({ tree = { icon_set = "codicons" }, panel = { orientation = "left", panel_size = 30 } }) ``` ================================================ FILE: doc/litee.txt ================================================ *litee.nvim* litee.nvim Author: Louis DeLosSantos Homepage: License: MIT license ██╗ ██╗████████╗███████╗███████╗ ███╗ ██╗██╗ ██╗██╗███╗ ███╗ ██║ ██║╚══██╔══╝██╔════╝██╔════╝ ████╗ ██║██║ ██║██║████╗ ████║ Lightweight ██║ ██║ ██║ █████╗ █████╗ ██╔██╗ ██║██║ ██║██║██╔████╔██║ Integrated ██║ ██║ ██║ ██╔══╝ ██╔══╝ ██║╚██╗██║╚██╗ ██╔╝██║██║╚██╔╝██║ Text ███████╗██║ ██║ ███████╗███████╗██╗██║ ╚████║ ╚████╔╝ ██║██║ ╚═╝ ██║ Editing ╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═══╝ ╚═╝╚═╝ ╚═╝ Environment ==================================================================================== CONTENTS *litee-contents* 1 Intro..............................................|litee-intro| 2 Usage..............................................|litee-usage| 3 Library Modules................................|litee-libraries| 4 lib/details..................................|litee-lib-details| 5 lib/highlights............................|litee-lib-highlights| 6 lib/icons......................................|litee-lib-icons| 7 lib/jumps......................................|litee-lib-jumps| 8 lib/lsp..........................................|litee-lib-lsp| 9 lib/navi........................................|litee-lib-navi| 10 lib/notify......................................|litee-lib-navi| 11 lib/panel......................................|litee-lib-panel| 12 lib/state......................................|litee-lib-state| 13 lib/tree........................................|litee-lib-tree| 14 lib/util........................................|litee-lib-util| 15 lib/term........................................|litee-lib-term| ==================================================================================== INTRODUCTION *litee-intro* Litee.nvim (pronounced lite) is a library for building "IDE-lite" experience in Neovim. By utilizing the "litee" library plugin authors can achieve a consistent experience across separate plugins. There are several official litee plugins which can act as a reference for implementing additional. - Calltree https://github.com/ldelossa/litee-calltree Analogous to VSCode's "Call Hierarchy" tool, this feature exposes an explorable tree of incoming or outgoing calls for a given symbol. Unlike other Neovim plugins, the tree can be expanded and collapsed to discover "callers-of-callers" and "callees-of-callees" until you hit a leaf. - Symboltree https://github.com/ldelossa/litee-symboltree Analogous to VSCode's "Outline" tool, this feature exposes a live tree of document symbols for the current file. The tree is updated as you move around and change files. - Filetree https://github.com/ldelossa/litee-filetree Analogous to VSCode's "Explorer", this feature exposes a full feature file explorer which supports recursive copies, recursive moves, and proper renaming of a file (more on this in the appropriate section). ==================================================================================== Usage *litee-usage* Litee.nvim exports a single lua module called "lib". Inside "lib" are several sub-modules which expose a facility for plugin authors. Each lib will be covered in detail in their respective sections. The library itself has a config object which configures each sub-module. The configuration is structured like so (with defaults). > M.config = { icons = {}, jumps = {}, lsp = {}, navi = {}, notify = { enabled = true, }, panel = { orientation = "left", panel_size = 30, }, state = {}, tree = { icon_set = "default", indent_guides = true } } < Each key of the configuration corresponds to a library sub-module. You can apply configuration to litee/lib by way of it's setup method found in litee/lib/init.lua, for example: > require('litee.lib').setup({ tree = { icon_set = "codicons" }, panel = { orientation = "top", panel_size = 15 } }) < The above overrides the default configuration options for the icon_set used in the tree sub-module and changes the panel sub-module's orientation and size. Example for custom icons: > -- Provide a custom icon_set which will be merged with the default icon_set. require('litee.lib').setup{ tree = { icon_set_custom = { Struct = "s" } } } -- You can even copy, paste the icon_set in `lib/icons/init.lua`, then -- modify it and pass it to `icon_set_custom`. local icon_set_custom = { ... } require('litee.lib').setup{ tree = { icon_set_custom = icon_set_custom } } -- Provide a custom icon_set which will be merged with the specified icon_set -- from `lib/icons/init.lua` by name. require('litee.lib').setup{ tree = { icon_set_custom = { Struct = "s" }, icon_set = "codicons" } } < ==================================================================================== lib/details *litee-lib-details* The `details` library exports a consistent way to display and close a details pop up window. The contents of the `details` pop-up is provided by a `detail_func` passed into the `details_popup` function. Thus, the caller can determine what the details are for a particular node or plugin element. The `details` object was designed to be used with a tree node, however passing in any type for the "node" argument will work as long as the associated "detail_func" can parse the object and spit out a set of buffer lines. Use `close_details_popup()` to close the pop-up. ==================================================================================== lib/highlights *litee-lib-highlights* The `highlights` library defines the highlights used within the `litee` library, methods for setting up the default highlights, and a sub-module for the auto-highlights features. The following highlights are exported and can be defined for theme overriding. > M.hls = { SymbolDetailHL = "LTSymbolDetail", SymbolHL = "LTSymbol", SymbolJumpHL = "LTSymbolJump", SymbolJumpRefsHL = "LTSymbolJumpRefs", IndentGuideHL = "LTIndentGuide", ExpandedGuideHL = "LTExpandedGuide", CollapsedGuideHL = "LTCollapsedGuide", SelectFiletreeHL = "LTSelectFiletree" } < The `auto` module provides a facility for highlighting areas of a source file given a node and a window. TODO: The `auto` module should not rely on "lib_util.resolve_absolute_file_path" and "lib_util.resolve_location", as these methods require us to understand the types of "node" a head of time. ==================================================================================== lib/icons *litee-lib-icons* The `icons` library defines and exports icon sets which plugin authors may use for a consistent experience. Currently `codicons`, `simple`, `nerd` and `default` icons are exported. The `default` icon set only supplies the stock UTF-8 icons necessary to display litee's UI such as indent guides and tree symbols. Users of this library can override the exported icon libraries during runtime to change an icon from its default. This library also exports a list of icon highlights which can be overriden. Check out the source file `lib/icons/init.lua` for full details. ==================================================================================== lib/jumps *litee-lib-jumps* The `jumps` library defines and exports functions which perform source file jumps to a specific "location" object. The "location" object is specified by the LSP, see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#location Highlights can be set within the jumped-to source window and can be cleared with the `set_jump_hl` method. TODO: refactor jumps library to no longer need "lib_util.resolve_location" - all location details should be passed in. ==================================================================================== lib/lsp *litee-lib-lsp* The `lsp` library holds methods specific to interacting with the native LSP library. This includes helpers for making various LSP requests, wrappers for LSP methods and a library for creating Hover pop-ups. This is the library to use or contribute to if you need to interface with Neovim's native LSP libraries. ==================================================================================== lib/navi *litee-lib-navi* The `navi` library is small and exports methods for progressing a cursor forward or backwards in a window which is registered with litee. Two callbacks exist to perform actions before and after the cursor move. See source file for details. See `litee-lib-panel` for information on registration. ==================================================================================== lib/notify *litee-lib-notify* The `notify` library produces pop-up notifications at the top right corner of the editor. Methods exist to create both persistent notifications and ones with configurable timeouts, along with methods to close all notifications. Currently, notifications stack on top of each other so only one is visible at a time. ==================================================================================== lib/panel *litee-lib-panel* The `panel` library provides a consistent panel experience across plugins which utilizes the `litee.nvim` library. The panel is akin to panels in JetBrains and VSCode IDEs. For a plugin to integrate with the panel it must register itself with the panel and it also must utilize "lib/state" to store its runtime state. Runtime state holds information about a registered plugins windows, tab, buffer, etc, and is formally defined in `lib/state` To register a plugin with the panel the `litee.lib.panel.register_component` method must be used. This method requires a `pre_window_create` callback and optionally takes a `post_window_create` callback. The `pre_window_create` callback must create a buffer for the plugin being registered and write the buffer ID into the plugin's component state, which is an argument to this callback. The plugin is free to perform any other actions associated with displaying its window, such as setting up autocommands or buffer options. The `post_window_create` method is optional and when called Neovim is inside the newly created window within the panel. The plugin is free to perform any in-window manipulations such as setting syntax highlights or window options. The panel also supports a feature dubbed "PopOut" panels. "PopOut" panels are floating windows which act as if you're "popping out" the panel window to a float, and will "pop" back into the panel when closed. This is convenient for "zooming" into panel windows. Additionally, the panel need not be visible for a "PopOut" panel to be displayed. Meaning, users of `litee.nvim` do not necessary need to interface with the panel, and can use only "PopOut" panels if they prefer, tho the plugin has to facilitate this by calling the right `lib/panel` api functions. For more info on "PopOut" panels see the "popout_to" function in `litee.lib.panel`. The rest of the API footprint exists to display/hide/jump into the panel. See the source code for full details. ==================================================================================== lib/state *litee-lib-state* The `state` library provides a library for handling plugin state. Litee must keep track of when components (plugins) are displayed in the panel, if their windows are valid, if they have an associated tree handle, whether their buffer is valid, etc... Likewise, plugins need a way to retrieve and store their own state in between function calls. The `state` library acts a registry for this state and makes it retrievable both globally and by components. A component should only mutate its own state, unless it positively knows what its doing. The state structure looks as follows: > { "componentA" = { buf = 0, win = 0, tab = 0, tree = 0, win_dimensions = {0, 0}, invoking_win = 0, active_lsp_clients = {list of lsp clients}, (component specific fields)... }, "componentB" = { buf = 0, win = 0, tab = 0, tree = 0, win_dimensions = {0, 0}, invoking_win = 0, active_lsp_clients = {list of lsp clients}, (component specific fields)... } } < When functions expect the full state the function parameter is typically "state" and when functions expect a component's stat (state["componentA"] for example) the parameter is typically "component_state". Ideally all of a plugin's methods which interface with litee require a "component_state", and they should only touch their own state. Accessors methods for both global and component state exist in `lib/state` along with methods to write both types of state back to the registry. Neovim is single threaded and tables are references, so its actually pretty uncommon to write state back into the registry. The exception is when setting the initial component state table in the global state table, performed by `put_component_state` `lib/state.lua` exports some helpful methods such as getting the component type for a particular buffer ID. Like always, until I have time to document better, check out the source for full details. ==================================================================================== lib/tree *litee-lib-tree* The `tree` library implements a reusable tree which supports the expansion and collapsing of nodes. A node in the tree has a core data structure which a plugin can tag on plugin-specific data on. A node looks as follows and can be instantiated with the "lib.tree.node.new_node()" method. > { -- a non-unique display name for the node name = name, -- the depth of the node in the target tree depth = depth, -- a unique key used to identify the node when -- placed into a tree key = key, -- a list of children nodes with recursive definitions. children = {}, -- whether this node is expanded in its containing -- tree. expanded = false, } < Its the caller's job to define each value ensuring the key for the node is unique within the tree being built. The `tree` lib itself is a registry where you request a tree and get back a handle to it. The handle can be stored on a plugin's state object and stored in `lib/state` and it also passed to `lib/tree` methods to identify which tree is being acted upon. The data structure which represents a tree looks like this: `{root = {}, depth_table = {}, kind = "", source_line_map = nil, buf_line_map = nil}` Where root is an empty root node, depth_table is a flattened 2d array containing a list of nodes at a particular depth of the tree (useful for quick lookups), kind is the kind of tree defined by the caller, source_line_map is a mapping from a source code line to a node in the tree, and buf_line_map is a mapping from a buffer line in the tree to a source code line. source_line_map can only be realized if your node has a top level "location" key with an LSP specified `location` structure as its value. Both aforementioned maps make it possibe to map items in the tree to source code lines and vice versa. The best way to understand this is looking at the reference implementations of "litee-filetree", "litee-calltree", and "litee-symboltree". Once a tree is created you can begin building its nodes. A tree is build by creating a root node and then a list of children. For convenience, the `add_node` function will take a root and a list of children, compute the children's depth, attach the children to the root, and add it to the tree. It's important to understand that by using the `add_node` without further options, you do not need to worry about the children node's depth fields, but you DO need to always set the root's depth field. When the `add_node` method receives a root node with its depth field of 0 it throws away the old tree and creates a new one. When the `add_node` method receives a root node with a depth greater then zero it can compute its children's depths and will add this new root (of a sub-tree) to the existing tree at the appropriate depth. Sometimes, the caller wants to build the entire tree with their own business logic and simply use the `tree` library for marshaling into a Neovim buffer and other facilities such as expand and collapsing. If this is the case you can use the `external` flag to the 'add_node' method. When the `add_node` method sees the external flag is set to true, it performs no actions on the root and simply adds it to the tree structure outlined above. The `children` parameter is completely ignored. Once the tree is built and all the desired nodes are added, you can write the tree to a buffer with the "write_tree" method. `function M.write_tree(buf, tree, marshal_func, no_guide_leaf)` The write_tree method takes a buffer ID to write too, a tree ID to write, a marshal_func which is called for each node and returns the necessary strings which will make up the buffer line in the tree, and a "no_guide_leaf" flag. See the method's documentation for marshal_func details. The `no_guide_leaf` flag tells the `write_tree` method that when a node is expanded, and it has no children, leave off the "expanded" guide (the down arrow showing its expanded). This is a nice effect for some trees and a helper method exists if your plugin will always use this method `function M.write_tree_no_guide_leaf(buf, tree, marshal_func)`. Make note, the `write_tree` function can only determine a leaf *after* the node is expanded, for example, a node is collapsed, you expand it, it has no children, no expanded guide is shown. There are times where the caller can determine an item is a leaf *before* its expanded, for example, in a file tree, we know a regular file does not need an expand guide. To accomplish this the `marshal_func` suports returning an override for the expand guide as its very last argument. If returned, this symbol is used instead of the expand guide `write_tree` would use on its own accord. `litee-filetree` uses this to simply return a " " when it sees a regular file. There is a method, `function M.marshal_line(linenr, handle)', which takes a tree buffer's linenr and a tree handle and returns the corresponding node. A small note on the depth_table. A depth_table is a flattened 2d array which hold a list of nodes at a particular depth. This makes getting nodes at a particular depth very easy and (maybe...?) faster then a tree traversal. A depth_table is available after any initial and all subsequent `add_node` calls. Various methods exist in the library for manipulating the tree such as collapsing nodes and removing subtrees. Take a look at the source code to see what is available. ==================================================================================== lib/util *litee-lib-util* Lib `util` is a dumping ground for various helper functions and utilities. Lib `util` exports several helpful sub-libraries. `lib.util.window` For helper functions around Neovim windows `lib.util.buffer` For helper functions around Neovim buffers `lib.util.path` For helper functions dealing with file system paths (only linux right now.) ==================================================================================== lib/term *litee-lib-term* Lib `term` exports a method for opening a Neovim native terminal that is aware of `litee.nvim` environment. This terminal can be opened on the top or bottom and is controlled by the "term" configuration block in the `lib.config` module. > term = { -- can be "top" or "bottom" position = "bottom", -- the initial size of the terminal term_size = 15, -- if true maps arrow keys to window resize -- commands. map_resize_keys = true, }, < The terminal creates two "terminal" mode key bindings. > vim.api.nvim_buf_set_keymap(buf, 't', "v", "lua require('configs.terminal').terminal_vsplit()", opts) vim.api.nvim_buf_set_keymap(buf, 't', "n", "", opts) vim.api.nvim_buf_set_keymap(buf, 't', "h", " h", opts) vim.api.nvim_buf_set_keymap(buf, 't', "j", " j", opts) vim.api.nvim_buf_set_keymap(buf, 't', "k", " k", opts) vim.api.nvim_buf_set_keymap(buf, 't', "l", " l", opts) < The first one will open another terminal in a vsplit relative to the terminal you're issuing the command in. The second is a helper that puts your terminal back into normal mode. The final set are short-cuts for jumping out of the terminal into other windows. Be aware, these mappins will only work when your Vim mode is "terminal" which is when input is being forwarded directly to the shell. If you are in normal mode (the shell is just a buffer of lines at that point) the mappins will revert back to normal mode mappings. The term can be triggered open with the "LTTerm" user command. vim:ft=help ================================================ FILE: doc/tags ================================================ after litee.txt /*after* before litee.txt /*before* litee-contents litee.txt /*litee-contents* litee-intro litee.txt /*litee-intro* litee-lib-details litee.txt /*litee-lib-details* litee-lib-highlights litee.txt /*litee-lib-highlights* litee-lib-icons litee.txt /*litee-lib-icons* litee-lib-jumps litee.txt /*litee-lib-jumps* litee-lib-lsp litee.txt /*litee-lib-lsp* litee-lib-navi litee.txt /*litee-lib-navi* litee-lib-notify litee.txt /*litee-lib-notify* litee-lib-panel litee.txt /*litee-lib-panel* litee-lib-state litee.txt /*litee-lib-state* litee-lib-term litee.txt /*litee-lib-term* litee-lib-tree litee.txt /*litee-lib-tree* litee-lib-util litee.txt /*litee-lib-util* litee-usage litee.txt /*litee-usage* litee.nvim litee.txt /*litee.nvim* ================================================ FILE: lua/litee/lib/commands.lua ================================================ local M = {} -- commands will setup any Vim commands exported on litee.lib -- setup. function M.setup() vim.cmd("command! LTPanel lua require('litee.lib.panel').toggle_panel()") vim.cmd("command! LTTerm lua require('litee.lib.term').terminal()") vim.cmd("command! LTListTerms lua require('litee.lib.term').list_terminals()") vim.cmd("command! LTClearJumpHL lua require('litee.lib.jumps').set_jump_hl(false)") vim.cmd("command! LTClosePanelPopOut lua require('litee.lib.panel').close_current_popout()") end return M ================================================ FILE: lua/litee/lib/config.lua ================================================ local M = {} -- config is a global configuration object -- for each module in the litee library. -- -- config for a particular module is keyed -- by the module's directory name. M.config = { icons = {}, jumps = {}, lsp = {}, navi = {}, notify = { enabled = true, }, panel = { orientation = "left", panel_size = 30, }, state = {}, term = { position = "bottom", term_size = 15, map_resize_keys = true, }, tree = { icon_set = "default", icon_set_custom = nil, indent_guides = true } } return M ================================================ FILE: lua/litee/lib/details/init.lua ================================================ local M = {} -- a singleton float window for a details popup. local float_win = nil -- close_details_popups closes the created popup window -- if it exists. function M.close_details_popup() if float_win ~= nil and vim.api.nvim_win_is_valid(float_win) then vim.api.nvim_win_close(float_win, true) float_win = nil end end -- details_popup creates a popup window showing futher details -- about a symbol. -- -- @param state (table) The global state as defined by -- the lib/state library. -- @param node (table) A node passed to `detail_func` representing -- the item being described. -- @param detail_func function(state, node) A function called with the -- provided state and node that returns a list of buffer lines. -- The function must evaluate both arguments and return a list of buffer -- lines which describe any details about the node the caller defines. function M.details_popup(state, node, detail_func) local buf = vim.api.nvim_create_buf(false, true) if buf == 0 then vim.api.nvim_err_writeln("details_popup: could not create details buffer") return end vim.api.nvim_buf_set_option(buf, 'bufhidden', 'delete') vim.api.nvim_buf_set_option(buf, 'syntax', 'yaml') local lines = detail_func(state, node) if lines == nil then return end local width = 20 for _, line in ipairs(lines) do local line_width = vim.fn.strdisplaywidth(line) if line_width > width then width = line_width end end vim.api.nvim_buf_set_option(buf, 'modifiable', true) vim.api.nvim_buf_set_lines(buf, 0, #lines, false, lines) vim.api.nvim_buf_set_option(buf, 'modifiable', false) local popup_conf = vim.lsp.util.make_floating_popup_options( width, #lines, { border= "rounded", focusable= false, zindex = 99, } ) float_win = vim.api.nvim_open_win(buf, false, popup_conf) end return M ================================================ FILE: lua/litee/lib/highlights/auto.lua ================================================ local lib_util = require('litee.lib.util') local lib_hi = require('litee.lib.highlights') local M = {} -- the current highlight used for auto-highlighting M.higlight_ns = vim.api.nvim_create_namespace("calltree-hl") -- highlight will set a highlight on the source code -- lines the provided node represents if the invoking_win -- holds a buffer to said file. -- -- @param node (table) The element representing a source -- code symbol or element. This function requires the node -- to have a top level ".location" field. -- @param set (bool) If false any highlights which were -- previously set are cleared. If true, highlights will -- be created. -- @param win (int) A window handle to the window -- being evaluated for highlighting. function M.highlight(node, set, win) if not vim.api.nvim_win_is_valid(win) then return end local buf = vim.api.nvim_win_get_buf(win) if not vim.api.nvim_buf_is_valid(buf) then return end vim.api.nvim_buf_clear_namespace( buf, M.higlight_ns, 0, -1 ) if not set then return end local location = node.location if location == nil then return end local range = location.range if range["end"] == nil then return end -- make sure URIs match before setting highlight local invoking_buf = vim.api.nvim_win_get_buf(win) local cur_file = vim.api.nvim_buf_get_name(invoking_buf) local symbol_path = lib_util.absolute_path_from_uri(location.uri) if cur_file ~= symbol_path then return end vim.api.nvim_buf_add_highlight( buf, M.higlight_ns, lib_hi.hls.SymbolJumpHL, range["start"].line, range["start"].character, range["end"].character ) vim.api.nvim_win_set_cursor(win, {range["start"].line+1, 0}) end return M ================================================ FILE: lua/litee/lib/highlights/init.lua ================================================ local M = {} -- hls is a map of UI specific highlights used -- by the litee.nvim library. M.hls = { SymbolDetailHL = "LTSymbolDetail", SymbolHL = "LTSymbol", SymbolJumpHL = "LTSymbolJump", SymbolJumpRefsHL = "LTSymbolJumpRefs", IndentGuideHL = "LTIndentGuide", ExpandedGuideHL = "LTExpandedGuide", CollapsedGuideHL = "LTCollapsedGuide", SelectFiletreeHL = "LTSelectFiletree", NormalSB = "LTNormalSB" } -- setup_default_highlights configures a list of default -- highlights for the litee.nvim library. function M.setup_default_highlights() local dark = { LTBoolean = 'hi LTBoolean guifg=#0087af guibg=None', LTConstant = 'hi LTConstant guifg=#0087af guibg=None', LTConstructor = 'hi LTConstructor guifg=#4DC5C6 guibg=None', LTField = 'hi LTField guifg=#0087af guibg=None', LTFunction = 'hi LTFunction guifg=#988ACF guibg=None', LTMethod = 'hi LTMethod guifg=#0087af guibg=None', LTNamespace = 'hi LTNamespace guifg=#87af87 guibg=None', LTNumber = 'hi LTNumber guifg=#9b885c guibg=None', LTOperator = 'hi LTOperator guifg=#988ACF guibg=None', LTParameter = 'hi LTParameter guifg=#988ACF guibg=None', LTParameterReference = 'hi LTParameterReference guifg=#4DC5C6 guibg=None', LTString = 'hi LTString guifg=#af5f5f guibg=None', LTSymbol = 'hi LTSymbol guifg=#87afd7 ', LTSymbolDetail = 'hi LTSymbolDetail ctermfg=024 cterm=italic guifg=#988ACF gui=italic', LTSymbolJump = 'hi LTSymbolJump ctermfg=015 ctermbg=110 cterm=italic,bold,underline guifg=#464646 guibg=#87afd7 gui=italic,bold', LTSymbolJumpRefs = 'hi LTSymbolJumpRefs ctermfg=015 ctermbg=110 cterm=italic,bold,underline guifg=#464646 guibg=#9b885c gui=italic,bold', LTType = 'hi LTType guifg=#9b885c guibg=None', LTURI = 'hi LTURI guifg=#988ACF guibg=None', LTIndentGuide = 'hi LTIndentGuide guifg=None guibg=None', LTExpandedGuide = 'hi LTExpandedGuide guifg=None guibg=None', LTCollapsedGuide = 'hi LTCollapsedGuide guifg=None guibg=None', LTSelectFiletree = 'hi LTSelectFiletree ctermbg=131 ctermfg=246 cterm=None guibg=#af5f5f guifg=#e4e4e4 gui=None' } local light = { LTBoolean = 'hi LTBoolean guifg=#005f87 guibg=None', LTConstant = 'hi LTConstant guifg=#005f87 guibg=None', LTConstructor = 'hi LTConstructor guifg=#9b885c guibg=None', LTField = 'hi LTField guifg=#005f87 guibg=None', LTFunction = 'hi LTFunction guifg=#806CCF guibg=None', LTMethod = 'hi LTMethod guifg=#005f87 guibg=None', LTNamespace = 'hi LTNamespace guifg=#87af87 guibg=None', LTNumber = 'hi LTNumber guifg=#9b885c guibg=None', LTOperator = 'hi LTOperator guifg=#806CCF guibg=None', LTParameter = 'hi LTParameter guifg=#806CCF guibg=None', LTParameterReference = 'hi LTParameterReference guifg=#268889 guibg=None', LTString = 'hi LTString guifg=#af5f5f guibg=None', LTSymbol = 'hi LTSymbol guifg=#806CCF gui=underline', LTSymbolDetail = 'hi LTSymbolDetail ctermfg=024 cterm=italic guifg=#005f87 gui=italic', LTSymbolJump = 'hi LTSymbolJump ctermfg=015 ctermbg=110 cterm=italic,bold,underline guifg=#464646 guibg=#87afd7 gui=italic,bold', LTSymbolJumpRefs = 'hi LTSymbolJumpRefs ctermfg=015 ctermbg=110 cterm=italic,bold,underline guifg=#464646 guibg=#9b885c gui=italic,bold', LTType = 'hi LTType guifg=#268889 guibg=None', LTURI = 'hi LTURI guifg=#806CCF guibg=None', LTIndentGuide = 'hi LTIndentGuide guifg=None guibg=None', LTExpandedGuide = 'hi LTExpandedGuide guifg=None guibg=None', LTCollapsedGuide = 'hi LTCollapsedGuide guifg=None guibg=None', LTSelectFiletree = 'hi LTSelectFiletree ctermbg=131 ctermfg=246 cterm=None guibg=#af5f5f guifg=#e4e4e4 gui=None' } local bg = vim.api.nvim_get_option("background") if bg == "dark" then for hl_name, hl in pairs(dark) do if vim.fn.hlexists(hl_name) == 0 then vim.cmd(hl) end end end if bg == "light" then for hl_name, hl in pairs(light) do if vim.fn.hlexists(hl_name) == 0 then vim.cmd(hl) end end end end return M ================================================ FILE: lua/litee/lib/icons/init.lua ================================================ local M = {} -- a lot of these are yoinked from: -- https://github.com/onsails/lspkind-nvim/blob/master/lua/lspkind/init.lua M.nerd = { Account = '', Array = "", Bookmark = "", Boolean = "", Calendar = '', Check = '', CheckAll = '', Circle = '', CircleFilled = '', CirclePause = '', CircleSlash = '', CircleStop = '', Class = "ﴯ", Collapsed = "", Color = "", Comment = '', Constant = "", Constructor = "", DiffAdded = '', Enum = "", EnumMember = "", Event = "", Expanded = "", Field = "ﰠ", File = "", Folder = "", Function = "", GitBranch = '', GitCommit = 'ﰖ', GitCompare = '', GitIssue = '', GitMerge = 'שּׁ', GitPullRequest = '', GitRepo = '', IndentGuide = "⎸", Info = '', Interface = "", Key = "", Keyword = "", Method = "", Module = "", MultiComment = '', Namespace = "", Notebook = "ﴬ", Null = "ﳠ", Number = "", Object = "", Operator = "", Package = "", Pass = '', PassFilled = '', Pencil = '', Property = "ﰠ", Reference = "", Separator = "•", Snippet = "", Space = " ", String = "", Struct = "פּ", Text = "", TypeParameter = "", Unit = "塞", Value = "", Variable = "", } M.codicons = { Account = '', Array = "", Bookmark = "", Boolean = "", Calendar = '', Check = '', CheckAll = '', Circle = '', CircleFilled = '', CirclePause = '', CircleSlash = '', CircleStop = '', Class = "", Collapsed = "", Color = "", Comment = '', CommentExclaim = '', Constant = "", Constructor = "", DiffAdded = '', Enum = "", EnumMember = "", Event = "", Expanded = "", Field = "", File = "", Folder = "", Function = "", GitBranch = '', GitCommit = '', GitCompare = '', GitIssue = '', GitMerge = '', GitPullRequest = '', GitRepo = '', History = '', IndentGuide = "⎸", Info = '', Interface = "", Key = "", Keyword = "", Method = "", Module = "", MultiComment = '', Namespace = "", Notebook = "", Notification = '', Null = "", Number = "", Object = "", Operator = "", Package = "", Pass = '', PassFilled = '', Pencil = '', Property = "", Reference = "", RequestChanges = '', Separator = "•", Snippet = "", Space = " ", String = "", Struct = "", Sync = '', Text = "", TypeParameter = "", Unit = "", Value = "", Variable = "", } M.default = { Account = "🗣", Array = "\\[\\]", Bookmark = "🏷", Boolean = "∧", Calendar = '🗓', Check = '✓', CheckAll = '🗸🗸', Circle = '🞆', CircleFilled = '●', CirclePause = '⦷', CircleSlash = '⊘', CircleStop = '⦻', Class = "c", Collapsed = "▶", Color = "🖌", Comment = '🗩', CommentExclaim = '🗩', Constant = "c", Constructor = "c", DiffAdded = '+', Enum = "Ε", EnumMember = "Ε", Event = "🗲", Expanded = "▼", Field = "𝐟", File = "🗀", Folder = "🗁", Function = "ƒ", GitBranch = ' ', GitCommit = '⫰', GitCompare = '⤄', GitIssue = '⊙', GitMerge = '⫰', GitPullRequest = '⬰', GitRepo = '🕮', History = '⟲', IndentGuide = "⎸", Info = '🛈', Interface = "I", Key = "", Keyword = "", Method = "", Module = "M", MultiComment = '🗩', Namespace = "N", Notebook = "🕮", Notification = '🕭', Null = "null", Number = "#", Object = "{}", Operator = "0", Package = "{}", Pass = '🗸', PassFilled = '🗸', Pencil = '', Property = "🛠", Reference = "⛉", RequestChanges = '⨪', Separator = "•", Space = " ", String = "\"", Struct = "struct", Sync = '🗘', Text = "\"", TypeParameter = "T", Unit = "U", Value = "v", Variable = "V", } M.simple = { Account = "A", Array = "\\[\\]", Bookmark = "BM", Boolean = "bool", Calendar = 'cal', Check = 'x', CheckAll = 'xx', Circle = 'o', CircleFilled = 'O', CirclePause = 'P', CircleSlash = 'ø', CircleStop = 'stop', Class = "class", Collapsed = ">", Color = "color", Comment = 'c', CommentExclaim = 'c!', Constant = "c", Constructor = "C", DiffAdded = '+', Enum = "Ε", EnumMember = "Ε", Event = "e", Expanded = "v", Field = "f", File = "F", Folder = "D", Function = "fn", GitBranch = 'br', GitCommit = 'ci', GitCompare = 'cmp', GitIssue = 'I', GitMerge = 'M', GitPullRequest = 'PR', GitRepo = 'R', History = 'H', IndentGuide = " ", Info = 'i', Interface = "I", Key = "k", Keyword = "kw", Method = "m", Module = "M", MultiComment = '#', Namespace = "NS", Notebook = "NB", Notification = 'N', Null = "null", Number = "#", Object = "{}", Operator = "op", Package = "{}", Pass = 'y', PassFilled = 'yy', Pencil = 'e', Property = "P", Reference = "&", RequestChanges = '-', Separator = ".", Space = " ", String = "\"", Struct = "S", Sync = 's', Text = "\"", TypeParameter = "T", Unit = "U", Value = "v", Variable = "V", } M.icon_hls = { Array = "LTConstant", Boolean = "LTBoolean", Class = "LTType", Constant = "LTConstant", Constructor = "LTFunction", Enum = "LTType", EnumMember = "LTField", Event = "LTType", Field = "LTField", File = "LTURI", Folder = "LTNamespace", Function = "LTFunction", Interface = "LTType", Key = "LTType", Keyword = "LTConstant", Method = "LTFunction", Module = "LTNamespace", Namespace = "LTNamespace", Null = "LTType", Number = "LTNumber", Object = "LTType", Operator = "LTOperator", Package = "LTNamespace", Property = "LTMethod", Reference = "LTType", Snippet = "LTString", String = "LTString", Struct = "LTType", Text = "LTString", TypeParameter = "LTParameter", Unit = "LTType", Value = "LTType", Variable = "LTConstant", Info = 'LTInfo', Pass = 'LTSuccess', PassFilled = 'LTSuccess', Account = 'LTAccount', Check = 'LTSuccess', CheckAll = 'LTSuccess', CircleFilled = 'LTDefault', Circle = 'LTDefault', CircleSlash = 'LTWarning', GitCompare = 'LTGitCompare', GitBranch = 'LTGitBranch', GitPullRequest = 'LTGitPullRequest', CircleStop = 'LTFailure', DiffAdded = 'LTDiffAdded', CirclePause = 'LTWarning', GitCommit = 'LTGitCommit', Comment = 'LTComment', MultiComment = 'LTMultiComment', Calendar = 'LTDefault', Pencil = 'LTWarning', History = 'LTWarning', Sync = 'LTWarning', CommentExclaim = 'LTWarning', RequestChanges = 'LTFailure', Notification = 'LTDiffAdded' } return M ================================================ FILE: lua/litee/lib/init.lua ================================================ local commands = require('litee.lib.commands') local config = require('litee.lib.config').config local lib_hi = require('litee.lib.highlights') local lib_icons = require('litee.lib.icons') local M = {} -- Once set up, `M.icon_set` must be a non-nil table. M.icon_set = nil local function icon_set() local tree = config.tree local base = lib_icons[tree.icon_set] or lib_icons["default"] local icon_set = nil if tree.icon_set_custom ~= nil then icon_set = tree.icon_set_custom -- merge custom icon with the default for key, val in pairs(base) do icon_set[key] = icon_set[key] or base[key] end else icon_set = base end M.icon_set = icon_set end -- If a custom icon_set (table) is provided by the user, -- `M.icon_set` will be updated and returned. -- A user can use the icons from `lib_icons` -- via providing a key name instead of a custom icon_set (table). -- When both arguments are provided, the custom icon_set is merged -- with the icons given by its name from `lib_icons`. function M.icon_set_update(custom, icon_key) local icon_set = nil if custom ~= nil then icon_set = icon_key and lib_icons[icon_key] or M.icon_set for key, val in pairs(custom) do icon_set[key] = custom[key] end else icon_set = lib_icons[icon_key] or lib_icons["default"] end return icon_set end local function merge_subconfig(component, user_subconfig) local subconfig = config[component] if subconfig == nil then return end for key, val in pairs(user_subconfig) do subconfig[key] = val end end function M.setup(user_config) if user_config ~= nil then for component, user_subconfig in pairs(user_config) do merge_subconfig(component, user_subconfig) end end -- setup icon_set icon_set() -- setup default highlights lib_hi.setup_default_highlights() -- setup commands commands.setup() -- au to close popup with cursor moves or buffer is closed. vim.cmd("au CursorMoved,BufWinLeave,WinLeave * lua require('litee.lib.util.buffer').close_all_popups()") -- on resize cycle the panel to re-adjust window sizes. vim.cmd("au VimResized * lua require('litee.lib.panel.autocmds').on_resize()") -- will clean out any tree data for a tab when closed. only necessary vim.cmd([[au TabClosed * lua require('litee.lib.state.autocmds').on_tab_closed(vim.fn.expand(''))]]) end return M ================================================ FILE: lua/litee/lib/jumps/init.lua ================================================ local config = require('litee.lib.config').config local lib_hi = require('litee.lib.highlights') local lib_panel = require('litee.lib.panel') local M = {} -- the current highlight source, reset on jumps M.jump_higlight_ns = vim.api.nvim_create_namespace("calltree-jump") -- the buffer we highlighted last. M.last_highlighted_buffer = nil -- move_or_create will a attempt to move from the -- calltree ui window to the nearest editor window -- -- if the move fails, assumingly because no other window -- exists, a new window will be created. -- -- @param orientation (string) The orientation of the -- surrouning plugin UI to make jumps intuitive. -- This is typically the orientation of the litee.nvim -- panel. Valid arguments are "left, right, top, bottom". local function move_or_create(orientation) local cur_win = vim.api.nvim_get_current_win() if orientation == "left" then vim.cmd('wincmd l') elseif orientation == "right" then vim.cmd('wincmd h') elseif orientation == "top" then vim.cmd('wincmd j') elseif orientation == "bottom" then vim.cmd('wincmd k') end if cur_win == vim.api.nvim_get_current_win() then if orientation == "left" then vim.cmd("botright vsplit") return true elseif orientation == "right" then vim.cmd("topleft vsplit") return true elseif orientation == "top" then vim.cmd("topleft split") return true elseif orientation == "bottom" then vim.cmd("topleft split") return true end end return false end -- jump_tab will open a new tab then jump to the symbol -- -- @param location (table) A location object as defined by -- the LSP. -- @param node (table) An element which is being jumped to, -- if this element has a high level "references" field with -- more "Location" objects, they will be highlighted as well. function M.jump_tab(location, node, offset_encoding) M.set_jump_hl(false, nil) vim.cmd("tabedit " .. location.uri) vim.cmd("set nocursorline") -- if the panel currently has a component "popped-out" -- close it before jumping. lib_panel.close_current_popout() vim.lsp.util.jump_to_location(location, offset_encoding or "utf-8") M.set_jump_hl(true, node) end -- jump_split will open a new split then jump to the symbol -- -- @param split (string) The type of split, valid arguments -- are "split" or "vsplit". -- @param location (table) A location object as defined by -- the LSP. -- @param node (table) An element which is being jumped to, -- if this element has a high level "references" field with -- more "Location" objects, they will be highlighted as well. function M.jump_split(split, location, node, offset_encoding) M.set_jump_hl(false, nil) if not move_or_create(config["panel"].orientation) then vim.cmd(split) end -- if the panel currently has a component "popped-out" -- close it before jumping. lib_panel.close_current_popout() vim.lsp.util.jump_to_location(location, offset_encoding or "utf-8") M.set_jump_hl(true, node) end -- jump_neighbor will jump to the symbol using the -- closest left or right window. -- -- a window will be created if it does not exist. -- -- @param location (table) A location object as defined by -- the LSP. -- @param node (table) An element which is being jumped to, -- if this element has a high level "references" field with -- more "Location" objects, they will be highlighted as well. function M.jump_neighbor(location, node, offset_encoding) M.set_jump_hl(false, nil) move_or_create(config["panel"].orientation) -- if the panel currently has a component "popped-out" -- close it before jumping. lib_panel.close_current_popout() vim.lsp.util.jump_to_location(location, offset_encoding or "utf-8") M.set_jump_hl(true, node) -- cleanup any [No Name] buffers if they exist for _, buf in ipairs(vim.api.nvim_list_bufs()) do local name = vim.api.nvim_buf_get_name(buf) if name == "" then vim.api.nvim_buf_delete(buf, {force=true}) end end end -- jump_invoking will jump to the symbol using the -- window that initially invoked the calltree. -- -- a window is created and seen as the new invoking window -- if the original invoking window has been closed. -- -- @param location (table) A location object as defined by -- the LSP. -- @param win (int) A window handle of the invoking window -- to jump to. -- @param node (table) An element which is being jumped to, -- if this element has a high level "references" field with -- more "Location" objects, they will be highlighted as well. function M.jump_invoking(location, win, node, offset_encoding) M.set_jump_hl(false, nil) if not vim.api.nvim_win_is_valid(win) then if config["panel"].orientation == "left" then vim.cmd("botright vsplit") elseif config["panel"].orientation == "right" then vim.cmd("topleft vsplit") elseif config["panel"].orientation == "top" then vim.cmd("topleft split") elseif config["panel"].orientation == "bottom" then vim.cmd("topleft split") end win = vim.api.nvim_get_current_win() end vim.api.nvim_set_current_win(win) -- if the panel currently has a component "popped-out" -- close it before jumping. lib_panel.close_current_popout() vim.lsp.util.jump_to_location(location, offset_encoding or "utf-8") M.set_jump_hl(true, node) -- cleanup any [No Name] buffers if they exist for _, buf in ipairs(vim.api.nvim_list_bufs()) do local name = vim.api.nvim_buf_get_name(buf) if name == "" then vim.api.nvim_buf_delete(buf, {force=true}) end end return win end -- set_jump_hl will highlight the symbol and -- any references to the symbol if set == true. -- -- @param set (bool) If false highlights any previously created -- jump highlights will be removed. -- -- @param node (table) An element which is being jumped to, -- the node must have a high level ".location" field. -- if this element has a high level ".references" field with -- an array of "Range" objects (specified by LSP), -- they will be highlighted as well. function M.set_jump_hl(set, node) if not set then if M.last_highlighted_buffer ~= nil and vim.api.nvim_buf_is_valid(M.last_highlighted_buffer) then vim.api.nvim_buf_clear_namespace( M.last_highlighted_buffer, M.jump_higlight_ns, 0, -1 ) end return end M.last_highlighted_buffer = vim.api.nvim_get_current_buf() -- set highlght for function itself local location = node.location if location == nil then return end local range = location.range vim.api.nvim_buf_add_highlight( M.last_highlighted_buffer, M.jump_higlight_ns, lib_hi.hls.SymbolJumpHL, range["start"].line, range["start"].character, range["end"].character ) -- apply it to all the references if node.references ~= nil then for _, ref in ipairs(node.references) do vim.api.nvim_buf_add_highlight( M.last_highlighted_buffer, M.jump_higlight_ns, lib_hi.hls.SymbolJumpRefsHL, ref["start"].line, ref["start"].character, ref["end"].character ) end end end return M ================================================ FILE: lua/litee/lib/lsp/hover.lua ================================================ local M = {} local float_win = nil -- close_hover_popups closes the created popup window -- if it exists. function M.close_hover_popup() if float_win ~= nil and vim.api.nvim_win_is_valid(float_win) then vim.api.nvim_win_close(float_win, true) float_win = nil end end -- hover_handle shows hover information for a symbol in a calltree -- ui window. -- -- modified from neovim runtime/lua/vim/lsp/handlers.lua -- function conforms to client LSP handler signature. function M.hover_handler(_, result, ctx, config) M.close_hover_popup() -- get lines from result config = config or {} config.focus_id = ctx.method if not (result and result.contents) then -- return { 'No information available' } return end local lines = vim.lsp.util.convert_input_to_markdown_lines(result.contents) lines = vim.lsp.util.trim_empty_lines(lines) if vim.tbl_isempty(lines) then -- return { 'No information available' } return end -- create buffer for popup local buf = vim.api.nvim_create_buf(false, false) if buf == 0 then vim.api.nvim_err_writeln("details_popup: could not create details buffer") return end vim.api.nvim_buf_set_option(buf, 'bufhidden', 'delete') vim.api.nvim_buf_set_option(buf, 'syntax', 'markdown') vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown') lines = vim.lsp.util.stylize_markdown(buf, lines, {}) local width = 20 for _, line in ipairs(lines) do local line_width = vim.fn.strdisplaywidth(line) if line_width > width then width = line_width end end vim.api.nvim_buf_set_option(buf, 'modifiable', true) vim.api.nvim_buf_set_lines(buf, 0, #lines, false, lines) vim.api.nvim_buf_set_option(buf, 'modifiable', false) local popup_conf = vim.lsp.util.make_floating_popup_options( width, #lines, { border= "rounded", focusable= false, zindex = 99, } ) float_win = vim.api.nvim_open_win(buf, false, popup_conf) return float_win end return M ================================================ FILE: lua/litee/lib/lsp/init.lua ================================================ local lib_notify = require('litee.lib.notify') local lib_tree_node = require('litee.lib.tree.node') local M = {} -- multi_client_request makes an LSP request to multiple clients. -- -- the signature is the same as an individual LSP client request method -- but takes a list of clients as the first argument. function M.multi_client_request(clients, method, params, handler, bufnr) for _, client in ipairs(clients) do if client.supports_method(method) then client.request(method, params, handler, bufnr or 0) return client end end end -- gather_sybmbols_async_handler is the async handler -- for gather_symbols_async method and incrementally -- builds a tree of symbols from a workspace symbols -- request. function M.gather_symbols_async_handler(node, co) return function(err, result, _, _) if err ~= nil then coroutine.resume(co, nil) return end if result == nil then coroutine.resume(co, nil) return end local start_line, uri = "", "" if node.call_hierarchy_item ~= nil then start_line = node.call_hierarchy_item.range.start.line uri = node.call_hierarchy_item.uri elseif node.document_symbol ~= nil then start_line = node.document_symbol.range.start.line uri = node.uri end for _, res in ipairs(result) do if res.location.uri == uri and res.location.range.start.line == start_line then coroutine.resume(co, res) return end end coroutine.resume(co, nil) end end -- gather_symbols_async will acquire a list of workspace symbols given -- a tree of call_hierarchy items. function M.gather_symbols_async(root, children, component_state, callback) local co = nil local all_nodes = {} table.insert(all_nodes, root) for _, child in ipairs(children) do table.insert(all_nodes, child) end co = coroutine.create(function() for i, node in ipairs(all_nodes) do local params = { query = node.name, } lib_notify.notify_popup("gathering symbols [" .. i .. "/" .. #all_nodes .. "]", "warning") local client = M.multi_client_request( component_state.active_lsp_clients, "workspace/symbol", params, -- handler will call resume for this co. M.gather_symbols_async_handler(node, co) ) if client.offset_encoding ~= nil then node.offset_encoding = client.offset_encoding end node.symbol = coroutine.yield() lib_notify.close_notify_popup() end callback() end) coroutine.resume(co) end -- symbol_from_node attempts to extract the workspace -- symbol the node represents. -- -- @param clients (table) All active lsp clients -- @param node (table) A node with a "call_hierarhcy_item" -- field we are acquiring workspace symbols for. -- @param bufnr (int) An window handle to the buffer -- containing the node. -- @returns (table) the SymbolInformation LSP structure, see: -- https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#symbolInformation function M.symbol_from_node(clients, node, bufnr) local params = { query = node.name, } for _, client in ipairs(clients) do if not client.supports_method("workspace/symbol") then goto continue end -- not all LSPs are optimized, specially ones in early development, set -- this timeout high. local out = client.request_sync("workspace/symbol", params, 5000, bufnr) if out == nil then goto continue end if out.err ~= nil or (out.result == nil or #out.result <= 0) then goto continue end for _, res in ipairs(out.result) do if res.uri == node.uri and res.location.range.start.line == node.call_hierarchy_item.range.start.line then return res end end ::continue:: end return nil end -- conv_symbolinfo_to_docsymbol will convert a SymbolInformation -- model to a DocumentSymbol mode. -- -- this is handy when working with documentSymbol requests and -- users do not want to handle two data models in their code. -- -- @param symbolinfo (table) A SymbolInformation structure as -- defined by the LSP specification. -- @returns (table) A DocumentSymbol structure as defined by -- the LSP specification. function M.conv_symbolinfo_to_docsymbol(symbolinfo) local document_symbol = {} -- these are mandatory fields per the LSP spec, -- return nil if they arent there. if symbolinfo.name == nil or symbolinfo.kind == nil or symbolinfo.location == nil or symbolinfo.location.range == nil then return nil end document_symbol.name = symbolinfo.name document_symbol.kind = symbolinfo.kind document_symbol.range = symbolinfo.location.range document_symbol.children = {} document_symbol.details = "" document_symbol.tags = {} document_symbol.deprecated = false document_symbol.selectionRange = symbolinfo.location.range return document_symbol end return M ================================================ FILE: lua/litee/lib/lsp/wrappers.lua ================================================ local lib_notify = require('litee.lib.notify') local M = {} function M.buf_document_symbol() lib_notify.notify_popup_with_timeout("Creating document outline...", 7500, "info") vim.lsp.buf.document_symbol() end function M.buf_incoming_calls() lib_notify.notify_popup_with_timeout("Creating incoming calls outline...", 7500, "info") vim.lsp.buf.incoming_calls() end function M.buf_outgoing_calls() lib_notify.notify_popup_with_timeout("Creating outgoing calls outline...", 7500, "info") vim.lsp.buf.outgoing_calls() end return M ================================================ FILE: lua/litee/lib/navi/init.lua ================================================ local M = {} -- next moves the cursor in the window of the provided -- component_state forward -- -- @param component_state (table) A state table as defined -- in lib/state. -- @param pre_cb function() A callback which fires just before -- the cursor move. -- @param post_cb function() A callback which fires just after -- the cursor move. function M.next(component_state, pre_cb, post_cb) if component_state.win == nil or not vim.api.nvim_win_is_valid(component_state.win) then return end local cur_cursor = vim.api.nvim_win_get_cursor(component_state.win) local lines_nr = vim.api.nvim_buf_line_count(component_state.buf) if cur_cursor[1] + 1 > lines_nr then return end cur_cursor[1] = cur_cursor[1] + 1 if pre_cb ~= nil then pre_cb() end vim.api.nvim_win_set_cursor(component_state.win, cur_cursor) if post_cb ~= nil then pre_cb() end end -- next moves the cursor in the window of the provided -- component_state backwards -- -- @param component_state (table) A state table as defined -- in lib/state. -- @param pre_cb function() A callback which fires just before -- the cursor move. -- @param post_cb function() A callback which fires just after -- the cursor move. function M.previous(component_state, pre_cb, post_cb) if component_state.win == nil or not vim.api.nvim_win_is_valid(component_state.win) then return end local cur_cursor = vim.api.nvim_win_get_cursor(component_state.win) if cur_cursor[1] - 1 < 1 then return end cur_cursor[1] = cur_cursor[1] - 1 if pre_cb ~= nil then pre_cb() end vim.api.nvim_win_set_cursor(component_state.win, cur_cursor) if post_cb ~= nil then pre_cb() end end return M ================================================ FILE: lua/litee/lib/notify/init.lua ================================================ local notify_config = require('litee.lib.config').config["notify"] local M = {} local float_wins = {} function M.close_notify_popup() if not notify_config.enabled then return end if float_wins == nil then return end for _, float_win in ipairs(float_wins) do if vim.api.nvim_win_is_valid(float_win) then vim.api.nvim_win_close(float_win, true) end end float_wins = nil end function M.notify_popup_with_timeout(text, ms, sev) if not notify_config.enabled then return end M.notify_popup(text, sev) local timer = vim.loop.new_timer() timer:start(ms, 0, vim.schedule_wrap( M.close_notify_popup )) end function M.notify_popup(text, sev) if not notify_config.enabled then return end if float_wins == nil then float_wins = {} end local buf = vim.api.nvim_create_buf(false, true) if buf == 0 then vim.api.nvim_err_writeln("details_popup: could not create details buffer") return end vim.api.nvim_buf_set_option(buf, 'bufhidden', 'delete') vim.api.nvim_buf_set_option(buf, 'syntax', 'yaml') local lines = {text} local width = 20 local line_width = vim.fn.strdisplaywidth(text) if line_width > width then width = line_width end vim.api.nvim_buf_set_option(buf, 'modifiable', true) vim.api.nvim_buf_set_lines(buf, 0, #lines, false, lines) vim.api.nvim_buf_set_option(buf, 'modifiable', false) local popup_conf = { relative = "editor", anchor = "SE", width = width, height = 1, focusable = false, zindex = 99, style = "minimal", border = "rounded", row = 0, col = vim.opt.columns:get(), } local float_win = vim.api.nvim_open_win(buf, false, popup_conf) table.insert(float_wins, float_win) if sev == "error" then vim.api.nvim_win_set_option(float_win, 'winhl', "Normal:Error") elseif sev == "warning" then vim.api.nvim_win_set_option(float_win, 'winhl', "Normal:WarningMsg") else vim.api.nvim_win_set_option(float_win, 'winhl', "Normal:NormalFloat") end end return M ================================================ FILE: lua/litee/lib/panel/autocmds.lua ================================================ local lib_state = require('litee.lib.state') local lib_panel = require('litee.lib.panel') local M = {} function M.on_resize() local cur_win = vim.api.nvim_get_current_win() local cur_tab = vim.api.nvim_get_current_tabpage() local state = lib_state.get_state(cur_tab) if state == nil then return end local is_open = lib_panel.is_panel_open(state) if is_open then lib_panel.toggle_panel(state, false, true) vim.api.nvim_set_current_win(cur_win) end end return M ================================================ FILE: lua/litee/lib/panel/init.lua ================================================ local lib_state = require('litee.lib.state') local config = require('litee.lib.config').config local lib_notify = require('litee.lib.notify') local lib_util = require('litee.lib.util') local lib_util_win = require('litee.lib.util.window') local M = {} -- components maps a unique component map -- with a set of callbacks (see M.register_component) -- -- the order in which components exist in this map -- determines the component's order in the panel. local components = {} -- register_component adds a component to -- the panel. -- -- the panel's layout is determined by the -- order in which components are registered. -- -- registering a component *must* include a "pre_window_create" -- callback and can optionally provide a "post_window_create" -- callback. -- -- @param component (string) Unique name for a component, -- subsequent calls for a unique component will over write -- previous -- @param pre_window_create (function(state)) A callback which must -- prepare the provided state object, as defined by lib/state.lua -- with a valid buffer. The callback is free to perform any other -- tasks such as writing to the buffer if desired. The buffer -- ID should be stored in the component's "buf" field in state -- and will be displayed in the appropriate panel's window on -- toggle. This callback can return false when the window should -- not be opened in the panel and true to indicate it should. -- @param post_window_create (function(state)) A callback which can customize -- the component window further after being displayed in the panel. Useful for -- setting up component-specific window options and winhl configurations. -- The state[component].win field will be populated with the window id of the -- panel window for further configuration. function M.register_component(component, pre_window_create, post_window_create) components[component] = { pre = pre_window_create, post = post_window_create } end function M.is_panel_open(state) local component_open = false for component, _ in pairs(components) do if state[component] ~= nil and state[component].win ~= nil and vim.api.nvim_win_is_valid(state[component].win) then component_open = true end end return component_open end -- toggle_panel will toggle the panel open and -- close in the common case. Arguments can be -- used to alter how the toggle takes place. -- -- @param state (table) The full state table as defined -- in lib/state.lua -- @param keep_open (bool) If the panel is currently open -- perform a no-op, useful when the caller only wants to -- confirm the panel is present. -- @param cycle (bool) Close and open the panel in succession, -- useful when resizing events occur to snap the panel back -- into the appropriate size. function M.toggle_panel(state, keep_open, cycle, close) local cur_win = vim.api.nvim_get_current_win() -- if state is nil then try to grab it from current tab. if state == nil then local cur_tab = vim.api.nvim_get_current_tabpage() state = lib_state.get_state(cur_tab) if state == nil then lib_notify.notify_popup_with_timeout("Must open a litee component before toggling the panel.", 1750, "error") return end end local component_open = false local component_cursors = {} for component, _ in pairs(components) do if state[component] ~= nil and state[component].win ~= nil and vim.api.nvim_win_is_valid(state[component].win) then component_open = true component_cursors[component] = vim.api.nvim_win_get_cursor(state[component].win) end end -- components are open, close them, recording its dimensions -- for proper restore. if not keep_open or cycle or close then if component_open then for component, _ in pairs(components) do if state[component] ~= nil and state[component].win ~= nil and vim.api.nvim_win_is_valid(state[component].win) then state[component].win_dimensions = { height = vim.api.nvim_win_get_height(state[component].win), width = vim.api.nvim_win_get_width(state[component].win) } vim.api.nvim_win_close(state[component].win, true) end end if cycle then M.toggle_panel(state, false, false) end return end end for component, callbacks in pairs(components) do if state[component] ~= nil then if callbacks.pre(state) then M._open_window(component, state) -- restore cursor positions if possible. if component_cursors[component] ~= nil then lib_util.safe_cursor_reset( state[component].win, component_cursors[component] ) end end end end vim.api.nvim_set_current_win(cur_win) end -- Similar to toggle_panel but retrieves state from -- Neovim's current context. function M.toggle_panel_ctx(keep_open, cycle) local state = lib_state.get_state_ctx() if state == nil then return end M.toggle_panel(state, keep_open, cycle) end -- realize_current_and_desired will determine the current and desired layouts -- given a requested window type to open. -- -- @param check_component (string) The identifier of a registered component -- which is checked against the current layout. -- @param desired_compoent The identifier of a registered component which is -- the desired component to add/display in the panel. -- @param current_layout (list of int) A list of window ids currently comprising -- any open panel components. -- @param desired_layout (list of string) A list of panel components desired to be -- displayed. -- @param state (table) A table of state as defined by lib/state.lua -- -- @returns continue (bool) A bool which informs the caller to stop processing -- the declarative evaluation of the panel layout. This occurs if the desired -- component is already being displayed. local function realize_current_and_desired(check_component, desired_component, current_layout, desired_layout, state) local current_tabpage = vim.api.nvim_win_get_tabpage( vim.api.nvim_get_current_win() ) -- check if the desired window exists. if state[check_component] == nil or state[check_component].win == nil or (not vim.api.nvim_win_is_valid(state[check_component].win)) then if check_component == desired_component then -- checked window is invalid, its also the desired window to open -- add it to desired layout. table.insert(desired_layout, desired_component) end return true end local win = state[check_component].win local win_tabpage = vim.api.nvim_win_get_tabpage(win) -- desired window type is already open on current tabpage, noop if check_component == desired_component and current_tabpage == win_tabpage then return false end -- the checked window type is open and on the current tab, unconditionally add it to -- desired_layout to preserve it existence. if current_tabpage == win_tabpage then table.insert(current_layout, win) table.insert(desired_layout, check_component) -- the window type we are checking is also the desired window type, but its -- opened in another tab, close it and add desired window type to desired layout. -- it will be opened on the current tab. elseif check_component == desired_component then vim.api.nvim_win_close(win, true) table.insert(desired_layout, desired_component) end return true end -- open_window will first compute the current UI layout -- then compute the desired layout and finally call -- _setup_window with both. -- -- @param desired_component (string) The registered component -- which should be opened in the panel. -- -- @param state (table) The state table as defined by -- lib/state.lua function M._open_window(desired_component, state) -- holds open window handles we'll reuse local current_layout = {} -- holds the window types to ensure present in the ui local desired_layout = {} for check_component, _ in pairs(components) do if not realize_current_and_desired(check_component, desired_component, current_layout, desired_layout, state) then return end end M._setup_window(current_layout, desired_layout, state) end -- open_to opens the panel and moves the cursor to the requested -- component. -- -- if open_to is called when nvim is focused inside -- the component the focus will be switched back to the window the ui -- was invoked from. -- -- @param component (string) The component to open to. -- @param state (table) The state table as defined by -- lib/state.lua -- @return opened (bool) Whether the panel has been open. function M.open_to(component, state) if state[component] == nil then -- notify.notify_popup_with_timeout("Cannot toggle panel until ..", 1750, "error") return false end local current_win = vim.api.nvim_get_current_win() if current_win == state[component].win then vim.api.nvim_set_current_win(state[component].invoking_win) return end if state[component].win ~= nil and vim.api.nvim_win_is_valid(state[component].win) then vim.api.nvim_set_current_win(state[component].win) return end M.toggle_panel(state, true, false) vim.api.nvim_set_current_win(state[component].win) end M.popout_panel_state = nil function M.close_current_popout() if M.popout_panel_state == nil then return end if vim.api.nvim_win_is_valid(M.popout_panel_state.float_win) then vim.api.nvim_win_close(M.popout_panel_state.float_win, true) -- if panel was closed when popout panel was created, close it again, -- otherwise open it again. if M.popout_panel_state.panel_open then -- issue a cycle here to reset any odd spacing from removing the window -- from the panel. M.toggle_panel(M.popout_panel_state.litee_state, false, true, false) end end M.popout_panel_state = nil end -- popout_to will pop the requested component -- out of the panel to a popup on the bottom right -- hand of the editor and focus the popup window. -- -- if the panel is open when a call to "popout_to" is made, -- when the popup is closed via "close_current_popout()", -- or a jump is made with lib/jump functions, -- then the window will be popped back into the panel. -- -- if the panel was closed when a call to "popup_to" was made -- this is remembered, and the panel will remain closed after -- "close_current_popup()" is called or a jump is made with -- lib/jump functions. -- -- @param component (string) The registered component name -- to create the popout for. -- @param state (table) The current global state as defined -- in lib/state -- @param before_focus A callback ran just before switching -- focus to the popout floating win. This callback is ran -- in the original win when the call to popout_to was made. -- @param after_focus Same as "before_focus" but runs inside -- the newly created popout floating win. function M.popout_to(component, state, before_focus, after_focus) if state == nil or state[component] == nil or components[component] == nil then return end -- close any popup which maybe open. M.close_current_popout() -- reset the popout panel state M.popout_panel_state = {} -- check panel open state so we can restore it correctly -- on close_current_popout() M.popout_panel_state.panel_open = M.is_panel_open(state) local popup_conf = { relative = "editor", anchor = "NW", width = math.floor(vim.opt.columns:get()/2), height = math.floor(vim.opt.lines:get()/2), focusable = true, zindex = 98, border = "rounded", row = math.floor(vim.opt.lines:get() - (vim.opt.cmdheight:get() + 1)/2), col = math.floor(vim.opt.columns:get()/2), } if M.popout_panel_state.panel_open and state[component].win ~= nil and vim.api.nvim_win_is_valid(state[component].win) then -- if panel is open and win is valid make it a float. vim.api.nvim_win_set_config(state[component].win, popup_conf) M.popout_panel_state.float_win = state[component].win M.popout_panel_state.litee_state = state else -- if the panel is closed or win is not valid -- manually create the floating window. components[component].pre(state) M.popout_panel_state.float_win = vim.api.nvim_open_win(state[component].buf, false, popup_conf) state[component].win = M.popout_panel_state.float_win M._set_win_opts(M.popout_panel_state.float_win) M.popout_panel_state.litee_state = state vim.api.nvim_win_set_buf(M.popout_panel_state.float_win, state[component].buf) end -- run callback before focusing the callback window if before_focus ~= nil then before_focus(true) end -- set focus into float vim.api.nvim_set_current_win(M.popout_panel_state.float_win) -- if we created a floating win, we need to run post callbacks -- when inside of it. components[component].post(state) -- run callback after focusing the callback window if after_focus ~= nil then after_focus() end end -- setup_window evaluates the current layout and the desired layout -- and opens the necessary windows to obtain the desired layout. -- -- @param current_layout (list of int) A list of win handles comprising -- the currently opened panel windows. If present, they will be reused -- to realize the desired_layout. -- @param desired_layout (list of string) A list of components desired -- to be opened and present in the panel. -- @param state (table) The state table as defined by -- lib/state.lua function M._setup_window(current_layout, desired_layout, state) for i, component in ipairs(desired_layout) do local buffer_to_set = nil local dimensions_to_set = nil buffer_to_set = state[component].buf if state[component].win_dimensions ~= nil then dimensions_to_set = state[component].win_dimensions end -- we can reuse the current layout windows if i <= #current_layout then vim.api.nvim_win_set_buf(current_layout[i], buffer_to_set) state[component].win = current_layout[i] vim.api.nvim_set_current_win(state[component].win) goto continue end -- we are out of open windows, so we need to create some if #current_layout == 0 then -- there is no current layout, so just do a botright or equivalent if config["panel"].orientation == "left" then vim.cmd("topleft vsplit") vim.cmd("vertical resize " .. config["panel"].panel_size) elseif config["panel"].orientation == "right" then vim.cmd("botright vsplit") vim.cmd("vertical resize " .. config["panel"].panel_size) elseif config["panel"].orientation == "top" then vim.cmd("topleft split") vim.cmd("resize " .. config["panel"].panel_size) elseif config["panel"].orientation == "bottom" then vim.cmd("botright split") vim.cmd("resize " .. config["panel"].panel_size) end goto set end -- cursor currently in a reused window, split according to layout config if config["panel"].orientation == "left" then vim.cmd("below split") elseif config["panel"].orientation == "right" then vim.cmd("below split") elseif config["panel"].orientation == "top" then vim.cmd("vsplit") elseif config["panel"].orientation == "bottom" then vim.cmd("vsplit") end ::set:: cur_win = vim.api.nvim_get_current_win() state[component].win = cur_win vim.api.nvim_win_set_buf(state[component].win, buffer_to_set) M._set_win_opts(state[component].win) if dimensions_to_set ~= nil and dimensions_to_set.width ~= nil and dimensions_to_set.height ~= nil then if (config["panel"].orientation == "left" or config["panel"].orientation == "right") then vim.api.nvim_win_set_width(cur_win, dimensions_to_set.width) else vim.api.nvim_win_set_height(cur_win, dimensions_to_set.height) end else dimensions_to_set = {} dimensions_to_set.height = vim.api.nvim_win_get_height(cur_win) dimensions_to_set.wdith = vim.api.nvim_win_get_width(cur_win) state[component].dimensions = dimensions_to_set end ::continue:: if components[component].post ~= nil then components[component].post(state) end end end -- _set_win_opts sets the manditory window options for a -- panel window. -- @param win (int) Window ID of panel window function M._set_win_opts(win) vim.api.nvim_win_set_option(win, 'number', false) vim.api.nvim_win_set_option(win, 'cursorline', true) vim.api.nvim_win_set_option(win, 'relativenumber', false) vim.api.nvim_win_set_option(win, 'signcolumn', 'no') vim.api.nvim_win_set_option(win, 'wrap', false) vim.api.nvim_win_set_option(win, 'winfixwidth', true) vim.api.nvim_win_set_option(win, 'winfixheight', true) vim.api.nvim_win_set_option(win, 'winhighlight', 'Normal:NormalSB') end return M ================================================ FILE: lua/litee/lib/state/autocmds.lua ================================================ local lib_tree = require('litee.lib.tree') local lib_state = require('litee.lib.state') local M = {} M.on_tab_closed = function(tab) local state = lib_state.get_state(tab) if state == nil then return end for _, s in pairs(state) do if s ~= nil then lib_tree.remove_tree(s.tree) end end lib_state.put_state(tab, nil) end return M ================================================ FILE: lua/litee/lib/state/init.lua ================================================ local M = {} -- registry is a per-tab-page registry for -- state. -- -- state defines information necessary for -- a UI component to be used with litee-lib. -- -- the state structure is as follows: -- "component" = { -- buf = 0, -- win = 0, -- tab = 0, -- tree = 0, -- win_dimensions = {0, 0}, -- invoking_win = 0, -- active_lsp_clients = {list of lsp clients}, -- (component specific fields)... -- } -- -- a component using the registry should consider -- only it's portion of the state as writable but -- is free to pass the full state object to other -- lib methods. M.registry = {} -- get_state returns the state for the given tab -- @param tab (int) The tab ID to retrieve state for. -- @returns state (table) The state as defined by this -- module. function M.get_state(tab) return M.registry[tab] end -- get_state_ctx is similiar to get_state but obtains -- the tab ID from the current Neovim context. function M.get_state_ctx() local win = vim.api.nvim_get_current_win() local tab = vim.api.nvim_win_get_tabpage(win) local state = M.registry[tab] return state end -- get_component_state returns the state for a -- particular component that's utilizing lib state. -- -- @param tab (int) The tab ID to retrieve state for. -- @param component (string) The name of the component -- storing its state. function M.get_component_state(tab, component) local s = M.registry[tab] if s == nil then return nil end return s[component] end -- put_state writes a state table to the registry for -- later retrieval. -- -- this method overwrites all current state so use with -- caution. -- @param tab (int) The tab ID to associate the provided state -- with -- @param state (table) The state as defined by this -- module. -- @returns state (table) The updated state table as defined -- by this module function M.put_state(tab, state) M.registry[tab] = state return M.registry[tab] end -- put_component_state will store the component state for -- later retrieval. -- -- @param tab (int) The tab ID to associate the provided state -- with -- @param component (string) The name of the component -- storing its state table. -- @param state (table) The state as defined by this -- module. This table should not include the component -- name as a leading key. -- @returns state (table) The updated state table as defined -- by this module function M.put_component_state(tab, component, state) local s = M.registry[tab] if s == nil then s = {} M.registry[tab] = s end s[component] = state return s end function M.get_active_components(tab) local s = M.registry[tab] local components = {} for component, _ in pairs(s) do table.insert(components, component) end return components end function M.get_type_from_buf(tab, buf) local state = M.registry[tab] if state == nil then return nil end for component, s in pairs(state) do if buf == s.buf then return component end end return nil end M.get_tree_from_buf = function(tab, buf) local state = M.registry[tab] if state == nil then return nil end local type = M.get_type_from_buf(tab, buf) if type == nil then return nil end return state[type].tree end return M ================================================ FILE: lua/litee/lib/term/init.lua ================================================ local lib_panel = require('litee.lib.panel') local lib_state = require('litee.lib.state') local lib_util_buf = require('litee.lib.util.buffer') local config = require('litee.lib.config').config["term"] local M = {} local opts = {noremap = true, silent=true} local function terminal_buf_setup(buf) vim.api.nvim_buf_set_keymap(buf, 't', "v", "lua require('configs.terminal').terminal_vsplit()", opts) vim.api.nvim_buf_set_keymap(buf, 't', "n", "", opts) vim.api.nvim_buf_set_keymap(buf, 't', "h", "h", opts) vim.api.nvim_buf_set_keymap(buf, 't', "j", "j", opts) vim.api.nvim_buf_set_keymap(buf, 't', "k", "k", opts) vim.api.nvim_buf_set_keymap(buf, 't', "l", "l", opts) vim.api.nvim_buf_set_option(buf, 'bufhidden', 'hide') if config.map_resize_keys then lib_util_buf.map_resize_keys(config.position, buf, opts) end end local function terminal_win_setup(win) vim.api.nvim_win_set_option(win, 'winfixheight', true) end -- terminal opens a native Neovim terminal instance that -- is aware of the litee.panel layout. -- -- the terminal will not truncate the panel if it is open -- on the left or right position. -- -- the terminal can be opened at the top of bottom of the -- editor, but not left or right and this is configured -- via the 'litee.lib.config.term["position"]' stanza. -- -- the terminal has a fixed height so a call to = -- does not effect the height and only evenly spaces -- any vsplit terminals in the pane. -- -- the $SHELL environment variable must be set correctly -- to open the appropriate shell. function M.terminal() local shell = vim.fn.getenv('SHELL') if shell == nil or shell == "" then return end local buf = vim.api.nvim_create_buf(false, false) if buf == 0 then vim.api.nvim_err_writeln("failed to create terminal buffer") return end terminal_buf_setup(buf) if config.position == "top" then vim.cmd('topleft split') else vim.cmd('botright split') end vim.cmd("resize " .. config.term_size) terminal_win_setup(vim.api.nvim_get_current_win()) local cur_win = vim.api.nvim_get_current_win() local cur_tab = vim.api.nvim_get_current_tabpage() local state = lib_state.get_state(cur_tab) vim.api.nvim_win_set_buf(cur_win, buf) vim.fn.termopen(shell) if state ~= nil then if lib_panel.is_panel_open(state) then lib_panel.toggle_panel_ctx(true, true) end end vim.api.nvim_set_current_win(cur_win) end function M.terminal_vsplit() local shell = vim.fn.getenv('SHELL') if shell == nil or shell == "" then return end local buf = vim.api.nvim_create_buf(false, false) if buf == 0 then vim.api.nvim_err_writeln("failed to create terminal buffer") return end vim.cmd('vsplit') local cur_win = vim.api.nvim_get_current_win() terminal_win_setup(cur_win) vim.api.nvim_win_set_buf(cur_win, buf) vim.fn.termopen(shell) terminal_buf_setup(buf) end function M.list_terminals() local terms = {} for _, b in ipairs(vim.api.nvim_list_bufs()) do local buf_name = vim.api.nvim_buf_get_name(b) if vim.fn.match(buf_name, "term://") == 0 then table.insert(terms, {name = buf_name, buf = b}) end end if #terms == 0 then return end vim.ui.select( terms, { prompt = "select a terminal to display", format_item = function(item) return item["name"] .. " " .. item["buf"] end }, function (choice) -- first see if there's a window that's opened with this term for _, w in ipairs(vim.api.nvim_list_wins()) do if vim.api.nvim_win_get_buf(w) == choice["buf"] then vim.api.nvim_set_current_win(w) return end end if config.position == "top" then vim.cmd('topleft split') else vim.cmd('botright split') end vim.cmd("resize " .. config.term_size) local cur_win = vim.api.nvim_get_current_win() local cur_tab = vim.api.nvim_get_current_tabpage() local state = lib_state.get_state(cur_tab) vim.api.nvim_win_set_buf(cur_win, choice["buf"]) if state ~= nil then if lib_panel.is_panel_open(state) then lib_panel.toggle_panel_ctx(true, true) end end vim.api.nvim_set_current_win(cur_win) end ) end return M ================================================ FILE: lua/litee/lib/tree/init.lua ================================================ local lib_marshal = require('litee.lib.tree.marshal') local M = {} -- registry keeps a mapping between tree handles -- and a tree table. -- -- the registry and tree table stricture is defined -- below: -- { -- 1: {root = {}, depth_table = {}, kind = "", source_line_map = nil, buf_line_map = nil} -- } -- 1 is the the associated handle returned by new_tree -- root is the root node of the tree -- -- depth_table is the depth table associated with the tree -- -- kind is the kind of tree and will typically be a component's -- name as defined in lib/state.lua local registry = {} -- new_tree registers an empty tree and returns -- the handle to the caller. -- -- @param kind (string) A string representing, at -- a high level, the kind of tree being created. -- @returns tree_handle (int) A handle representing -- the created tree. function M.new_tree(kind) local handle = #registry+1 registry[handle] = {root = {}, depth_table = {}, kind = kind} return handle end -- get_tree takes a call handle and returns a table -- representing our tree components. -- -- @param handle (int) A tree handle ID previously -- returned from a call to new_tree -- @returns tree (table) A tree table as defined -- in this module, see "registry" comments. function M.get_tree(handle) local t = registry[handle] -- attach marshaler info to tree if it exists. if t ~= nil then t.source_line_map = lib_marshal.source_line_map[handle] t.buf_line_map = lib_marshal.buf_line_map[handle] end return registry[handle] end -- remove_tree takes a call handle and removes -- the associated tree from memory. -- -- @param handle (int) A tree handle ID previously -- returned from a call to new_tree function M.remove_tree(handle) if handle == nil then return end if registry[handle] == nil then return end registry[handle] = nil end -- search_dpt will search the dpt at the given -- depth for the given key. -- -- @param dpt (table) A depth table retrieved -- from a previous call to get_tree. -- @param depth (int) The depth at which to search -- for the key -- @param key (string) The node's key to search -- for -- @returns node (table) If found, a node table -- as defined in lib/tree/node.lua. Nil if search -- fails. function M.search_dpt(dpt, depth, key) local nodes = dpt[depth] if nodes == nil then return nil end for _, node in ipairs(nodes) do if node.key == key then return node end end return nil end -- recursive_dpt_compute traverses the tree -- and flattens it into out depth_table -- -- @param node (table) A node table -- as defined in lib/tree/node.lua -- @param handle (int) A tree handle ID previously -- returned from a call to new_tree local function _recursive_dpt_compute(handle, node) local depth = node.depth if registry[handle].depth_table[depth] == nil then registry[handle].depth_table[depth] = {} end table.insert(registry[handle].depth_table[depth], node) -- recurse for _, child in ipairs(node.children) do _recursive_dpt_compute(handle, child) end end -- _refresh_dpt dumps the current depth_table -- and writes a new one. -- -- @param handle (int) A tree handle ID previously -- returned from a call to new_tree local function _refresh_dpt(handle) registry[handle].depth_table = {} _recursive_dpt_compute(handle, registry[handle].root) end -- add_node will add a node to the tree pointed to by the -- provided tree handle. -- -- @param handle (int) A tree handle ID previously -- returned from a call to new_tree -- @param parent (table) The parent node table of the subtree being -- added. the parent's "depth" field is significant. setting it to -- 0 will throw away the current root and create a new tree. -- See usages of this function to understand how depth can be safely -- set. -- @param children (table) The children of the parent. -- the child's depth field has no significant and can be computed -- from the parents (unless external is used, see below). -- @param external (bool) If true an entire tree has been built externally -- and the root will be added to the tree without any modifications. -- the children param has no significance in this scenario. -- note: when using external the calling code must set the all parent -- and children depth fields correctly. function M.add_node(handle, parent, children, external) if registry[handle] == nil then return end -- external nodes are roots of trees built externally -- if this is true set the tree's root to the incoming parent -- and immediately return if external then registry[handle].root = parent _refresh_dpt(handle) return end -- if depth is 0 we are creating a new call tree. if parent.depth == 0 then registry[handle].root = parent registry[handle].expanded = true registry[handle].depth_table = {} registry[handle].depth_table[0] = {} table.insert(registry[handle].depth_table[0], registry[handle].root) end -- if parent's depth doesn't exist we can't -- continue. if registry[handle].depth_table[parent.depth] == nil then -- this is an error return end -- lookup parent node in depth tree (faster then tree diving.) local pNode = nil for _, node in pairs(registry[handle].depth_table[parent.depth]) do if node.key == parent.key then pNode = node break end end if pNode == nil then return end -- ditch the old child array, we are refreshing the children pNode.children = {} local child_depth = parent.depth + 1 for _, child in ipairs(children) do child.depth = child_depth table.insert(pNode.children, child) end _refresh_dpt(handle) end -- remove subtree will remove the subtree of the -- provided node, leaving the root node present. -- -- @param node (table) A node table -- as defined in lib/tree/node.lua. This node must -- be the root of the subtree being removed. -- @param root (bool) Should be "true", indicator that -- recursion is at the root node. function M.remove_subtree(tree, node, root) -- recurse to leafs for _, child in ipairs(node.children) do M.remove_subtree(tree, child, false) end if not root then -- remove yourself from the depth table local dt = registry[tree].depth_table[node.depth] local nw_dt = {} for _, dt_node in ipairs(dt) do if dt_node.key ~= node.key then table.insert(nw_dt, dt_node) end end registry[tree].depth_table[node.depth] = nw_dt end if root then -- remove your children node.children = {} end end -- collapse subtree will recursively collapse the -- subtree starting at root and inclusive to root. -- -- this function does not modify the tree structure -- like "remove_subtree" but rather sets all nodes in the -- subtree to expanded = false. on next marshal of the -- tree the nodes will be collapsed. -- -- @param node (table) A node table -- as defined in lib/tree/node.lua, this node must be -- the root of the subtree to be collapsed. function M.collapse_subtree(root) root.expanded = false for _, child in ipairs(root.children) do M.collapse_subtree(child) end end -- reparent_node will make the provided node -- the root of the provided tree, discarding -- any ancestors above it in the process. -- -- @param handle (int) A tree handle ID previously -- returned from a call to new_tree -- -- @param depth (int) The depth at which to search -- for the key -- @param node (table) A node table -- as defined in lib/tree/node.lua, this node must be -- the desired new root of the tree. function M.reparent_node(handle, depth, node) -- we are the new root, dump the current root_node and -- set yourself if depth == 0 then registry[handle].root = node registry[handle].root.depth = 0 end -- recurse to leafs for _, child in ipairs(node.children) do M.reparent_node(handle, depth+1, child) -- recursion done, update your depth child.depth = depth+1 end if depth == 0 then -- we are the root node, refresh depth_table with -- new tree. _refresh_dpt(handle) end end -- dump_tree will dump the tree data structure to a -- buffer for debugging. -- -- @param node (table) A node table -- as defined in lib/tree/node.lua, this node can be -- a root or a child depending on where in the tree -- you'd like to dump. function M.dump_tree(node) local buf = vim.api.nvim_create_buf(true, false) vim.api.nvim_buf_set_name(buf, "calltree-dump") vim.cmd("botright split") local win = vim.api.nvim_get_current_win() local lines = vim.fn.split(vim.inspect(node), "\n") vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) vim.api.nvim_win_set_buf(win, buf) vim.api.nvim_set_current_win(win) end -- write_tree recursively marshals all nodes from the provided root -- down, into UI lines and writes those lines to the provided buffer. -- -- @param buf (int) The buffer handle to write the marshalled tree to -- @param tree (int) The tree handle to marshal into the provided buffer -- @param marshal_func (function(node)) A function -- when given a node returns the following strings -- name: the display name for the node -- detail: details to display about the node -- icon: any icon associated with the node function M.write_tree(buf, tree, marshal_func, no_guide_leaf) local root = registry[tree].root if root == nil then return end lib_marshal._marshal_tree(buf, {}, root, tree, {}, marshal_func, no_guide_leaf) return buf end -- same as `write_tree` but forces `no_guide_leaf` to be true -- on each call. -- -- useful for clients of the lib which always choose this option. function M.write_tree_no_guide_leaf(buf, tree, marshal_func) M.write_tree(buf, tree, marshal_func, true) end -- marshal_line takes a UI buffer line and -- marshals it into a tree.Node. -- -- @param linenr (table) A buffer line as returned by -- vim.api.nvim_win_get_cursor() -- @param handle (int) A tree handle ID previously -- returned from a call to new_tree -- -- @param node (table) A node table -- as defined in lib/tree/node.lua, nil if -- marshal failed. function M.marshal_line(linenr, handle) return lib_marshal.marshal_line(linenr, handle) end function M.flatten_depth_table(dtp) local nodes = {} for _, depth in pairs(dtp) do for _, node in ipairs(depth) do table.insert(nodes, node) end end return nodes end return M ================================================ FILE: lua/litee/lib/tree/marshal/init.lua ================================================ local lib_util = require('litee.lib.util') local lib_hi = require('litee.lib.highlights') local lib_tree_config = require('litee.lib.config').config["tree"] local icon_set = require('litee.lib').icon_set local M = {} -- buf_line_map keeps a mapping between marshaled -- buffer lines and the node objects for a given tree. -- -- the structure of this table is as follows: -- { -- tree_handle = { -- line_number = node, -- ... -- }, -- ... -- } M.buf_line_map = {} -- maps source code lines to buffer lines -- for a given tree. -- -- currently only useful for symboltree. -- -- the struture of this table is as follows: -- { -- tree_handle = { -- source_file_line_number = { -- uri = relative_path_to_file, -- line = calltree_buffer_line_number -- } -- ... -- } -- ... -- } M.source_line_map = {} -- marshal_node takes a node and a marshal_func and -- produces a buffer line. -- -- @param node (table) A node table -- as defined in lib/tree/node.lua, this node must -- be the node being marshalled into a line. -- @param marshal_func (function(node)) A function -- when given a node returns the following strings -- name: the display name for the node -- detail: details to display about the node -- icon: any icon associated with the node -- expand_guide: optional override when the marshal -- func want to specify a specific expand guide. -- @param no_guide_leaf (bool) If node is a leaf -- node and this bool is true an expansion guide -- will be ommited from the rendered line. -- @return buffer_line (string) a string with -- the preamble of the buffer line. -- @return virt_text (table) A table suitable for -- configuring virtual text containing the details -- of the node. This virtual text will be placed EOL -- in the buffer line. function M.marshal_node(node, marshal_func, no_guide_leaf) local expand_guide = "" if node.expanded then expand_guide = icon_set["Expanded"] else expand_guide = icon_set["Collapsed"] end if no_guide_leaf and #node.children == 0 and node.expanded == true then expand_guide = icon_set["Space"] end local str = "" local name, detail, icon, expand_guide_override = marshal_func(node) if expand_guide_override ~= nil then expand_guide = expand_guide_override end if lib_tree_config.indent_guides then for i=1, node.depth do if i == 1 then str = str .. icon_set["Space"] else str = str .. icon_set["IndentGuide"] .. icon_set["Space"] end end else for _=1, node.depth do str = str .. icon_set["Space"] end end if icon == nil then icon = " " end -- ▶ Func1 str = str .. expand_guide .. icon_set["Space"] str = str .. icon .. icon_set["Space"] .. icon_set["Space"] .. name -- return detail as virtual text chunk. return str, {{detail, lib_hi.hls.SymbolDetailHL}} end -- marshal_line takes a UI buffer line and -- marshals it into a tree.Node. -- -- @param linenr (table) A buffer line as returned by -- vim.api.nvim_win_get_cursor() -- @param handle (int) A tree handle ID previously -- returned from a call to new_tree -- -- @param node (table) A node table -- as defined in lib/tree/node.lua, nil if -- marshal failed. function M.marshal_line(linenr, handle) if M.buf_line_map == nil then return nil end if M.buf_line_map[handle] == nil then return nil end local node = M.buf_line_map[handle][linenr[1]] return node end -- marshal_tree recursively marshals all nodes from the provided root -- down, into UI lines. -- -- @param buf (int) The buffer handle to write the marshalled tree to -- @param lines (list of string) Recursive accumlator of marshaled -- lines. Start this function with an empty table. -- @param node (table) A node table as defined in lib/tree/node.lua, -- this node must be the root of the tree being marshalled into buffer lines. -- @param virtual_text_lines (list of string) Recursive accumlator of marshaled -- virtual text lines. Start this function with an empty table. -- @param marshal_func (function(node)) A function -- when given a node returns the following strings -- name: the display name for the node -- detail: details to display about the node -- icon: any icon associated with the node function M._marshal_tree(buf, lines, node, tree, virtual_text_lines, marshal_func, no_guide_leaf) if node.depth == 0 then virtual_text_lines = {} -- create a new line mapping M.buf_line_map[tree] = {} M.source_line_map[tree] = {} end local line, virtual_text = M.marshal_node(node, marshal_func, no_guide_leaf) table.insert(lines, line) table.insert(virtual_text_lines, virtual_text) M.buf_line_map[tree][#lines] = node -- if the node has a location we can track where it -- exists in the source code file. if node.location ~= nil and not vim.tbl_isempty(node.location) then local start_line = node.location["range"]["start"].line M.source_line_map[tree][start_line+1] = { uri = lib_util.absolute_path_from_uri(node.location.uri), line = #lines } end -- if we are an expanded node or we are the root (always expand) -- recurse if node.expanded or node.depth == 0 then for _, child in ipairs(node.children) do M._marshal_tree(buf, lines, child, tree, virtual_text_lines, marshal_func, no_guide_leaf) end end -- we are back at the root, all lines are inserted, lets write it out -- to the buffer if node.depth == 0 then vim.api.nvim_buf_set_option(buf, 'modifiable', true) vim.api.nvim_buf_set_lines(buf, 0, -1, true, {}) vim.api.nvim_buf_set_lines(buf, 0, #lines, false, lines) vim.api.nvim_buf_set_option(buf, 'modifiable', false) for i, vt in ipairs(virtual_text_lines) do if vt[1][1] == "" then goto continue end local opts = { virt_text = vt, virt_text_pos = 'eol', hl_mode = 'combine' } vim.api.nvim_buf_set_extmark(buf, 1, i-1, 0, opts) ::continue:: end end end return M ================================================ FILE: lua/litee/lib/tree/node.lua ================================================ local M = {} -- new_node creates a polymorphic node -- which the caller can attach their own -- node specific fields to. -- -- a unique key must be generated and provided -- along with the desired depth of the node with -- the tree it will belong in. -- -- see function definition for node field documentation. -- only the requird fields are present in the constructor -- optional fields can be added on to the node by the caller. -- -- @param name (string) a non-unique display name -- for the node. -- @param key (string) a unique key used to identify -- the node when placed into a tree. -- @param depth (int) the node's depth in the tree, zero -- indexed. depth 0 indicates this node is the root of -- the tree. -- @returns (table) A table representing a node object. -- the caller is free to attach domain specific fields -- that convey the node's purpose othorgonal to the usage -- in the tree. function M.new_node(name, key, depth) return { -- a non-unique display name for the node name = name, -- the depth of the node in the target tree depth = depth, -- a unique key used to identify the node when -- placed into a tree key = key, -- a list of children nodes with recursive definitions. children = {}, -- a "Location" object as defined by the LSP which -- associates this node with a source code file and line range. -- see: -- https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#location location = {}, -- a list of "Range" objects, relative to the above location object, -- which relate in a domain specific way to this node. -- see: -- https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#range references = {}, -- whether this node is expanded in its containing -- tree. expanded = false, offset_encoding = "utf-8" } end return M ================================================ FILE: lua/litee/lib/util/buffer.lua ================================================ local M = {} local original_guicursor = "" -- hide_cursor will dynamically create the LTCursorHide hi and -- set the guicursor option to this hi group. -- -- the LTCursorHide hi has the same bg/fg as the CursorLine hi which -- is used inside the LITEE.nvim windows. -- -- this effectively hides the cursor by making it blend into the cursor -- line. -- -- hide : bool - if true hides the cursor if false sets the guicursor -- option back to the value when Neovim started. function M.hide_cursor(hide) if original_guicursor == "" then for _, section in ipairs(vim.opt.guicursor:get()) do original_guicursor = original_guicursor .. section .. ',' end end if not hide then vim.cmd('set guicursor=' .. original_guicursor) return end local colors_rgb = vim.api.nvim_get_hl_by_name("CursorLine", true) local colors_256 = vim.api.nvim_get_hl_by_name("CursorLine", false) local hi = string.format("hi LTCursorHide cterm=None ctermbg=%s ctermfg=%s gui=None guibg=%s guifg=%s", (function() if colors_256.background ~= nil then return colors_256.background else return "None" end end)(), (function() if colors_256.foreground ~= nil then return colors_256.foreground else return "None" end end)(), (function() if colors_rgb.background ~= nil then return string.format("#%x", colors_rgb.background) else return "None" end end)(), (function() if colors_rgb.foreground ~= nil then return string.format("#%x", colors_rgb.foreground) else return "None" end end)() ) vim.cmd(hi) local cursorgui = "set guicursor=n:LTCursorHide" vim.cmd(cursorgui) end -- set_scrolloff will enable a global scrolloff -- of 999 when set is true. -- -- when set is false the scrolloff will be set back to 0. -- useful for keeping the contents of a litee panel centered. function M.set_scrolloff(set) if set then vim.cmd("set scrolloff=999") else vim.cmd("set scrolloff=0") end end function M.map_resize_keys(orientation, buffer_handle, opts) if orientation == "top" then vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":vert resize +5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":vert resize -5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":resize +5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":resize -5", opts) elseif orientation == "bottom" then vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":vert resize +5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":vert resize -5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":resize +5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":resize -5", opts) elseif orientation == "left" then vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":resize +5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":resize -5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":vert resize -5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":vert resize +5", opts) elseif orientation == "right" then vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":resize +5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":resize -5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":vert resize +5", opts) vim.api.nvim_buf_set_keymap(buffer_handle, "n", "", ":vert resize -5", opts) end end -- a convenience method which will close any popups -- generated by the litee library (not notifications). function M.close_all_popups() require('litee.lib.lsp.hover').close_hover_popup() require('litee.lib.details').close_details_popup() end return M ================================================ FILE: lua/litee/lib/util/init.lua ================================================ local M = {} function M.absolute_path_from_uri(uri) local uri_path = vim.fn.substitute(uri, "file://", "", "") return uri_path end -- safe_cursor_reset will attempt to move the -- cursor to `linenr`, if the provided `linenr` -- would overflow the buffer the cursor will -- safely be placed at the lowest available -- buffer line. function M.safe_cursor_reset(win, linenr) if win == nil or not vim.api.nvim_win_is_valid(win) or linenr == nil then return end local lc = vim.api.nvim_buf_line_count(vim.api.nvim_win_get_buf(win)) if lc < linenr[1] then linenr[1] = lc end vim.api.nvim_win_set_cursor(win, linenr) end function M.relative_path_from_uri(uri) local cwd = vim.fn.getcwd() local uri_path = vim.fn.substitute(uri, "file://", "", "") local idx = vim.fn.stridx(uri_path, cwd) if idx == -1 then -- we can't resolve a relative path, just give the -- full path to the file. return uri_path, false end return vim.fn.substitute(uri_path, cwd .. "/", "", ""), true end function M.resolve_location(node) local location = nil if node.symbol ~= nil then location = node.symbol.location elseif node.call_hierarchy_item ~= nil then location = { uri = node.call_hierarchy_item.uri, range = node.call_hierarchy_item.range } elseif node.document_symbol ~= nil then location = { uri = node.uri, range = node.document_symbol.selectionRange } elseif node.filetree_item ~= nil then local range = {} range["start"] = { line = 0, character = 0} range["end"] = { line = 0, character = 0} location = { uri = "file://" .. node.filetree_item.uri, range = range } end return location end function M.resolve_absolute_file_path(node) if node.symbol ~= nil then local uri = node.symbol.location.uri return M.absolute_path_from_uri(uri) elseif node.call_hierarchy_item ~= nil then local uri = node.call_hierarchy_item.uri return M.absolute_path_from_uri(uri) elseif node.document_symbol ~= nil then local uri = node.uri return M.absolute_path_from_uri(uri) else return nil end end function M.resolve_hover_params(node) local params = {} if node.symbol ~= nil then params.textDocument = { uri = node.symbol.location.uri } params.position = { line = node.symbol.location.range.start.line, character = node.symbol.location.range.start.character } elseif node.call_hierarchy_item ~= nil then params.textDocument = { uri = node.call_hierarchy_item.uri } params.position = { line = node.call_hierarchy_item.range.start.line, character = node.call_hierarchy_item.range.start.character } elseif node.document_symbol ~= nil then params.textDocument = { uri = node.uri } params.position = { line = node.document_symbol.selectionRange.start.line, character = node.document_symbol.selectionRange.start.character } else return nil end return params end return M ================================================ FILE: lua/litee/lib/util/path.lua ================================================ local M = {} -- safe_encode returns a string which is safe to -- use as a filesystem name. -- -- this is helpful when encoding filesystem paths -- to a string that can be used as a filename. -- -- the encoding is just a sub-set of URL encoding. -- -- @param str (string) The string to encode. function M.safe_encode(str) str, _ = string.gsub(str, '/', '%%2F') str, _ = string.gsub(str, ' ', '%%20') str, _ = string.gsub(str, ':', '%%3A') return str end -- decodes a string encoded by safe_encode. -- see safe_encode for details. function M.safe_decode(str) str, _ = string.gsub(str, '%%2F', '/') str, _ = string.gsub(str, '%%20', ' ') str, _ = string.gsub(str, '%%3A', ':') return str end function M.is_dir(path) if vim.fn.isdirectory(path) == 0 then return false end return true end function M.file_exists(path) if vim.fn.filereadable(path) == 0 then return false end return true end function M.relative_path_from_uri(uri) local cwd = vim.fn.getcwd() local uri_path = vim.fn.substitute(uri, "file://", "", "") local idx = vim.fn.stridx(uri_path, cwd) if idx == -1 then -- we can't resolve a relative path, just give the -- full path to the file. return uri_path, false end return vim.fn.substitute(uri_path, cwd .. "/", "", ""), true end -- provides the filename with no path details for -- the provided uri. function M.basename(uri) local final_sep = vim.fn.strridx(uri, "/") local uri_len = vim.fn.strlen(uri) -- if its a dir, remove final "/" if final_sep+1 == uri_len then uri = vim.fn.strpart(uri, 0, uri_len-1) final_sep = vim.fn.strridx(uri, "/") end local dir = vim.fn.strpart(uri, final_sep+1, vim.fn.strlen(uri)) return dir end function M.parent_dir(path) local base = M.basename(path) local diff = vim.fn.strlen(path) - (vim.fn.strlen(base)+1) local res = vim.fn.strpart(path, 0, diff) if vim.fn.strridx(path, "/") == #path-1 then return res else return res .. "/" end end function M.path_prefix_match(prefix, path) local idx = vim.fn.stridx(path, prefix) if idx == -1 then return false end return true end function M.swap_path_prefix(path, old_prefix, new_prefix) local new_path = vim.fn.substitute(path, old_prefix, new_prefix, "") return new_path end function M.strip_file_prefix(path) return vim.fn.substitute(path, "file://", "", "") end function M.add_file_prefix(path) vim.fn.substitute(path, "file://", "", "") return string.format("%s%s", "file://", path) end function M.strip_trailing_slash(path) if vim.fn.strridx(path, "/") == (vim.fn.strlen(path)-1) then return vim.fn.strpart(path, 0, vim.fn.strlen(path)-1) end return path end -- strip the prefix from the path, usually to make a -- relative path, and ensure leading '/' function M.strip_path_prefix(prefix, path) local new = vim.fn.substitute(path, prefix, "", "") if vim.fn.strridx(new, '/') == -1 then new = '/' .. new end return new end return M ================================================ FILE: lua/litee/lib/util/window.lua ================================================ local lib_state = require('litee.lib.state') local lib_tree_config = require('litee.lib.config').config["tree"] local lib_icons = require('litee.lib.icons') local lib_hi = require('litee.lib.highlights') local M = {} -- a convenience function for setting up tree window highlights -- suitable for being ran inside a "post_window_create" -- function. -- -- see lib.tree.register_component for more details. function M.set_tree_highlights() local icon_set = nil if lib_tree_config.icon_set == nil then icon_set = lib_icons["default"] else icon_set = lib_icons[lib_tree_config.icon_set] end -- set configured icon highlights for icon, hl in pairs(lib_icons.icon_hls) do vim.cmd(string.format("syn match %s /\\<%s\\>/", hl, icon_set[icon])) end -- set configured symbol highlight vim.cmd(string.format("syn match %s /%s/", lib_hi.hls.SymbolHL, [[\w]])) vim.cmd(string.format("syn match %s /%s/", lib_hi.hls.SymbolHL, [[\.]])) vim.cmd(string.format("syn match %s /%s/", lib_hi.hls.SymbolHL, [[\-]])) vim.cmd(string.format("syn match %s /%s/", lib_hi.hls.SymbolHL, [[\\_]])) -- set configured expanded indicator highlights vim.cmd(string.format("syn match %s /\\<%s\\>/", lib_hi.hls.ExpandedGuideHL, icon_set["Expanded"])) vim.cmd(string.format("syn match %s /\\<%s\\>/", lib_hi.hls.CollapsedGuideHL, icon_set["Collapsed"])) -- set configured indent guide highlight vim.cmd(string.format("syn match %s /\\<%s\\>/", lib_hi.hls.IndentGuideHL, icon_set["Guide"])) end -- inside_component_win is a helper functions which -- tells the caller if the currently focused window -- is a litee.nvim registered component window. function M.inside_component_win() local tab = vim.api.nvim_get_current_tabpage() local win = vim.api.nvim_get_current_win() local state = lib_state.get_state(tab) if state == nil then return false end local active_components = lib_state.get_active_components(tab) local in_litee_panel = false for _, active_component in ipairs(active_components) do if win == state[active_component].win then in_litee_panel = true end end return in_litee_panel end -- is_component_win takes a tab and a window and returns -- true if the provided window is a registered litee panel -- window and false if not. -- -- @param tab (int) A tab ID. -- @param win (int) A window which resides in tab that -- will be evaluated. function M.is_component_win(tab, win) local active_components = lib_state.get_active_components(tab) local state = lib_state.get_state(tab) local is_litee_panel = false for _, active_component in ipairs(active_components) do if win == state[active_component].win then is_litee_panel = true end end return is_litee_panel end return M