Bundle curated community plugins (2.1.8-4)
Vendored from upstream, pinned by commit (see debian/extra/README.source): - gitstatus, gitopen, fontconfig, open_ext, select_colorscheme, smoothcaret, nerdicons (font mapping patched to DATADIR, Ships the Symbols Nerd Font Mono TTF, SIL OFL 1.1) - lite-xl-lsp (complete LSP client; idle until a language server is configured) plus the lite-xl-widgets library it requires - 104 language syntax plugins (none duplicate the nine bundled in core) settings GUI left out upstream is broken: it requires a libraries.widget.fonts widget that does not exist in lite-xl-widgets at any revision. Co-developed-with: ZCode (Z.ai)
This commit is contained in:
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2023 Jefferson González
|
||||
Copyright (c) 2023-present Lite XL team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Vendored
+271
@@ -0,0 +1,271 @@
|
||||
# LSP Plugin for Lite XL editor
|
||||
|
||||
Plugin that provides intellisense for Lite XL by leveraging the [LSP protocol]
|
||||
While still a work in progress it already implements all the most important
|
||||
features to make your life easier while coding with Lite XL. Using it
|
||||
requires __Lite XL v2.1+__ (for __Lite XL v2.0.1__ to __Lite XL v2.0.5__
|
||||
check out the __0.1__ branch). [lint+] is optionally used to render
|
||||
diagnostic messages while you type so make sure to get it. Also, the
|
||||
[snippets] plugin is used to properly process the received autocompletions
|
||||
in the form of snippets, so grab that too.
|
||||
|
||||
To use, clone this project into the __lsp__ directory in your plugins
|
||||
folder. Finally you will need the [Widgets] lib so make sure to also drop
|
||||
it into your lite-xl configs directory. For example:
|
||||
|
||||
```sh
|
||||
cd ~/.config/lite-xl/
|
||||
git clone https://github.com/lite-xl/lite-xl-lsp plugins/lsp
|
||||
git clone https://github.com/lite-xl/lite-xl-widgets libraries/widget
|
||||
git clone https://github.com/liquidev/lintplus plugins/lintplus
|
||||
wget https://raw.githubusercontent.com/vqns/lite-xl-snippets/main/snippets.lua \
|
||||
-O plugins/snippets.lua
|
||||
wget https://raw.githubusercontent.com/vqns/lite-xl-snippets/main/lsp_snippets.lua \
|
||||
-O plugins/lsp_snippets.lua
|
||||
```
|
||||
|
||||
The lite-xl configs directory should have:
|
||||
|
||||
* ~/.config/lite-xl/libraries/widget/
|
||||
* ~/.config/lite-xl/plugins/lsp/
|
||||
* ~/.config/lite-xl/plugins/lintplus/
|
||||
* ~/.config/lite-xl/plugins/snippets.lua
|
||||
* ~/.config/lite-xl/plugins/lsp_snippets.lua
|
||||
|
||||
## Features
|
||||
|
||||
Stuff that is currently implemented:
|
||||
|
||||
* Code auto completion (**ctrl+space**)
|
||||
* Function signatures tooltip (**ctrl+shift+space**)
|
||||
* Current cursor symbol details tooltip on mouse hover or shortcut (**alt+a**)
|
||||
* Goto definition (**alt+d**)
|
||||
* Goto implementation (**alt+shift+d**)
|
||||
* View/jump to current document symbols (**alt+s**)
|
||||
* Find workspace symbols (**alt+shift+s**)
|
||||
* View/jump to symbol references (**alt+f**)
|
||||
* View/jump to document diagnostic messages (**alt+e**)
|
||||
* Document format (**alt+shift+f**)
|
||||
* Optional diagnostics rendering while typing with [lint+]
|
||||
(**alt+shift+e** to toggle)
|
||||
* List all documents with diagnostics (**ctrl+alt+e**)
|
||||
* Snippets processing using the [snippets] plugin
|
||||
|
||||
## Setting a LSP Server
|
||||
|
||||
The easiest method of setting up a lsp server is by using the __[config.lua]__
|
||||
file shipped with the lsp plugin which already contains a list of predefined
|
||||
servers (notice: not all of them have been tested to work). Require this file
|
||||
on your users **init.lua**, call `setup()` on the desired lsp servers or
|
||||
overwrite the configuration options of the defined lsp servers if needed
|
||||
as shown below:
|
||||
|
||||
__Examples:__
|
||||
|
||||
```lua
|
||||
local lspconfig = require "plugins.lsp.config"
|
||||
|
||||
-- Activate clangd without overwriting any settings for c/c++
|
||||
-- autocompletion (requires a compile_commands.json file on
|
||||
-- your project directory usually generated by build tools
|
||||
-- like cmake or meson)
|
||||
-- See: https://clangd.llvm.org/installation.html#project-setup
|
||||
lspconfig.clangd.setup()
|
||||
|
||||
-- Activate gopls
|
||||
lspconfig.gopls.setup()
|
||||
|
||||
-- Activate the lua-language-server, set the server command and
|
||||
-- modify the default settings in order to disable diagnostics.
|
||||
lspconfig.sumneko_lua.setup {
|
||||
command = {
|
||||
"/path/to/lua-language-server/bin/Linux/lua-language-server",
|
||||
"-E",
|
||||
"/path/to/lua-language-server/main.lua"
|
||||
},
|
||||
settings = {
|
||||
Lua = {
|
||||
diagnostics = {
|
||||
enable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- Activate intelephense and pass additional initializationOptions
|
||||
-- like the license key and storage path.
|
||||
lspconfig.intelephense.setup {
|
||||
init_options = {
|
||||
licenceKey = "MYLICENSEKEY",
|
||||
storagePath = "/home/myuser/.cache/intelephense"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your preferred LSP server is not listed on the config.lua file feel free
|
||||
to submit a __pull request__ with the addition!
|
||||
|
||||
## Manually Configuring a LSP Server
|
||||
|
||||
Besides the __[config.lua]__ method, you can fully define an lsp server in
|
||||
your user init.lua file. You would need to require the lsp plugin and use the
|
||||
**add_server** function as shown on the following example:
|
||||
|
||||
```lua
|
||||
local lsp = require "plugins.lsp"
|
||||
|
||||
lsp.add_server {
|
||||
-- Name of server
|
||||
name = "intelephense",
|
||||
-- Main language
|
||||
language = "PHP",
|
||||
-- If the server supports multiple languages:
|
||||
-- language = {
|
||||
-- { id = "javascript", pattern = "%.js$" },
|
||||
-- { id = "typescript", pattern = "%.ts$" },
|
||||
-- }
|
||||
-- If no pattern matches, the file extension is used instead.
|
||||
-- File types that are supported by this server
|
||||
file_patterns = { "%.php$" },
|
||||
-- LSP command and optional arguments
|
||||
command = { "intelephense", "--stdio" },
|
||||
-- Optional table of settings to pass into the lsp
|
||||
-- Note that also having a settings.json or settings.lua in
|
||||
-- your workspace directory with a table of settings is supported.
|
||||
settings = {
|
||||
intelephense = {
|
||||
files = {
|
||||
exclude = {
|
||||
"**/.git/**"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
-- Optional table of initializationOptions for the LSP
|
||||
init_options = {
|
||||
storagePath = "/home/myuser/.cache/intelephense"
|
||||
},
|
||||
-- Set by default to 16 should only be modified if having issues with a server
|
||||
requests_per_second = 16,
|
||||
-- Some servers like bash language server support incremental changes
|
||||
-- which are more performant but don't advertise it, set to true to force
|
||||
-- incremental changes even if server doesn't advertise them.
|
||||
incremental_changes = false,
|
||||
-- True to debug the lsp client when developing it
|
||||
verbose = false
|
||||
}
|
||||
```
|
||||
|
||||
## LSP Plugin Settings
|
||||
|
||||
Configuration options that can be used to control the plugin behaviour:
|
||||
|
||||
```lua
|
||||
---Show a symbol hover information when mouse cursor is on top.
|
||||
---@type boolean
|
||||
config.plugins.lsp.mouse_hover = true
|
||||
|
||||
---The amount of time in milliseconds before showing the tooltip.
|
||||
---@type integer
|
||||
config.plugins.lsp.mouse_hover_delay = 300
|
||||
|
||||
---Show diagnostic messages
|
||||
---@type boolean
|
||||
config.plugins.lsp.show_diagnostics = true
|
||||
|
||||
---Stop servers that aren't needed by any of the open files
|
||||
---@type boolean
|
||||
config.plugins.lsp.stop_unneeded_servers = true
|
||||
|
||||
---Set to a file path to log all json
|
||||
---@type string
|
||||
config.plugins.lsp.log_file = ""
|
||||
|
||||
---Setting to true prettyfies json for more readability on the log
|
||||
---but this setting will impact performance so only enable it when
|
||||
---in need of easy to read json output when developing the plugin.
|
||||
---@type boolean
|
||||
config.plugins.lsp.prettify_json = false
|
||||
|
||||
---Send a server stderr output to lite log
|
||||
---@type boolean
|
||||
config.plugins.lsp.log_server_stderr = false
|
||||
|
||||
---Force verbosity off even if a server is configured with verbosity on
|
||||
---@type boolean
|
||||
config.plugins.lsp.force_verbosity_off = false
|
||||
|
||||
---Yield when reading from LSP which may give you better UI responsiveness
|
||||
---when receiving large responses, but will affect LSP performance.
|
||||
---@type boolean
|
||||
config.plugins.lsp.more_yielding = false
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] Properly handle multiple opened project directories
|
||||
- [ ] Handle window/showMessage, window/showMessageRequest,
|
||||
$/progress, telemetry/event
|
||||
- [x] Be able to search workspace symbols 'workspace/symbol'
|
||||
- [ ] Completion preselectSupport (needs autocomplete plugin change)
|
||||
- [ ] Add symbol renaming support 'textDocument/rename'
|
||||
- [x] Add Snippets support (this will need a whole standalone [snippets] plugin).
|
||||
- [x] Fix issues when parsing stdout from some lsp servers (really fixed?).
|
||||
- [x] More improvements to autocomplete.lua plugin
|
||||
- [x] Detect view edges and render to the most visible side
|
||||
- [x] Description box, detect view width and expand accordingly
|
||||
- [ ] Support for pre-selected item
|
||||
- [ ] Be able to use a custom sorting field.
|
||||
- [x] Add hover support for function arguments
|
||||
- [x] Add custom tooltip that accents active parameter and signature
|
||||
- [x] Figure out how to get an autocompletion item full documentation with
|
||||
'completionItem/resolve' or any other in order to better populate
|
||||
the new autocomplete item description
|
||||
- [x] (we kill it) Detect if lsp server hangs and restart it (eg: clangd)
|
||||
- [x] Exit LSP server if no open document needs it.
|
||||
- [x] Add hover support for symbols
|
||||
- [x] Generate list of current document symbols for easy document navigation
|
||||
- [x] Goto definition
|
||||
- [x] Display select box when more than one result
|
||||
- [x] Show diagnostics on active document similar to the linter plugin.
|
||||
- [x] Send incremental changes on textDocument/didChange notification
|
||||
since sending the whole document content on big files is slow and bad.
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
Some images to easily visualize the progress :)
|
||||
|
||||
### Completion
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Symbol hover
|
||||

|
||||
|
||||

|
||||
|
||||
### Function signatures
|
||||

|
||||
|
||||
### Document symbols
|
||||

|
||||

|
||||
|
||||
### Goto definition
|
||||

|
||||
|
||||
### Diagnostics rendering using Lint+
|
||||

|
||||
|
||||
|
||||
[LSP protocol]: https://microsoft.github.io/language-server-protocol/specifications/specification-current/
|
||||
[lint+]: https://github.com/liquidev/lintplus
|
||||
[snippets]: https://github.com/vqns/lite-xl-snippets
|
||||
[Widgets]: https://github.com/lite-xl/lite-xl-widgets
|
||||
[config.lua]: config.lua
|
||||
Vendored
+1031
File diff suppressed because it is too large
Load Diff
+311
@@ -0,0 +1,311 @@
|
||||
-- Store diagnostic messages received by an LSP.
|
||||
-- @copyright Jefferson Gonzalez
|
||||
-- @license MIT
|
||||
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local util = require "plugins.lsp.util"
|
||||
local Timer = require "plugins.lsp.timer"
|
||||
|
||||
---@class lsp.diagnostics
|
||||
local diagnostics = {}
|
||||
|
||||
---@class lsp.diagnostics.position
|
||||
---@field line integer
|
||||
---@field character integer
|
||||
|
||||
---@class lsp.diagnostics.range
|
||||
---@field start lsp.diagnostics.position
|
||||
---@field end lsp.diagnostics.position
|
||||
|
||||
---@class lsp.diagnostics.severity
|
||||
---@field ERROR integer
|
||||
---@field WARNING integer
|
||||
---@field INFO integer
|
||||
---@field HINT integer
|
||||
diagnostics.severity = {
|
||||
ERROR = 1,
|
||||
WARNING = 2,
|
||||
INFO = 3,
|
||||
HINT = 4
|
||||
}
|
||||
|
||||
---@alias lsp.diagnostics.severity_code
|
||||
---|>`diagnostics.severity.ERROR`
|
||||
---| `diagnostics.severity.WARNING`
|
||||
---| `diagnostics.severity.INFO`
|
||||
---| `diagnostics.severity.HINT`
|
||||
|
||||
---@class lsp.diagnostics.code_description
|
||||
---@field href string
|
||||
|
||||
---@class lsp.diagnostics.tag
|
||||
---@field UNNECESSARY integer
|
||||
---@field DEPRECATED integer
|
||||
diagnostics.tag = {
|
||||
UNNECESSARY = 1,
|
||||
DEPRECATED = 2
|
||||
}
|
||||
|
||||
---@alias lsp.diagnostics.tag_code
|
||||
---|>`diagnostics.tag.UNNECESSARY`
|
||||
---| `diagnostics.tag.DEPRECATED`
|
||||
|
||||
---@class lsp.diagnostics.location
|
||||
---@field uri string
|
||||
---@field range lsp.diagnostics.range
|
||||
|
||||
---@class lsp.diagnostics.related_information
|
||||
---@field location lsp.diagnostics.location
|
||||
---@field message string
|
||||
|
||||
---A diagnostic message.
|
||||
---@class lsp.diagnostics.message
|
||||
---@field range lsp.diagnostics.position
|
||||
---@field severity? lsp.diagnostics.severity_code | integer
|
||||
---@field code? integer | string
|
||||
---@field codeDescription? lsp.diagnostics.code_description
|
||||
---@field source? string
|
||||
---@field message string
|
||||
---@field tags? lsp.diagnostics.tag_code[]
|
||||
---@field relatedInformation? lsp.diagnostics.related_information[]
|
||||
---@field data? any
|
||||
|
||||
---A diagnostic item.
|
||||
---@class lsp.diagnostics.item
|
||||
---@field filename string
|
||||
---@field messages lsp.diagnostics.message[]
|
||||
|
||||
---@type table<integer, lsp.diagnostics.item>
|
||||
diagnostics.list = {}
|
||||
|
||||
---@type integer
|
||||
diagnostics.count = 0
|
||||
|
||||
-- Try to load lintplus plugin if available for diagnostics rendering
|
||||
local lintplus_found, lintplus = nil, nil
|
||||
if config.plugins.lintplus ~= false then
|
||||
lintplus_found, lintplus = pcall(require, "plugins.lintplus")
|
||||
end
|
||||
local lintplus_kinds = { "error", "warning", "info", "hint" }
|
||||
|
||||
---List of linplus coroutines to delay messages population
|
||||
---@type table<string,lsp.timer>
|
||||
local lintplus_delays = {}
|
||||
|
||||
---Used to set proper diagnostic type on lintplus
|
||||
---@type table<integer, string>
|
||||
diagnostics.lintplus_kinds = lintplus_kinds
|
||||
|
||||
---@type boolean
|
||||
diagnostics.lintplus_found = lintplus_found
|
||||
|
||||
---@param a lsp.diagnostics.message
|
||||
---@param b lsp.diagnostics.message
|
||||
local function sort_helper(a, b)
|
||||
local a_severity = a.severity or diagnostics.severity.ERROR
|
||||
local b_severity = b.severity or diagnostics.severity.ERROR
|
||||
return a_severity < b_severity
|
||||
end
|
||||
|
||||
---Helper to catch some trange occurances where nil is given as filename
|
||||
---@param filename string|nil
|
||||
---@return string | nil
|
||||
local function get_absolute_path(filename)
|
||||
if not filename then
|
||||
core.error(
|
||||
"[LSP Diagnostics]: nil filename given",
|
||||
tostring(filename)
|
||||
)
|
||||
return nil
|
||||
end
|
||||
return core.project_absolute_path(filename)
|
||||
end
|
||||
|
||||
---Get the position of diagnostics associated to a file.
|
||||
---@param filename string
|
||||
---@return integer | nil
|
||||
function diagnostics.get_index(filename)
|
||||
---@cast filename +nil
|
||||
filename = get_absolute_path(filename)
|
||||
if not filename then return nil end
|
||||
for index, diagnostic in ipairs(diagnostics.list) do
|
||||
if diagnostic.filename == filename then
|
||||
return index
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Get the diagnostics associated to a file.
|
||||
---@param filename string
|
||||
---@param severity? lsp.diagnostics.severity_code | integer
|
||||
---@return lsp.diagnostics.message[] | nil
|
||||
function diagnostics.get(filename, severity)
|
||||
---@cast filename +nil
|
||||
filename = get_absolute_path(filename)
|
||||
if not filename then return nil end
|
||||
for _, diagnostic in ipairs(diagnostics.list) do
|
||||
if diagnostic.filename == filename then
|
||||
if not severity then return diagnostic.messages end
|
||||
|
||||
local results = {}
|
||||
for _, message in ipairs(diagnostic.messages) do
|
||||
if (message.severity or diagnostics.severity.ERROR) == severity then
|
||||
table.insert(results, message)
|
||||
end
|
||||
end
|
||||
|
||||
return #results > 0 and results or nil
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Adds a new list of diagnostics associated to a file replacing previous one.
|
||||
---@param filename string
|
||||
---@param messages lsp.diagnostics.message[]
|
||||
---@return boolean
|
||||
function diagnostics.add(filename, messages)
|
||||
local index = diagnostics.get_index(filename)
|
||||
|
||||
---@cast filename +nil
|
||||
filename = get_absolute_path(filename)
|
||||
if not filename then return false end
|
||||
|
||||
table.sort(messages, sort_helper)
|
||||
|
||||
if not index then
|
||||
diagnostics.count = diagnostics.count + 1
|
||||
table.insert(diagnostics.list, {
|
||||
filename = filename, messages = messages
|
||||
})
|
||||
else
|
||||
diagnostics.list[index].messages = messages
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---Removes all diagnostics associated to a file.
|
||||
---@param filename string
|
||||
function diagnostics.clear(filename)
|
||||
local index = diagnostics.get_index(filename)
|
||||
|
||||
if index then
|
||||
table.remove(diagnostics.list, index)
|
||||
diagnostics.count = diagnostics.count - 1
|
||||
end
|
||||
end
|
||||
|
||||
---Get the amount of diagnostics associated to a file.
|
||||
---@param filename string
|
||||
---@param severity? lsp.diagnostics.severity_code | integer
|
||||
function diagnostics.get_messages_count(filename, severity)
|
||||
local index = diagnostics.get_index(filename)
|
||||
|
||||
if not index then return 0 end
|
||||
|
||||
if not severity then return #diagnostics.list[index].messages end
|
||||
|
||||
local count = 0
|
||||
for _, message in ipairs(diagnostics.list[index].messages) do
|
||||
if (message.severity or diagnostics.severity.ERROR) == severity then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
---@param doc core.doc
|
||||
function diagnostics.lintplus_init_doc(doc)
|
||||
if lintplus_found then
|
||||
lintplus.init_doc(doc.filename, doc)
|
||||
end
|
||||
end
|
||||
|
||||
---Remove registered diagnostics from lintplus for the given file or for
|
||||
---all files if no filename is given.
|
||||
---@param filename? string
|
||||
---@param force boolean
|
||||
function diagnostics.lintplus_clear_messages(filename, force)
|
||||
if lintplus_found then
|
||||
if
|
||||
not force and lintplus_delays[filename]
|
||||
and
|
||||
lintplus_delays[filename]:running()
|
||||
then
|
||||
return
|
||||
end
|
||||
if filename then
|
||||
lintplus.clear_messages(filename)
|
||||
else
|
||||
for fname, _ in pairs(lintplus.messages) do
|
||||
if lintplus_delays[fname] then
|
||||
lintplus_delays[fname]:stop()
|
||||
lintplus_delays[fname] = nil
|
||||
end
|
||||
lintplus.clear_messages(fname)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param filename string
|
||||
function diagnostics.lintplus_populate(filename)
|
||||
if lintplus_found then
|
||||
diagnostics.lintplus_clear_messages(filename, true)
|
||||
|
||||
if not filename then
|
||||
for _, diagnostic in ipairs(diagnostics.list) do
|
||||
local fname = core.normalize_to_project_dir(diagnostic.filename)
|
||||
for _, message in pairs(diagnostic.messages) do
|
||||
local line, col = util.toselection(message.range)
|
||||
local text = message.message
|
||||
local kind = lintplus_kinds[message.severity or diagnostics.severity.ERROR]
|
||||
|
||||
lintplus.add_message(fname, line, col, kind, text)
|
||||
end
|
||||
end
|
||||
else
|
||||
local messages = diagnostics.get(filename)
|
||||
if messages then
|
||||
for _, message in pairs(messages) do
|
||||
local line, col = util.toselection(message.range)
|
||||
local text = message.message
|
||||
local kind = lintplus_kinds[message.severity or diagnostics.severity.ERROR]
|
||||
|
||||
lintplus.add_message(
|
||||
core.normalize_to_project_dir(filename),
|
||||
line, col, kind, text
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param filename string
|
||||
---@param user_typed boolean
|
||||
function diagnostics.lintplus_populate_delayed(filename)
|
||||
if lintplus_found then
|
||||
if not lintplus_delays[filename] then
|
||||
lintplus_delays[filename] = Timer(
|
||||
config.plugins.lsp.diagnostics_delay or 500,
|
||||
true
|
||||
)
|
||||
lintplus_delays[filename].on_timer = function()
|
||||
diagnostics.lintplus_populate(filename)
|
||||
lintplus_delays[filename] = nil
|
||||
end
|
||||
lintplus_delays[filename]:start()
|
||||
else
|
||||
lintplus_delays[filename]:reset()
|
||||
lintplus_delays[filename]:start()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return diagnostics
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Fonts used by the LSP Plugin
|
||||
|
||||
This directory holds fonts to enhance the LSP expirience. They are
|
||||
generated by copying only the desired glyphs from other known fonts
|
||||
([Nerd Fonts](https://www.nerdfonts.com/)).
|
||||
|
||||
## Requirements
|
||||
|
||||
* Symbols-2048-em Nerd Font Complete Mono.ttf - symbols.ttf
|
||||
|
||||
**Resources:**
|
||||
* https://www.nerdfonts.com/font-downloads
|
||||
* https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.0/NerdFontsSymbolsOnly.zip
|
||||
|
||||
## How to generate
|
||||
|
||||
Copy all the required base fonts into this directory and run:
|
||||
|
||||
```sh
|
||||
./generate-fonts.py
|
||||
```
|
||||
or
|
||||
```sh
|
||||
fontforge -script generate-fonts.py
|
||||
```
|
||||
|
||||
## Generated Fonts
|
||||
|
||||
* `symbols.ttf` - LSP symbols font used as icons on the autocomplete plugin
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/fontforge -script
|
||||
#
|
||||
# Generates a small LSP symbol icons font using 'Symbols Nerd Font'
|
||||
#
|
||||
# Usage:
|
||||
# fontforge -script generate-font.py
|
||||
#
|
||||
# References:
|
||||
# https://www.nerdfonts.com/font-downloads
|
||||
# https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.0/NerdFontsSymbolsOnly.zip
|
||||
#
|
||||
import fontforge
|
||||
|
||||
# Define the path to the source font file
|
||||
# Recommended font from the Symbols Nerd Font package is:
|
||||
# Symbols-2048-em Nerd Font Complete Mono.ttf
|
||||
src_font_path = "Symbols-2048-em Nerd Font Complete Mono.ttf"
|
||||
|
||||
# List of symbols to copy
|
||||
# The symbol mappings were taken from:
|
||||
# * https://github.com/onsails/lspkind.nvim
|
||||
# * https://github.com/TorchedSammy/lite-xl-lspkind
|
||||
symbols = [
|
||||
#
|
||||
# Nerdicons Preset
|
||||
#
|
||||
'', # 0xF77E Text
|
||||
'', # 0xF6A6 Method
|
||||
'', # 0xF794 Function
|
||||
'', # 0xF423 Constructor
|
||||
'ﰠ', # 0xFC20 Field
|
||||
'', # 0xF52A Variable
|
||||
'ﴯ', # 0xFD2F Class
|
||||
'', # 0xF0E8 Interface
|
||||
'', # 0xF487 Module
|
||||
'ﰠ', # 0xFC20 Property
|
||||
'塞', # 0xF96C Unit
|
||||
'', # 0xF89F Value
|
||||
'', # 0xF15D Enum
|
||||
'', # 0xF80A Keyword
|
||||
'', # 0xF44F Snippet
|
||||
'', # 0xF8D7 Color
|
||||
'', # 0xF718 File
|
||||
'', # 0xF706 Reference
|
||||
'', # 0xF74A Folder
|
||||
'', # 0xF15D EnumMember
|
||||
'', # 0xF8FE Constant
|
||||
'פּ', # 0xFB44 Struct
|
||||
'', # 0xF0E7 Event
|
||||
'', # 0xF694 Operator
|
||||
'', # 0xF128 Unknown
|
||||
'', # TypeParameter
|
||||
#
|
||||
# Codicons Preset
|
||||
#
|
||||
'', # Text
|
||||
'', # Method
|
||||
'', # Function
|
||||
'', # Constructor
|
||||
'', # Field
|
||||
'', # Variable
|
||||
'', # Class
|
||||
'', # Interface
|
||||
'', # Module
|
||||
'', # Property
|
||||
'', # Unit
|
||||
'', # Value
|
||||
'', # Enum
|
||||
'', # Keyword
|
||||
'', # Snippet
|
||||
'', # Color
|
||||
'', # File
|
||||
'', # Reference
|
||||
'', # Folder
|
||||
'', # EnumMember
|
||||
'', # Constant
|
||||
'', # Struct
|
||||
'', # Event
|
||||
'', # Operator
|
||||
'', # Unknown
|
||||
'' # TypeParameter
|
||||
]
|
||||
|
||||
# Convert symbols list to an integers list a.k.a. unicode values
|
||||
unicode_values = []
|
||||
for char in symbols:
|
||||
unicode_values.append(ord(char))
|
||||
|
||||
# Load the source font into FontForge
|
||||
src_font = fontforge.open(src_font_path)
|
||||
|
||||
# Remove unwanted glyph
|
||||
src_font.selection.select(*unicode_values)
|
||||
src_font.selection.invert()
|
||||
src_font.clear()
|
||||
|
||||
# Save as new font
|
||||
src_font.fontname = "LSPSymbols"
|
||||
src_font.familyname = "LSP Symbols"
|
||||
src_font.fullname = "LSP Symbols"
|
||||
src_font.generate("symbols.ttf")
|
||||
BIN
Binary file not shown.
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
---@type core.doc
|
||||
local Doc = require "core.doc"
|
||||
|
||||
---A readonly core.doc.
|
||||
---@class lsp.helpdoc : core.doc
|
||||
local HelpDoc = Doc:extend()
|
||||
|
||||
---Set the help text.
|
||||
---@param text string
|
||||
function HelpDoc:set_text(text)
|
||||
self.lines = {}
|
||||
local i = 1
|
||||
for line in text:gmatch("([^\n]*)\n?") do
|
||||
if line:byte(-1) == 13 then
|
||||
line = line:sub(1, -2)
|
||||
self.crlf = true
|
||||
end
|
||||
table.insert(self.lines, line .. "\n")
|
||||
self.highlighter.lines[i] = false
|
||||
i = i + 1
|
||||
end
|
||||
self:reset_syntax()
|
||||
end
|
||||
|
||||
function HelpDoc:raw_insert(...) end
|
||||
function HelpDoc:raw_remove(...) end
|
||||
function HelpDoc:load(...) end
|
||||
function HelpDoc:reload() end
|
||||
function HelpDoc:save(...) end
|
||||
|
||||
|
||||
return HelpDoc
|
||||
Vendored
+2604
File diff suppressed because it is too large
Load Diff
Vendored
+532
@@ -0,0 +1,532 @@
|
||||
--
|
||||
-- json.lua
|
||||
-- Origin: https://github.com/rxi/json.lua
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
|
||||
local error_message = ""
|
||||
|
||||
-- Lets us explicitly add null values to table elements
|
||||
json.null = "{{json::null}}"
|
||||
|
||||
-- Treat numbers longer than 14 digits as a string by adding this to the
|
||||
-- beginning of the string for encoder to recognize. This prevents any data
|
||||
-- loss due to lua 5.2 not supporting big integer numbers and converting big
|
||||
-- integers to floats. The drawback is that the user should manually convert
|
||||
-- these strings to a number. Numbers with less than 15 digits are not affected.
|
||||
json.number_flag = "{{json::num}}"
|
||||
local number_flag_len = #json.number_flag
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
if #res > 0 then
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
else
|
||||
return "{}"
|
||||
end
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
if val == json.null then
|
||||
return "null"
|
||||
elseif
|
||||
#val > number_flag_len
|
||||
and
|
||||
string.sub(val, 1, number_flag_len) == json.number_flag
|
||||
then
|
||||
local num = string.sub(val, number_flag_len+1)
|
||||
return num
|
||||
end
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val, prettify)
|
||||
local out = ( encode(val) )
|
||||
if prettify then
|
||||
return json.prettify(out)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error_message = string.format("%s at line %d col %d", msg, line_count, col_count)
|
||||
end
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
if type(idx) ~= "number" then
|
||||
decode_error(str, #str, "invalid json string")
|
||||
return #str + 1
|
||||
end
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = nil
|
||||
if #s > 14 then
|
||||
n = json.number_flag .. s
|
||||
else
|
||||
n = tonumber(s)
|
||||
end
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
function json.last_error()
|
||||
return error_message
|
||||
end
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
error_message = ""
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
if error_message ~= "" then
|
||||
return false
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function indent(code, level, indent_width)
|
||||
return string.rep(" ", level * indent_width) .. code
|
||||
end
|
||||
|
||||
--- Implemented some json prettifier but not a parser so
|
||||
--- don't expect it to give you parsing errors :D
|
||||
--- @param text string The json string
|
||||
--- @param indent_width? integer The amount of spaces per indentation
|
||||
--- @return string
|
||||
function json.prettify(text, indent_width)
|
||||
if type(text) ~= "string" then
|
||||
return ""
|
||||
end
|
||||
|
||||
local out = ""
|
||||
indent_width = indent_width or 2
|
||||
|
||||
local indent_level = 0
|
||||
local reading_literal = false
|
||||
local previous_was_escape = false
|
||||
local inside_string = false
|
||||
local in_value = false
|
||||
local last_was_bracket = false
|
||||
local string_char = ""
|
||||
local last_char = ""
|
||||
|
||||
for char in text:gmatch(".") do
|
||||
if (char == "{" or char == "[") and not inside_string then
|
||||
if not in_value or last_was_bracket then
|
||||
out = out .. indent(char, indent_level, indent_width) .. "\n"
|
||||
else
|
||||
out = out .. char .. "\n"
|
||||
end
|
||||
last_was_bracket = true
|
||||
in_value = false
|
||||
indent_level = indent_level + 1
|
||||
elseif (char == '"' or char == "'") and not inside_string then
|
||||
inside_string = true
|
||||
string_char = char
|
||||
if not in_value then
|
||||
out = out .. indent(char, indent_level, indent_width)
|
||||
else
|
||||
out = out .. char
|
||||
end
|
||||
elseif inside_string then
|
||||
local pe_set = false
|
||||
if char == "\\" and previous_was_escape then
|
||||
previous_was_escape = false
|
||||
elseif char == "\\" then
|
||||
previous_was_escape = true
|
||||
pe_set = true
|
||||
end
|
||||
out = out .. char
|
||||
if char == string_char and not previous_was_escape then
|
||||
inside_string = false
|
||||
elseif previous_was_escape and not pe_set then
|
||||
previous_was_escape = false
|
||||
end
|
||||
elseif char == ":" then
|
||||
in_value = true
|
||||
last_was_bracket = false
|
||||
out = out .. char .. " "
|
||||
elseif char == "," then
|
||||
in_value = false
|
||||
reading_literal = false
|
||||
out = out .. char .. "\n"
|
||||
elseif char == "}" or char == "]" then
|
||||
indent_level = indent_level - 1
|
||||
if
|
||||
(char == "}" and last_char == "{")
|
||||
or
|
||||
(char == "]" and last_char == "[")
|
||||
then
|
||||
out = out:gsub("%s*\n$", "") .. char
|
||||
else
|
||||
out = out .. "\n" .. indent(char, indent_level, indent_width)
|
||||
end
|
||||
elseif not char:match("%s") and not reading_literal then
|
||||
reading_literal = true
|
||||
if not in_value or last_was_bracket then
|
||||
out = out .. indent(char, indent_level, indent_width)
|
||||
last_was_bracket = false
|
||||
else
|
||||
out = out .. char
|
||||
end
|
||||
elseif not char:match("%s") then
|
||||
out = out .. char
|
||||
end
|
||||
|
||||
if not char:match("%s") then
|
||||
last_char = char
|
||||
end
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
|
||||
return json
|
||||
Vendored
+580
@@ -0,0 +1,580 @@
|
||||
-- A configurable listbox that can be used as tooltip, selection box and
|
||||
-- selection box with fuzzy search, this may change in the future.
|
||||
--
|
||||
-- @note This code is a readaptation of autocomplete plugin from rxi :)
|
||||
--
|
||||
-- TODO implement select box with fuzzy search
|
||||
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local util = require "plugins.lsp.util"
|
||||
local RootView = require "core.rootview"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
---@class lsp.listbox.item
|
||||
---@field text string
|
||||
---@field info string
|
||||
---@field on_draw fun(item:lsp.listbox.item, x:number, y:number, calc_only?:boolean):number
|
||||
|
||||
---@alias lsp.listbox.callback fun(doc: core.doc, item: lsp.listbox.item)
|
||||
|
||||
---@class lsp.listbox.signature_param
|
||||
---@field label string
|
||||
|
||||
---@class lsp.listbox.signature
|
||||
---@field label string
|
||||
---@field activeParameter? integer
|
||||
---@field activeSignature? integer
|
||||
---@field parameters lsp.listbox.signature_param[]
|
||||
|
||||
---@class lsp.listbox.signature_list
|
||||
---@field activeParameter? integer
|
||||
---@field activeSignature? integer
|
||||
---@field signatures lsp.listbox.signature[]
|
||||
|
||||
---@class lsp.listbox.position
|
||||
---@field line integer
|
||||
---@field col integer
|
||||
|
||||
---@class lsp.listbox
|
||||
local listbox = {}
|
||||
|
||||
---@class lsp.listbox.settings
|
||||
---@field items lsp.listbox.item[]
|
||||
---@field shown_items lsp.listbox.item[]
|
||||
---@field selected_item_idx integer
|
||||
---@field show_items_count boolean
|
||||
---@field max_height integer
|
||||
---@field active_view core.docview | nil
|
||||
---@field line integer | nil
|
||||
---@field col integer | nil
|
||||
---@field last_line integer | nil
|
||||
---@field last_col integer | nil
|
||||
---@field callback lsp.listbox.callback | nil
|
||||
---@field is_list boolean
|
||||
---@field has_fuzzy_search boolean
|
||||
---@field above_text boolean
|
||||
local settings = {
|
||||
items = {},
|
||||
shown_items = {},
|
||||
selected_item_idx = 1,
|
||||
show_items_count = false,
|
||||
max_height = 6,
|
||||
active_view = nil,
|
||||
line = nil,
|
||||
col = nil,
|
||||
last_line = nil,
|
||||
last_col = nil,
|
||||
callback = nil,
|
||||
is_list = false,
|
||||
has_fuzzy_search = false,
|
||||
above_text = false,
|
||||
}
|
||||
|
||||
local mt = { __tostring = function(t) return t.text end }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
---@return core.docview | nil
|
||||
local function get_active_view()
|
||||
if getmetatable(core.active_view) == DocView then
|
||||
return core.active_view
|
||||
end
|
||||
end
|
||||
|
||||
---@param active_view core.docview
|
||||
---@return number x
|
||||
---@return number y
|
||||
---@return number width
|
||||
---@return number height
|
||||
local function get_suggestions_rect(active_view)
|
||||
if #settings.shown_items == 0 then
|
||||
listbox.hide()
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
local line, col
|
||||
if settings.line then
|
||||
line, col = settings.line, settings.col
|
||||
else
|
||||
line, col = active_view.doc:get_selection()
|
||||
end
|
||||
|
||||
-- Validate line against current view because there can be cases
|
||||
-- when user rapidly switches between tabs causing the deferred draw
|
||||
-- to be called late and the current document view already changed.
|
||||
if line > #active_view.doc.lines then
|
||||
listbox.hide()
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
local x, y = active_view:get_line_screen_position(line)
|
||||
|
||||
-- This function causes tokenizer to fail if given line is greater than
|
||||
-- the amount of lines the document holds, so validation above is needed.
|
||||
x = x + active_view:get_col_x_offset(line, col)
|
||||
|
||||
local padding_x = style.padding.x
|
||||
local padding_y = style.padding.y
|
||||
|
||||
if settings.above_text and line > 1 then
|
||||
y = y - active_view:get_line_height() - style.padding.y
|
||||
else
|
||||
y = y + active_view:get_line_height() + style.padding.y
|
||||
end
|
||||
|
||||
local font = settings.is_list and active_view:get_font() or style.font
|
||||
local text_height = font:get_height()
|
||||
|
||||
local max_width = 0
|
||||
for _, item in ipairs(settings.shown_items) do
|
||||
local w = 0
|
||||
if item.on_draw then
|
||||
w = item.on_draw(item, 0, 0, true)
|
||||
else
|
||||
w = font:get_width(item.text)
|
||||
if item.info then
|
||||
w = w + style.font:get_width(item.info) + style.padding.x
|
||||
end
|
||||
end
|
||||
max_width = math.max(max_width, w)
|
||||
end
|
||||
|
||||
local max_items = #settings.shown_items
|
||||
if settings.is_list and max_items > settings.max_height then
|
||||
max_items = settings.max_height
|
||||
end
|
||||
|
||||
-- additional line to display total items
|
||||
if settings.show_items_count then
|
||||
max_items = max_items + 1
|
||||
end
|
||||
|
||||
if max_width < 150 then
|
||||
max_width = 150
|
||||
end
|
||||
|
||||
local height = max_items * (text_height + (padding_y/4)) + (padding_y*2)
|
||||
local width = max_width + padding_x * 2
|
||||
|
||||
x = x - padding_x
|
||||
y = y - padding_y
|
||||
|
||||
local win_w = core.root_view.size.x
|
||||
if (width/win_w*100) >= 85 and (width+style.padding.x*4) < win_w then
|
||||
x = win_w - width - style.padding.x*2
|
||||
elseif width > (win_w - x) then
|
||||
x = x - (width - (win_w - x))
|
||||
if x < 0 then
|
||||
x = 0
|
||||
end
|
||||
end
|
||||
|
||||
return x, y, width, height
|
||||
end
|
||||
|
||||
---@param av core.docview
|
||||
local function draw_listbox(av)
|
||||
if #settings.shown_items <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
|
||||
-- draw border
|
||||
if not settings.is_list then
|
||||
local border_width = 1
|
||||
renderer.draw_rect(
|
||||
rx - border_width,
|
||||
ry - border_width,
|
||||
rw + (border_width * 2),
|
||||
rh + (border_width * 2),
|
||||
style.divider
|
||||
)
|
||||
end
|
||||
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
|
||||
local padding_x = style.padding.x
|
||||
local padding_y = style.padding.y
|
||||
|
||||
-- draw text
|
||||
local font = settings.is_list and av:get_font() or style.font
|
||||
local line_height = font:get_height() + (padding_y / 4)
|
||||
local y = ry + padding_y
|
||||
|
||||
local max_height = settings.max_height
|
||||
|
||||
local show_count = (
|
||||
#settings.shown_items <= max_height or not settings.is_list
|
||||
) and
|
||||
#settings.shown_items or max_height
|
||||
|
||||
local start_index = settings.selected_item_idx > max_height and
|
||||
(settings.selected_item_idx-(max_height-1)) or 1
|
||||
|
||||
for i=start_index, start_index+show_count-1, 1 do
|
||||
if not settings.shown_items[i] then
|
||||
break
|
||||
end
|
||||
|
||||
local item = settings.shown_items[i]
|
||||
|
||||
if item.on_draw then
|
||||
item.on_draw(item, rx + padding_x, y)
|
||||
else
|
||||
local color = (i == settings.selected_item_idx and settings.is_list) and
|
||||
style.accent or style.text
|
||||
|
||||
common.draw_text(
|
||||
font, color, item.text, "left",
|
||||
rx + padding_x, y, rw, line_height
|
||||
)
|
||||
|
||||
if item.info then
|
||||
color = (i == settings.selected_item_idx and settings.is_list) and
|
||||
style.text or style.dim
|
||||
|
||||
common.draw_text(
|
||||
style.font, color, item.info, "right",
|
||||
rx, y, rw - padding_x, line_height
|
||||
)
|
||||
end
|
||||
end
|
||||
y = y + line_height
|
||||
end
|
||||
|
||||
if settings.show_items_count then
|
||||
renderer.draw_rect(rx, y, rw, 2, style.caret)
|
||||
renderer.draw_rect(rx, y+2, rw, line_height, style.background)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
"Items",
|
||||
"left",
|
||||
rx + padding_x, y, rw, line_height
|
||||
)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
tostring(settings.selected_item_idx) .. "/" .. tostring(#settings.shown_items),
|
||||
"right",
|
||||
rx, y, rw - padding_x, line_height
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---Set the document position where the listbox will be draw.
|
||||
---@param position? lsp.listbox.position
|
||||
local function set_position(position)
|
||||
if type(position) == "table" then
|
||||
settings.line = position.line
|
||||
settings.col = position.col
|
||||
else
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Public functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
---@param elements lsp.listbox.item[]
|
||||
function listbox.add(elements)
|
||||
if type(elements) == "table" and #elements > 0 then
|
||||
local items = {}
|
||||
for _, element in pairs(elements) do
|
||||
table.insert(items, setmetatable(element, mt))
|
||||
end
|
||||
settings.items = items
|
||||
end
|
||||
end
|
||||
|
||||
function listbox.clear()
|
||||
settings.items = {}
|
||||
settings.selected_item_idx = 1
|
||||
settings.shown_items = {}
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
end
|
||||
|
||||
---@param element lsp.listbox.item
|
||||
function listbox.append(element)
|
||||
table.insert(settings.items, setmetatable(element, mt))
|
||||
end
|
||||
|
||||
function listbox.hide()
|
||||
settings.active_view = nil
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
settings.selected_item_idx = 1
|
||||
settings.shown_items = {}
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
---@param is_list? boolean
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show(is_list, position)
|
||||
set_position(position)
|
||||
|
||||
local active_view = get_active_view()
|
||||
if active_view then
|
||||
settings.active_view = active_view
|
||||
settings.last_line, settings.last_col = active_view.doc:get_selection()
|
||||
if settings.items and #settings.items > 0 then
|
||||
settings.is_list = is_list or false
|
||||
settings.shown_items = settings.items
|
||||
end
|
||||
end
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_text(text, position)
|
||||
if text and type("text") == "string" then
|
||||
local win_w = core.root_view.size.x - style.padding.x * 6
|
||||
text = util.wrap_text(text, style.font, win_w)
|
||||
|
||||
local items = {}
|
||||
for result in string.gmatch(text.."\n", "(.-)\n") do
|
||||
table.insert(items, {text = result})
|
||||
end
|
||||
listbox.add(items)
|
||||
end
|
||||
|
||||
listbox.show(false, position)
|
||||
end
|
||||
|
||||
---@param items lsp.listbox.item[]
|
||||
---@param callback lsp.listbox.callback
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_list(items, callback, position)
|
||||
listbox.add(items)
|
||||
|
||||
if callback then
|
||||
settings.callback = callback
|
||||
end
|
||||
|
||||
listbox.show(true, position)
|
||||
end
|
||||
|
||||
---@param signatures lsp.listbox.signature_list
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_signatures(signatures, position)
|
||||
local active_parameter = nil
|
||||
local active_signature = nil
|
||||
|
||||
if signatures.activeParameter then
|
||||
active_parameter = signatures.activeParameter + 1
|
||||
end
|
||||
|
||||
if signatures.activeSignature then
|
||||
active_signature = signatures.activeSignature + 1
|
||||
end
|
||||
|
||||
local signatures_count = #signatures.signatures
|
||||
|
||||
local items = {}
|
||||
for index, signature in ipairs(signatures.signatures) do
|
||||
table.insert(items, {
|
||||
text = signature.label,
|
||||
signature = signature,
|
||||
on_draw = function(item, x, y, calc_only)
|
||||
local width = 0
|
||||
local height = style.font:get_height()
|
||||
|
||||
if item.signature.parameters then
|
||||
if signatures_count > 1 then
|
||||
if index == active_signature then
|
||||
width = style.font:get_width("> ")
|
||||
else
|
||||
width = style.font:get_width("> ")
|
||||
x = x + style.font:get_width("> ")
|
||||
end
|
||||
end
|
||||
|
||||
width = width
|
||||
+ style.font:get_width("(")
|
||||
+ style.font:get_width(")")
|
||||
|
||||
if not calc_only then
|
||||
if signatures_count > 1 and index == active_signature then
|
||||
x = renderer.draw_text(style.font, "> ", x, y, style.caret)
|
||||
end
|
||||
x = renderer.draw_text(style.font, "(", x, y, style.text)
|
||||
end
|
||||
|
||||
local params_count = #item.signature.parameters
|
||||
for pindex, param in ipairs(item.signature.parameters) do
|
||||
local label = ""
|
||||
if type(param.label) == "table" then
|
||||
label = signature.label:sub(param.label[1]+1, param.label[2])
|
||||
else
|
||||
label = param.label
|
||||
end
|
||||
if label and pindex ~= params_count then
|
||||
label = label .. ", "
|
||||
end
|
||||
width = width + style.font:get_width(label)
|
||||
if not calc_only then
|
||||
local color = style.text
|
||||
if
|
||||
(
|
||||
signature.activeParameter
|
||||
and
|
||||
(signature.activeParameter + 1) == pindex
|
||||
)
|
||||
or
|
||||
(index == active_signature and active_parameter == pindex)
|
||||
then
|
||||
color = style.accent
|
||||
end
|
||||
x = renderer.draw_text(
|
||||
style.font,
|
||||
label,
|
||||
x, y,
|
||||
color
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if not calc_only then
|
||||
renderer.draw_text(style.font, ")", x, y, style.text)
|
||||
end
|
||||
else
|
||||
width = style.font:get_width(item.signature.label)
|
||||
if not calc_only then
|
||||
renderer.draw_text(
|
||||
style.font,
|
||||
item.signature.label,
|
||||
x, y,
|
||||
style.text
|
||||
)
|
||||
end
|
||||
end
|
||||
return width, width > 0 and height or 0
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
listbox.add(items)
|
||||
|
||||
listbox.show(false, position)
|
||||
end
|
||||
|
||||
function listbox.toggle_above(enable)
|
||||
if enable then
|
||||
settings.above_text = true
|
||||
else
|
||||
settings.above_text = false
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Patch event logic into RootView
|
||||
--------------------------------------------------------------------------------
|
||||
local root_view_update = RootView.update
|
||||
local root_view_draw = RootView.draw
|
||||
|
||||
RootView.update = function(...)
|
||||
root_view_update(...)
|
||||
|
||||
if not settings.active_view then return end
|
||||
|
||||
local active_view = get_active_view()
|
||||
if active_view then
|
||||
-- reset suggestions if caret was moved or not same active view
|
||||
local line, col = active_view.doc:get_selection()
|
||||
if
|
||||
settings.active_view ~= active_view
|
||||
or
|
||||
line ~= settings.last_line or col ~= settings.last_col
|
||||
then
|
||||
listbox.hide()
|
||||
end
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end
|
||||
|
||||
RootView.draw = function(...)
|
||||
if settings.active_view then
|
||||
local active_view = get_active_view()
|
||||
if
|
||||
active_view and settings.active_view == active_view
|
||||
and
|
||||
#settings.shown_items > 0
|
||||
then
|
||||
-- draw suggestions box after everything else
|
||||
core.root_view:defer_draw(draw_listbox, active_view)
|
||||
end
|
||||
end
|
||||
root_view_draw(...)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Commands
|
||||
--------------------------------------------------------------------------------
|
||||
local function predicate()
|
||||
local av = get_active_view()
|
||||
return av and settings.active_view and #settings.shown_items > 0, av
|
||||
end
|
||||
|
||||
command.add(predicate, {
|
||||
["listbox:select"] = function(av)
|
||||
---@cast av core.docview
|
||||
if settings.is_list then
|
||||
local doc = av.doc
|
||||
local item = settings.shown_items[settings.selected_item_idx]
|
||||
|
||||
if settings.callback then
|
||||
settings.callback(doc, item)
|
||||
end
|
||||
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:previous"] = function()
|
||||
if settings.is_list then
|
||||
settings.selected_item_idx = math.max(settings.selected_item_idx - 1, 1)
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:next"] = function()
|
||||
if settings.is_list then
|
||||
settings.selected_item_idx = math.min(
|
||||
settings.selected_item_idx + 1, #settings.shown_items
|
||||
)
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:cancel"] = function()
|
||||
listbox.hide()
|
||||
end,
|
||||
})
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Keymaps
|
||||
--------------------------------------------------------------------------------
|
||||
keymap.add {
|
||||
["tab"] = "listbox:select",
|
||||
["up"] = "listbox:previous",
|
||||
["down"] = "listbox:next",
|
||||
["escape"] = "listbox:cancel",
|
||||
}
|
||||
|
||||
|
||||
return listbox
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"addons": [
|
||||
{
|
||||
"id": "lsp",
|
||||
"name": "Language Server Protocol",
|
||||
"description": "Provides intellisense by leveraging the LSP protocol.",
|
||||
"version": "0.10",
|
||||
"mod_version": "3",
|
||||
"dependencies": {
|
||||
"widget": { "version": ">=0.1" },
|
||||
"lintplus": { "version": ">=0.2", "optional": true },
|
||||
"lsp_snippets": { "optional": true }
|
||||
},
|
||||
"tags": [
|
||||
"language server protocol"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+1662
File diff suppressed because it is too large
Load Diff
+121
@@ -0,0 +1,121 @@
|
||||
--
|
||||
-- SymbolResults Widget/View.
|
||||
-- @copyright Jefferson Gonzalez
|
||||
-- @license MIT
|
||||
--
|
||||
|
||||
local style = require "core.style"
|
||||
local Widget = require "libraries.widget"
|
||||
local Label = require "libraries.widget.label"
|
||||
local Line = require "libraries.widget.line"
|
||||
local ListBox = require "libraries.widget.listbox"
|
||||
local Server = require "plugins.lsp.server"
|
||||
|
||||
local Lsp = {}
|
||||
|
||||
---@class lsp.symbolresults : widget
|
||||
---@field public searching boolean
|
||||
---@field public symbol string
|
||||
---@field private title widget.label
|
||||
---@field private line widget.line
|
||||
---@field private list_container widget
|
||||
---@field private list widget.listbox
|
||||
local SymbolResults = Widget:extend()
|
||||
|
||||
function SymbolResults:new(symbol)
|
||||
SymbolResults.super.new(self)
|
||||
|
||||
Lsp = require "plugins.lsp"
|
||||
|
||||
self.name = "Symbols Search"
|
||||
self.defer_draw = false
|
||||
|
||||
self.searching = true
|
||||
self.symbol = symbol or ""
|
||||
self.title = Label(self, "Searching symbols for: " .. symbol)
|
||||
self.line = Line(self, 2, style.padding.x)
|
||||
|
||||
self.list_container = Widget(self)
|
||||
self.list_container.border.width = 0
|
||||
self.list_container:set_size(200, 200)
|
||||
|
||||
self.list = ListBox(self.list_container)
|
||||
self.list.border.width = 0
|
||||
|
||||
self.list:enable_expand(true)
|
||||
self.list:add_column("Num.")
|
||||
self.list:add_column("Symbol")
|
||||
self.list:add_column("Kind")
|
||||
self.list:add_column("Location")
|
||||
|
||||
local list_on_row_click = self.list.on_row_click
|
||||
self.list.on_row_click = function(this, idx, data)
|
||||
list_on_row_click(this, idx, data)
|
||||
self:on_selected(idx, data)
|
||||
end
|
||||
|
||||
self.num = 1
|
||||
|
||||
self.border.width = 0
|
||||
self:set_size(200, 200)
|
||||
self:show()
|
||||
end
|
||||
|
||||
function SymbolResults:add_result(result)
|
||||
local preview, position = Lsp.get_location_preview(result.location)
|
||||
local container_name = result.containerName and
|
||||
result.containerName .. "\n" or ""
|
||||
|
||||
local row = {
|
||||
tostring(self.num),
|
||||
ListBox.COLEND,
|
||||
style.syntax.keyword, container_name .. result.name,
|
||||
ListBox.COLEND,
|
||||
style.syntax.literal, Server.get_symbol_kind(result.kind),
|
||||
ListBox.COLEND,
|
||||
style.text, position, ListBox.NEWLINE, style.accent, preview
|
||||
}
|
||||
|
||||
self.num = self.num + 1
|
||||
|
||||
self.list:add_row(row, result)
|
||||
end
|
||||
|
||||
function SymbolResults:stop_searching()
|
||||
self.searching = false
|
||||
end
|
||||
|
||||
function SymbolResults:on_selected(idx, data)
|
||||
Lsp.goto_location(data.location)
|
||||
end
|
||||
|
||||
function SymbolResults:update()
|
||||
if not SymbolResults.super.update(self) then return end
|
||||
-- update the positions and sizes
|
||||
self.background_color = style.background
|
||||
self.title:set_position(style.padding.x, style.padding.y)
|
||||
if not self.searching or #self.list.rows > 0 then
|
||||
local label = "Finished: "
|
||||
if self.searching then
|
||||
label = "Searching: "
|
||||
end
|
||||
self.title:set_label(
|
||||
label
|
||||
.. #self.list.rows
|
||||
.. " results found for "
|
||||
.. '"'
|
||||
.. self.symbol
|
||||
.. '"'
|
||||
)
|
||||
end
|
||||
self.line:set_position(0, self.title:get_bottom() + 10)
|
||||
self.list_container:set_position(style.padding.x, self.line:get_bottom() + 10)
|
||||
self.list_container:set_size(
|
||||
self.size.x - (style.padding.x * 2),
|
||||
self.size.y - self.line:get_bottom()
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
return SymbolResults
|
||||
|
||||
Vendored
+101
@@ -0,0 +1,101 @@
|
||||
local core = require "core"
|
||||
local Object = require "core.object"
|
||||
|
||||
---Timer class
|
||||
---@class lsp.timer : core.object
|
||||
---@field public interval integer
|
||||
---@field public single_shot boolean
|
||||
---@field private started boolean
|
||||
---@field private last_run integer
|
||||
local Timer = Object:extend()
|
||||
|
||||
---Constructor
|
||||
---@param interval integer The interval in milliseconds
|
||||
---@param single_shot boolean Indicates if timer should only run once
|
||||
function Timer:new(interval, single_shot)
|
||||
Timer.super.new(self)
|
||||
|
||||
self.single_shot = single_shot or false
|
||||
self.started = false
|
||||
self.cancel_thread_marker = { cancel = true }
|
||||
self.last_run = 0
|
||||
|
||||
self:set_interval(interval or 1000)
|
||||
end
|
||||
|
||||
---Starts a non running timer.
|
||||
function Timer:start()
|
||||
if self.started then return end
|
||||
|
||||
self.started = true
|
||||
self.cancel_thread_marker = { cancel = false }
|
||||
local this = self
|
||||
|
||||
-- Save marker so that we keep this one and not an "updated" one
|
||||
local marker = self.cancel_thread_marker
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
if marker.cancel == true then return end
|
||||
this:reset()
|
||||
local now = system.get_time()
|
||||
local remaining = (this.last_run + this.interval) - now
|
||||
if remaining > 0 then
|
||||
repeat
|
||||
if not this.started or marker.cancel then return end
|
||||
coroutine.yield(remaining)
|
||||
now = system.get_time()
|
||||
remaining = (this.last_run + this.interval) - now
|
||||
until remaining <= 0
|
||||
end
|
||||
if not this.started or marker.cancel then return end
|
||||
this:on_timer()
|
||||
if this.single_shot then break end
|
||||
end
|
||||
this.started = false
|
||||
end)
|
||||
end
|
||||
|
||||
---Stops a running timer.
|
||||
function Timer:stop()
|
||||
self.started = false
|
||||
end
|
||||
|
||||
---Resets the timer countdown for execution.
|
||||
function Timer:reset()
|
||||
self.last_run = system.get_time()
|
||||
end
|
||||
|
||||
---Restarts the timer countdown for execution.
|
||||
function Timer:restart()
|
||||
self:reset()
|
||||
if not self.started then
|
||||
self:start()
|
||||
end
|
||||
end
|
||||
|
||||
---Check if the timer is running.
|
||||
---@return boolean
|
||||
function Timer:running()
|
||||
return self.started
|
||||
end
|
||||
|
||||
---Appropriately set the timer interval by converting milliseconds to seconds.
|
||||
---@param interval integer The interval in milliseconds
|
||||
function Timer:set_interval(interval)
|
||||
local new_interval = interval / 1000
|
||||
-- As this new interval might be shorter than the currently running one, and
|
||||
-- because we might already be sleeping waiting for the old interval, we
|
||||
-- mark the already running coroutine as cancelled, and create a new one.
|
||||
if self.started and self.interval > new_interval then
|
||||
self.cancel_thread_marker.cancel = true
|
||||
self:stop()
|
||||
self:start()
|
||||
end
|
||||
self.interval = new_interval
|
||||
end
|
||||
|
||||
---To be overwritten by the instantiated timer objects
|
||||
function Timer:on_timer() end
|
||||
|
||||
|
||||
return Timer
|
||||
Vendored
+474
@@ -0,0 +1,474 @@
|
||||
-- Some functions adapted from: https://github.com/orbitalquark/textadept-lsp
|
||||
-- and others added as needed.
|
||||
--
|
||||
-- @copyright Jefferson Gonzalez
|
||||
-- @license MIT
|
||||
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local json = require "plugins.lsp.json"
|
||||
|
||||
local util = {}
|
||||
|
||||
---Check if the given file is currently opened on the editor.
|
||||
---@param abs_filename string
|
||||
function util.doc_is_open(abs_filename)
|
||||
-- Normalize path format to the one used by normal docs
|
||||
abs_filename = common.normalize_path(abs_filename) or abs_filename
|
||||
for _, doc in ipairs(core.docs) do
|
||||
---@cast doc core.doc
|
||||
if doc.abs_filename == abs_filename then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Converts a utf-8 column position into the equivalent utf-16 position.
|
||||
---@param doc core.doc
|
||||
---@param line integer
|
||||
---@param column integer
|
||||
---@return integer col_position
|
||||
function util.doc_utf8_to_utf16(doc, line, column)
|
||||
local ltext = doc.lines[line]
|
||||
local ltext_len = ltext and #ltext or 0
|
||||
local ltext_ulen = ltext and utf8extra.len(ltext) or 0
|
||||
column = common.clamp(column, 1, ltext_len > 0 and ltext_len or 1)
|
||||
-- no need for conversion so return column as is
|
||||
if ltext_len == ltext_ulen then return column end
|
||||
if column > 1 then
|
||||
local col = 1
|
||||
for pos, code in utf8extra.next, ltext do
|
||||
if pos >= column then
|
||||
return col
|
||||
end
|
||||
-- Codepoints that high are encoded using surrogate pairs
|
||||
if code < 0x010000 then
|
||||
col = col + 1
|
||||
else
|
||||
col = col + 2
|
||||
end
|
||||
end
|
||||
return col
|
||||
end
|
||||
return column
|
||||
end
|
||||
|
||||
---Converts a utf-16 column position into the equivalent utf-8 position.
|
||||
---@param doc core.doc
|
||||
---@param line integer
|
||||
---@param column integer
|
||||
---@return integer col_position
|
||||
function util.doc_utf16_to_utf8(doc, line, column)
|
||||
local ltext = doc.lines[line]
|
||||
local ltext_len = ltext and #ltext or 0
|
||||
local ltext_ulen = ltext and utf8extra.len(ltext) or 0
|
||||
column = common.clamp(column, 1, ltext_len > 0 and ltext_len or 1)
|
||||
-- no need for conversion so return column as is
|
||||
if ltext_len == ltext_ulen then return column end
|
||||
if column > 1 then
|
||||
local col = 1
|
||||
local utf8_pos = 1
|
||||
for pos, code in utf8extra.next, ltext do
|
||||
if col >= column then
|
||||
return pos
|
||||
end
|
||||
utf8_pos = pos
|
||||
-- Codepoints that high are encoded using surrogate pairs
|
||||
if code < 0x010000 then
|
||||
col = col + 1
|
||||
else
|
||||
col = col + 2
|
||||
end
|
||||
end
|
||||
return utf8_pos
|
||||
end
|
||||
return column
|
||||
end
|
||||
|
||||
---Split a string by the given delimeter
|
||||
---@param s string The string to split
|
||||
---@param delimeter string Delimeter without lua patterns
|
||||
---@param delimeter_pattern? string Optional delimeter with lua patterns
|
||||
---@return table
|
||||
---@return boolean ends_with_delimiter
|
||||
function util.split(s, delimeter, delimeter_pattern)
|
||||
if not delimeter_pattern then
|
||||
delimeter_pattern = delimeter
|
||||
end
|
||||
|
||||
local last_idx = 1
|
||||
local result = {}
|
||||
for match_idx, afer_match_idx in s:gmatch("()"..delimeter_pattern.."()") do
|
||||
table.insert(result, string.sub(s, last_idx, match_idx - 1))
|
||||
last_idx = afer_match_idx
|
||||
end
|
||||
if last_idx > #s then
|
||||
return result, true
|
||||
else
|
||||
table.insert(result, string.sub(s, last_idx))
|
||||
return result, false
|
||||
end
|
||||
end
|
||||
|
||||
---Get the extension component of a filename.
|
||||
---@param filename string
|
||||
---@return string
|
||||
function util.file_extension(filename)
|
||||
local parts = util.split(filename, "%.")
|
||||
if #parts > 1 then
|
||||
return parts[#parts]:gsub("%%", "")
|
||||
end
|
||||
|
||||
return filename
|
||||
end
|
||||
|
||||
---Check if a file exists.
|
||||
---@param file_path string
|
||||
---@return boolean
|
||||
function util.file_exists(file_path)
|
||||
local file = io.open(file_path, "r")
|
||||
if file ~= nil then
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Converts the given LSP DocumentUri into a valid filename path.
|
||||
---@param uri string LSP DocumentUri to convert into a filename.
|
||||
---@return string
|
||||
function util.tofilename(uri)
|
||||
local filename = ""
|
||||
if PLATFORM == "Windows" then
|
||||
filename = uri:gsub('^file:///', '')
|
||||
else
|
||||
filename = uri:gsub('^file://', '')
|
||||
end
|
||||
|
||||
filename = filename:gsub(
|
||||
'%%(%x%x)',
|
||||
function(hex) return string.char(tonumber(hex, 16)) end
|
||||
)
|
||||
|
||||
if PLATFORM == "Windows" then filename = filename:gsub('/', '\\') end
|
||||
|
||||
return filename
|
||||
end
|
||||
|
||||
---Convert a file path to a LSP valid uri.
|
||||
---@param file_path string
|
||||
---@return string
|
||||
function util.touri(file_path)
|
||||
local function char_escape(char)
|
||||
if string.match(char, "[_.~-]") then return char end
|
||||
if char == "/" then return char end
|
||||
if char == "\\" and PLATFORM == "Windows" then return "/" end
|
||||
return string.format("%%%02X", string.byte(char))
|
||||
end
|
||||
|
||||
file_path = string.gsub(file_path, "([%W ])", char_escape)
|
||||
if PLATFORM ~= "Windows" then
|
||||
file_path = 'file://' .. file_path
|
||||
else
|
||||
file_path = 'file:///' .. file_path:gsub('\\', '/')
|
||||
end
|
||||
|
||||
return file_path
|
||||
end
|
||||
|
||||
---Converts a document range returned by lsp to a valid document selection.
|
||||
---@param range table LSP Range.
|
||||
---@param doc? core.doc
|
||||
---@return integer line1
|
||||
---@return integer col1
|
||||
---@return integer line2
|
||||
---@return integer col2
|
||||
function util.toselection(range, doc)
|
||||
local line1 = range.start.line + 1
|
||||
local col1 = range.start.character + 1
|
||||
local line2 = range['end'].line + 1
|
||||
local col2 = range['end'].character + 1
|
||||
|
||||
if doc then
|
||||
col1 = util.doc_utf16_to_utf8(doc, line1, col1)
|
||||
col2 = util.doc_utf16_to_utf8(doc, line2, col2)
|
||||
end
|
||||
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
---Opens the given location on a external application.
|
||||
---@param location string
|
||||
function util.open_external(location)
|
||||
local filelauncher = ""
|
||||
if PLATFORM == "Windows" then
|
||||
filelauncher = "start"
|
||||
elseif PLATFORM == "Mac OS X" then
|
||||
filelauncher = "open"
|
||||
else
|
||||
filelauncher = "xdg-open"
|
||||
end
|
||||
|
||||
-- non-Windows platforms need the text quoted (%q)
|
||||
if PLATFORM ~= "Windows" then
|
||||
location = string.format("%q", location)
|
||||
end
|
||||
|
||||
system.exec(filelauncher .. " " .. location)
|
||||
end
|
||||
|
||||
---Prettify json output and logs it if config.lsp.log_file is set.
|
||||
---@param code string
|
||||
---@return string
|
||||
function util.jsonprettify(code)
|
||||
if config.plugins.lsp.prettify_json then
|
||||
code = json.prettify(code)
|
||||
end
|
||||
|
||||
if config.plugins.lsp.log_file and #config.plugins.lsp.log_file > 0 then
|
||||
local log = io.open(config.plugins.lsp.log_file, "a+")
|
||||
log:write("Output: \n" .. tostring(code) .. "\n\n")
|
||||
log:close()
|
||||
end
|
||||
|
||||
return code
|
||||
end
|
||||
|
||||
---Gets the last component of a path. For example:
|
||||
---/my/path/to/somwhere would return somewhere.
|
||||
---@param path string
|
||||
---@return string
|
||||
function util.getpathname(path)
|
||||
local components = {}
|
||||
if PLATFORM == "Windows" then
|
||||
components = util.split(path, "\\")
|
||||
else
|
||||
components = util.split(path, "/")
|
||||
end
|
||||
|
||||
if #components > 0 then
|
||||
return components[#components]
|
||||
end
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
---Check if a value is on a table.
|
||||
---@param value any
|
||||
---@param table_array table
|
||||
---@return boolean
|
||||
function util.intable(value, table_array)
|
||||
for _, element in pairs(table_array) do
|
||||
if element == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---Remove by key from a table and returns a new
|
||||
---table with element removed.
|
||||
---@param table_object table
|
||||
---@param key_name string|integer
|
||||
---@return table
|
||||
function util.table_remove_key(table_object, key_name)
|
||||
local new_table = {}
|
||||
for key, data in pairs(table_object) do
|
||||
if key ~= key_name then
|
||||
new_table[key] = data
|
||||
end
|
||||
end
|
||||
|
||||
return new_table
|
||||
end
|
||||
|
||||
---Get a table specific field or nil if not found.
|
||||
---@param t table The table we are going to search for the field.
|
||||
---@param fieldset string A field spec in the format
|
||||
---"parent[.child][.subchild]" eg: "myProp.subProp.subSubProp"
|
||||
---@return any|nil The value of the given field or nil if not found.
|
||||
function util.table_get_field(t, fieldset)
|
||||
local fields = util.split(fieldset, ".", "%.")
|
||||
local field = fields[1]
|
||||
local value = nil
|
||||
|
||||
if field and #fields > 1 and t[field] then
|
||||
local sub_fields = table.concat(fields, ".", 2)
|
||||
value = util.table_get_field(t[field], sub_fields)
|
||||
elseif field and #fields > 0 and t[field] then
|
||||
value = t[field]
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
---Merge the content of the tables into a new one.
|
||||
---Arguments from the later tables take precedence.
|
||||
---Doesn't touch the original tables.
|
||||
---`nil` arguments are ignored.
|
||||
---@param ... table?
|
||||
---@return table
|
||||
function util.deep_merge(...)
|
||||
local t = {}
|
||||
local args = table.pack(...)
|
||||
for i=1,args.n do
|
||||
local other = args[i]
|
||||
if other then
|
||||
assert(type(other) == "table", string.format("Argument %d must be a table", i))
|
||||
for k, v in pairs(other) do
|
||||
if type(v) == "table" then
|
||||
if type(t[k]) == "table" then
|
||||
t[k] = util.deep_merge(t[k], v)
|
||||
else
|
||||
t[k] = util.deep_merge({}, v)
|
||||
end
|
||||
else
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
---Check if a table is really empty.
|
||||
---@param t table
|
||||
---@return boolean
|
||||
function util.table_empty(t)
|
||||
return next(t) == nil
|
||||
end
|
||||
|
||||
---Convert markdown to plain text.
|
||||
---@param text string
|
||||
---@return string
|
||||
function util.strip_markdown(text)
|
||||
local clean_text = ""
|
||||
local prev_line = ""
|
||||
for match in (text.."\n"):gmatch("(.-)".."\n") do
|
||||
match = match .. "\n"
|
||||
|
||||
-- strip markdown
|
||||
local new_line = match
|
||||
-- Block quotes
|
||||
:gsub("^>+(%s*)", "%1")
|
||||
-- headings
|
||||
:gsub("^(%s*)######%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)#####%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)####%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)####%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)###%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)##%s(.-)\n", "%1%2\n")
|
||||
:gsub("^(%s*)#%s(.-)\n", "%1%2\n")
|
||||
-- heading custom id
|
||||
:gsub("{#.-}", "")
|
||||
-- emoji
|
||||
:gsub(":[%w%-_]+:", "")
|
||||
-- bold and italic
|
||||
:gsub("%*%*%*(.-)%*%*%*", "%1")
|
||||
:gsub("___(.-)___", "%1")
|
||||
:gsub("%*%*_(.-)_%*%*", "%1")
|
||||
:gsub("__%*(.-)%*__", "%1")
|
||||
:gsub("___(.-)___", "%1")
|
||||
-- bold
|
||||
:gsub("%*%*(.-)%*%*", "%1")
|
||||
:gsub("__(.-)__", "%1")
|
||||
-- strikethrough
|
||||
:gsub("%-%-(.-)%-%-", "%1")
|
||||
-- italic
|
||||
:gsub("%*(.-)%*", "%1")
|
||||
:gsub("%s_(.-)_%s", "%1")
|
||||
:gsub("\\_(.-)\\_", "_%1_")
|
||||
:gsub("^_(.-)_", "%1")
|
||||
-- code
|
||||
:gsub("^%s*```(%w+)%s*\n", "")
|
||||
:gsub("^%s*```%s*\n", "")
|
||||
:gsub("``(.-)``", "%1")
|
||||
:gsub("`(.-)`", "%1")
|
||||
-- lines
|
||||
:gsub("^%-%-%-%-*%s*\n", "")
|
||||
:gsub("^%*%*%*%**%s*\n", "")
|
||||
-- reference links
|
||||
:gsub("^%[[^%^](.-)%]:.-\n", "")
|
||||
-- footnotes
|
||||
:gsub("^%[%^(.-)%]:%s+", "[%1]: ")
|
||||
:gsub("%[%^(.-)%]", "[%1]")
|
||||
-- Images
|
||||
:gsub("!%[(.-)%]%((.-)%)", "")
|
||||
-- links
|
||||
:gsub("%s<(.-)>%s", "%1")
|
||||
:gsub("%[(.-)%]%s*%[(.-)%]", "%1")
|
||||
:gsub("%[(.-)%]%((.-)%)", "%1: %2")
|
||||
-- remove escaped punctuations
|
||||
:gsub("\\(%p)", "%1")
|
||||
|
||||
-- if paragraph put in same line
|
||||
local is_paragraph = false
|
||||
|
||||
local prev_spaces = prev_line:match("^%g+")
|
||||
local prev_endings = prev_line:match("[ \t\r\n]+$")
|
||||
local new_spaces = new_line:match("^%g+")
|
||||
|
||||
if prev_spaces and new_spaces then
|
||||
local new_lines = prev_endings ~= nil
|
||||
and prev_endings:gsub("[ \t\r]+", "") or ""
|
||||
|
||||
if #new_lines == 1 then
|
||||
is_paragraph = true
|
||||
clean_text = clean_text:gsub("[%s\n]+$", "")
|
||||
.. " " .. new_line:gsub("^%s+", "")
|
||||
end
|
||||
end
|
||||
|
||||
if not is_paragraph then
|
||||
clean_text = clean_text .. new_line
|
||||
end
|
||||
|
||||
prev_line = new_line
|
||||
end
|
||||
return clean_text
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param font renderer.font
|
||||
---@param max_width number
|
||||
function util.wrap_text(text, font, max_width)
|
||||
local lines = util.split(text, "\n")
|
||||
local wrapped_text = ""
|
||||
local longest_line = 0;
|
||||
for _, line in ipairs(lines) do
|
||||
local line_len = line:ulen() or 0
|
||||
if line_len > longest_line then
|
||||
longest_line = line_len
|
||||
local line_width = font:get_width(line)
|
||||
if line_width > max_width then
|
||||
local words = util.split(line, " ")
|
||||
local new_line = words[1] and words[1] or ""
|
||||
wrapped_text = wrapped_text .. new_line
|
||||
for w=2, #words do
|
||||
if font:get_width(new_line .. " " .. words[w]) <= max_width then
|
||||
new_line = new_line .. " " .. words[w]
|
||||
wrapped_text = wrapped_text .. " " .. words[w]
|
||||
else
|
||||
wrapped_text = wrapped_text .. "\n" .. words[w]
|
||||
new_line = words[w]
|
||||
end
|
||||
end
|
||||
wrapped_text = wrapped_text .. "\n"
|
||||
else
|
||||
wrapped_text = wrapped_text .. line .. "\n"
|
||||
end
|
||||
else
|
||||
wrapped_text = wrapped_text .. line .. "\n"
|
||||
end
|
||||
end
|
||||
|
||||
wrapped_text = wrapped_text:gsub("\n\n\n\n?", "\n\n"):gsub("%s*$", "")
|
||||
|
||||
return wrapped_text
|
||||
end
|
||||
|
||||
|
||||
return util
|
||||
Reference in New Issue
Block a user