Initial commit
22
.config/lite-xl/plugins/lsp/LICENSE
Normal file
|
|
@ -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.
|
||||
271
.config/lite-xl/plugins/lsp/README.md
Normal file
|
|
@ -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
|
||||
956
.config/lite-xl/plugins/lsp/config.lua
Normal file
|
|
@ -0,0 +1,956 @@
|
|||
--
|
||||
-- A list of servers.
|
||||
--
|
||||
-- Can be used by doing a 'local lspconfig = require "plugins.lsp.config"'
|
||||
-- on your user init.lua, for more details check the README.md
|
||||
--
|
||||
-- Servers taken from:
|
||||
-- https://github.com/prabirshrestha/vim-lsp/wiki/Servers
|
||||
-- https://github.com/mattn/vim-lsp-settings/tree/master/settings
|
||||
--
|
||||
|
||||
local lsp = require "plugins.lsp"
|
||||
local util = require "plugins.lsp.util"
|
||||
local config = require "core.config"
|
||||
local snippets = pcall(require, "plugins.snippets") and config.plugins.lsp.snippets
|
||||
|
||||
---Options that can be passed to a LSP server to overwrite the defaults.
|
||||
---@class lsp.config.options
|
||||
---
|
||||
---Name of server.
|
||||
---@field name string
|
||||
---Main language, eg: C.
|
||||
---Can be a string or a table.
|
||||
---If the table is empty, the file extension will be used instead.
|
||||
---The table should be an array of tables containing `id` and `pattern`.
|
||||
---The `pattern` will be matched with the file path.
|
||||
---Will use the `id` of the first `pattern` that matches.
|
||||
---If no pattern matches, the file extension will be used instead.
|
||||
---@field language string | lsp.server.languagematch[]
|
||||
---File types that are supported by this server.
|
||||
---@field file_patterns string[]
|
||||
---LSP command and optional arguments.
|
||||
---@field command table<integer,string|table>
|
||||
---On Windows, avoid running the LSP server with cmd.exe.
|
||||
---@field windows_skip_cmd? boolean
|
||||
---Enviroment variables to set for the server command.
|
||||
---@field env? table<string, string>
|
||||
---Seconds before closing the server when not needed anymore.
|
||||
---@field quit_timeout? number
|
||||
---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.
|
||||
---@field settings? table<string,any>
|
||||
---Optional table of initializationOptions for the LSP.
|
||||
---@field init_options? table<string,any>
|
||||
---Optional table of capabilities that will be merged with our default one.
|
||||
---@field custom_capabilities? table<string,any>
|
||||
---Function called when the server has been started.
|
||||
---@field on_start? fun(server: lsp.server)
|
||||
---Set by default to 16 should only be modified if having issues with a server.
|
||||
---@field requests_per_second? integer
|
||||
---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.
|
||||
---@field incremental_changes? boolean
|
||||
---Set to true to debug the lsp client when developing it.
|
||||
---@field verbose? boolean
|
||||
|
||||
---@class lsp.config.server
|
||||
---Register the lsp server for usage.
|
||||
---@field setup fun(options?:lsp.config.options)
|
||||
---Get the default lsp server options.
|
||||
---@field get_options fun():lsp.config.options
|
||||
|
||||
---Helper to register a language server.
|
||||
---@param options lsp.config.options
|
||||
---@return lsp.config.server
|
||||
local function add_lsp(options)
|
||||
return {
|
||||
setup = function(user_options)
|
||||
local merged_options = util.deep_merge(options, user_options)
|
||||
lsp.add_server(merged_options)
|
||||
end,
|
||||
get_options = function()
|
||||
return options
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
---List of predefined language servers that can be easily enabled at runtime.
|
||||
---@class lsp.config
|
||||
local lspconfig = {}
|
||||
|
||||
---# bash-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/bash-lsp/bash-language-server
|
||||
--- __Installation__: `npm i -g bash-language-server`
|
||||
--- __Note__: also install `shellcheck` for linting
|
||||
lspconfig.bashls = add_lsp {
|
||||
name = "bash-language-server",
|
||||
language = "shellscript",
|
||||
file_patterns = { "%.sh$" },
|
||||
command = { "bash-language-server", "start" },
|
||||
incremental_changes = true,
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# ccls
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/MaskRay/ccls/
|
||||
--- __Installation__: https://github.com/MaskRay/ccls/wiki
|
||||
lspconfig.ccls = add_lsp {
|
||||
name = "ccls",
|
||||
language = {
|
||||
{ id = "c", pattern = "%.[ch]$" },
|
||||
{ id = "cpp", pattern = "%.[ch]pp$" },
|
||||
{ id = "cpp", pattern = "%.[CH]$" },
|
||||
{ id = "cpp", pattern = "%.[ch]%+%+$" },
|
||||
},
|
||||
file_patterns = {
|
||||
"%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$",
|
||||
"%.cc$", "%.C$", "%.cxx$", "%.c++$", "%.hh$",
|
||||
"%.H$", "%.hxx$", "%.h++$", "%.objc$", "%.objcpp$"
|
||||
},
|
||||
command = { "ccls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# clangd
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://clangd.llvm.org/
|
||||
--- __Installation__: install the clang software package on your system
|
||||
--- __Note__: See https://clangd.llvm.org/installation.html#project-setup
|
||||
lspconfig.clangd = add_lsp {
|
||||
name = "clangd",
|
||||
language = {
|
||||
{ id = "c", pattern = "%.[ch]$" },
|
||||
{ id = "cpp", pattern = "%.[ch]pp$" },
|
||||
{ id = "cpp", pattern = "%.[CH]$" },
|
||||
{ id = "cpp", pattern = "%.[ch]%+%+$" },
|
||||
},
|
||||
file_patterns = {
|
||||
"%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$",
|
||||
"%.cc$", "%.C$", "%.cxx$", "%.c++$", "%.hh$",
|
||||
"%.H$", "%.hxx$", "%.h++$", "%.objc$", "%.objcpp$"
|
||||
},
|
||||
command = { "clangd", "-background-index" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Clojure
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://clojure-lsp.github.io/
|
||||
--- __Installation__: https://clojure-lsp.github.io/clojure-lsp/installation/
|
||||
lspconfig.clojure_lsp = add_lsp {
|
||||
name = "clojure-lsp",
|
||||
language = "clojure",
|
||||
file_patterns = { "%.clj$", "%.cljs$", "%.clc$", "%.edn$" },
|
||||
command = { "clojure-lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Crystal
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/elbywan/crystalline
|
||||
--- __Installation__: 'paru -S crystalline-bin'
|
||||
lspconfig.crystalline = add_lsp {
|
||||
name = "crystalline",
|
||||
language = "crystal",
|
||||
file_patterns = { "%.cr$" },
|
||||
command = { "crystalline", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# vscode-css-languageserver
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/vscode-langservers/vscode-css-languageserver-bin
|
||||
--- __Installation__: `npm install -g vscode-css-languageserver-bin`
|
||||
--- or `pacman -S vscode-css-languageserver`
|
||||
lspconfig.cssls = add_lsp {
|
||||
name = "css-languageserver",
|
||||
language = "css",
|
||||
file_patterns = { "%.css$", "%.less$", "%.sass$" },
|
||||
command = {
|
||||
{
|
||||
'vscode-css-languageserver',
|
||||
'vscode-css-language-server',
|
||||
'css-languageserver'
|
||||
},
|
||||
'--stdio'
|
||||
},
|
||||
fake_snippets = true,
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# D
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/Pure-D/serve-d
|
||||
--- __Installation__: https://github.com/Pure-D/serve-d?tab=readme-ov-file#installation
|
||||
lspconfig.serve_d = add_lsp {
|
||||
name = "serve_d",
|
||||
language = "d",
|
||||
file_patterns = { "%.di?$" },
|
||||
command = { "serve-d" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# dartls
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/dart-lang/sdk
|
||||
--- __Installation__: Provided in dart sdk
|
||||
lspconfig.dartls = add_lsp {
|
||||
name = "dart",
|
||||
language = "dart",
|
||||
file_patterns = { "%.dart$" },
|
||||
command = { "dart", "language-server", "--protocol=lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Deno
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://deno.land/manual/advanced/language_server
|
||||
--- __Installation__: Provided in Deno runtime
|
||||
lspconfig.deno = add_lsp {
|
||||
name = "deno",
|
||||
language = {
|
||||
{ id = "javascript", pattern = "%.js$" },
|
||||
{ id = "javascriptreact", pattern = "%.jsx$" },
|
||||
{ id = "typescript", pattern = "%.ts$" },
|
||||
{ id = "typescriptreact", pattern = "%.tsx$" },
|
||||
},
|
||||
file_patterns = { "%.[tj]s$", "%.[tj]sx$" },
|
||||
command = { 'deno', 'lsp' },
|
||||
verbose = false,
|
||||
settings = {
|
||||
deno = {
|
||||
enable = true,
|
||||
unstable = true,
|
||||
config = "./deno.json",
|
||||
importMap = "./import_map.json",
|
||||
lint = true,
|
||||
codeLens = {
|
||||
implementations = true,
|
||||
references = true,
|
||||
test = true,
|
||||
referencesAllFunctions = true
|
||||
},
|
||||
suggest = {
|
||||
names = true,
|
||||
paths = true,
|
||||
completeFunctionCalls = true,
|
||||
imports = {
|
||||
autoDiscover = true,
|
||||
hosts = {
|
||||
["https://deno.land/"] = true,
|
||||
["https://nest.land/"] = true,
|
||||
["https://crux.land/"] = true
|
||||
}
|
||||
},
|
||||
autoImports = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---# Dockerfile
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/rcjsuen/dockerfile-language-server-nodejs
|
||||
--- __Installation__: `npm install -g dockerfile-language-server-nodejs`
|
||||
lspconfig.dockerls = add_lsp {
|
||||
name = "docker-langserver",
|
||||
language = "dockerfile",
|
||||
file_patterns = { "Dockerfile$" },
|
||||
command = { "docker-langserver", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Elixir
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/elixir-lsp/elixir-ls
|
||||
--- __Installation__: 'paru -S elixir-ls'
|
||||
lspconfig.elixirls = add_lsp {
|
||||
name = "elixirls",
|
||||
language = "elixir",
|
||||
file_patterns = { "%.ex$", "%.exs$" },
|
||||
command = { "elixir-ls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Elm
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/elm-tooling/elm-language-server
|
||||
--- __Installation__: `paru -S elm-language-server`
|
||||
lspconfig.elmls = add_lsp {
|
||||
name = "elmls",
|
||||
language = "elm",
|
||||
file_patterns = { "%.elm$" },
|
||||
command = { "elm-language-server" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Erlang
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/erlang-ls/erlang_ls
|
||||
--- __Installation__: ?
|
||||
lspconfig.erlangls = add_lsp {
|
||||
name = "erlangls",
|
||||
language = "erlang",
|
||||
file_patterns = { "%.erl$", "%.hrl$" },
|
||||
command = { 'Erlang', 'LS', '-t', 'stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# fennel-ls
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://git.sr.ht/~xerool/fennel-ls
|
||||
--- __Installation__:
|
||||
--- ```sh
|
||||
--- git clone https://git.sr.ht/~xerool/fennel-ls
|
||||
--- make -C fennel-ls
|
||||
--- sudo make -C fennel-ls install
|
||||
--- ```
|
||||
lspconfig.fennells = add_lsp {
|
||||
name = "fennel-ls",
|
||||
language = "fennel",
|
||||
file_patterns = { "%.fnl$" },
|
||||
command = { "fennel-ls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Flow - JavaScript
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://flow.org/
|
||||
--- __Installation__: `npm install -g flow-bin`
|
||||
lspconfig.flow = add_lsp {
|
||||
name = "flow",
|
||||
language = "javascript",
|
||||
file_patterns = { "%.js$", "%.jsx$" },
|
||||
command = { "flow", "lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Fortran - fortls
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://fortls.fortran-lang.org/index.html
|
||||
--- __Installation__: `paru -S fortls`
|
||||
lspconfig.fortls = add_lsp {
|
||||
name = "fortls",
|
||||
language = "fortran",
|
||||
file_patterns = { "%.f$", "%.f90$", "%.f95$", "%.F$" },
|
||||
command = { "fortls", "--notify_init" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Gleam
|
||||
--- __Status__: Works (the gleam lsp itself acts kinda weird)
|
||||
--- __Site__: https://gleam.run/
|
||||
--- __Installation__: Included with the gleam compiler binary
|
||||
lspconfig.gleam = add_lsp {
|
||||
name = "gleam",
|
||||
language = "gleam",
|
||||
file_patterns = { "%.gleam$" },
|
||||
command = { "gleam", "lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# gopls
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://pkg.go.dev/golang.org/x/tools/gopls
|
||||
--- __Installation__: `go get -u golang.org/x/tools/gopls`
|
||||
lspconfig.gopls = add_lsp {
|
||||
name = "gopls",
|
||||
language = "go",
|
||||
file_patterns = { "%.go$" },
|
||||
command = { "gopls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# groovy-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/prominic/groovy-language-server
|
||||
--- __Installation__:
|
||||
--- ```sh
|
||||
--- mkdir ~/lsp
|
||||
--- cd ~/lsp
|
||||
--- git clone https://github.com/prominic/groovy-language-server.git
|
||||
--- cd ~/lsp/groovy-language-server
|
||||
--- ./gradlew build
|
||||
--- ```
|
||||
lspconfig.groovyls = add_lsp {
|
||||
name = "groovy-language-server",
|
||||
language = "groovy",
|
||||
file_patterns = { "%.groovy$", "%.gvy$", "%.gy$", "%.gsh$" },
|
||||
-- command = { "java", "-jar", "/path/to/groovy-language-server-all.jar" },
|
||||
command = { "groovy-language-server" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# haskell-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/haskell/haskell-language-server
|
||||
--- __Installation__: `ghcup install hls`
|
||||
--- or https://github.com/haskell/haskell-language-server#installation
|
||||
lspconfig.hls = add_lsp {
|
||||
name = "haskell-language-server",
|
||||
language = "haskell",
|
||||
file_patterns = { "%.hs$", "%.lhs$" },
|
||||
command = { 'haskell-language-server-wrapper', '--lsp' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# vscode-html-languageserver
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/vscode-langservers/vscode-html-languageserver-bin
|
||||
--- __Installation__: `npm install --global vscode-html-languageserver-bin`
|
||||
--- or `pacman -S vscode-html-languageserver`
|
||||
lspconfig.html = add_lsp {
|
||||
name = "html-languageserver",
|
||||
language = "html",
|
||||
file_patterns = { "%.html$" },
|
||||
command = {
|
||||
{
|
||||
'vscode-html-languageserver',
|
||||
'vscode-html-language-server',
|
||||
'html-languageserver'
|
||||
},
|
||||
'--stdio'
|
||||
},
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# intelephense
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/bmewburn/intelephense-docs
|
||||
--- __Installation__: `npm -g install intelephense`
|
||||
--- __Note__: Set your license and storage by passing the init_options as follows:
|
||||
--- ```lua
|
||||
--- init_options = { licenceKey = "...", storagePath = "/some/path"}
|
||||
--- ```
|
||||
lspconfig.intelephense = add_lsp {
|
||||
name = "intelephense",
|
||||
language = "php",
|
||||
file_patterns = { "%.php$" },
|
||||
command = { "intelephense", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# java
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/eclipse/eclipse.jdt.ls
|
||||
lspconfig.jdtls = add_lsp {
|
||||
name = "jdtls",
|
||||
language = "java",
|
||||
file_patterns = { "%.java$" },
|
||||
command = { "jdtls" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Scala
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://scalameta.org/metals/
|
||||
--- __Installation__: `paru -S metals`
|
||||
lspconfig.metals = add_lsp {
|
||||
name = "metals",
|
||||
language = "scala",
|
||||
file_patterns = { "%.scala$" },
|
||||
command = { "metals" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# vscode-json-languageserver
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://www.npmjs.com/package/vscode-json-languageserver
|
||||
--- __Installation__: `npm install -g vscode-json-languageserver`
|
||||
--- or `pacman -S vscode-json-languageserver`
|
||||
lspconfig.jsonls = add_lsp {
|
||||
name = "json-languageserver",
|
||||
language = "json",
|
||||
file_patterns = { "%.json$", "%.jsonc$" },
|
||||
command = {
|
||||
{
|
||||
'vscode-json-languageserver',
|
||||
'vscode-json-language-server',
|
||||
'json-languageserver',
|
||||
},
|
||||
'--stdio'
|
||||
},
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# kotlin-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/fwcd/kotlin-language-server
|
||||
--- __Installation__: https://github.com/fwcd/kotlin-language-server/releases
|
||||
lspconfig.kotlin_language_server = add_lsp {
|
||||
name = "kotlin-language-server",
|
||||
language = "kotlin",
|
||||
file_patterns = { "%.kt$", "%.kts$", "%.ktm$" },
|
||||
command = { 'kotlin-language-server' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# XML
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/eclipse/lemminx
|
||||
--- __Installation__: 'paru -S lemminx'
|
||||
lspconfig.lemminx = add_lsp {
|
||||
name = "lemminx",
|
||||
language = "xml",
|
||||
file_patterns = { "%.xml$" },
|
||||
command = { "lemminx" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# nil
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/oxalica/nil
|
||||
--- __Installation__: cargo install --git https://github.com/oxalica/nil nil
|
||||
--- __Note__: nix >= 2.4 needs to be installed
|
||||
lspconfig.nillsp = add_lsp {
|
||||
name = "nil",
|
||||
language = "nix",
|
||||
file_patterns = { "%.nix$" },
|
||||
command = { "nil" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# nimlsp
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/PMunch/nimlsp
|
||||
--- __Installation__: `nimble install nimlsp`
|
||||
lspconfig.nimlsp = add_lsp {
|
||||
name = "nimlsp",
|
||||
language = "nim",
|
||||
file_patterns = { "%.nim$" },
|
||||
command = { "nimlsp" },
|
||||
requests_per_second = 25,
|
||||
incremental_changes = false,
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# ocaml-lsp
|
||||
--- __Status__: Reported working on https://github.com/jgmdev/lite-xl-lsp/issues/17
|
||||
--- __Site__: https://github.com/ocaml/ocaml-lsp
|
||||
--- __Installation__: https://github.com/ocaml/ocaml-lsp#installation
|
||||
lspconfig.ocaml_lsp = add_lsp {
|
||||
name = "ocaml-lsp",
|
||||
language = "ocaml",
|
||||
file_patterns = { "%.ml$", "%.mli$" },
|
||||
command = { "ocamllsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Odin
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/DanielGavin/ols
|
||||
--- __Installation__: `paru -S odinls`
|
||||
lspconfig.odinls = add_lsp {
|
||||
name = "odinls",
|
||||
language = "odin",
|
||||
file_patterns = { "%.odin$" },
|
||||
command = { "ols" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# omnisharp
|
||||
--- __Status__: Works but, freeze on large projects (https://github.com/ppy/osu.git)
|
||||
--- __Site__: https://github.com/OmniSharp/omnisharp-roslyn
|
||||
--- __Installation__: See official website for instructions
|
||||
lspconfig.omnisharp = add_lsp {
|
||||
name = "omnisharp",
|
||||
language = "csharp",
|
||||
file_patterns = { "%.cs$" },
|
||||
command = { "omnisharp", "-lsp" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# PerlNavigator - Perl
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/bscan/PerlNavigator
|
||||
--- __Installation__: `paru -S perlnavigator`
|
||||
lspconfig.perlnavigator = add_lsp {
|
||||
name = "perlnavigator",
|
||||
language = "perl",
|
||||
file_patterns = { "%.pl$", "%.pm$" },
|
||||
command = { "perlnavigator" },
|
||||
settings = {
|
||||
perlnavigator = {
|
||||
-- The following setting is only needed if you want to set a custom perl path. It already defaults to "perl"
|
||||
perlPath = "perl"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---# python-language-server
|
||||
--- __Status__: Works (deprecated in favor of python-lsp-server)
|
||||
--- __Site__: https://github.com/palantir/python-language-server
|
||||
--- __Installation__: `pip install python-language-server`
|
||||
--- __Note__: Also don't forget to install any additional optional dependencies
|
||||
--- for additional features (see official site for details).
|
||||
lspconfig.pyls = add_lsp {
|
||||
name = "pyls",
|
||||
language = "python",
|
||||
file_patterns = { "%.py$" },
|
||||
command = { 'pyls' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# python-lsp-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/python-lsp/python-lsp-server
|
||||
--- __Installation__: `pip install python-lsp-server`
|
||||
--- __Note__: Also don't forget to install any additional optional dependencies
|
||||
--- for additional features (see official site for details).
|
||||
lspconfig.pylsp = add_lsp {
|
||||
name = "pylsp",
|
||||
language = "python",
|
||||
file_patterns = { "%.py$" },
|
||||
command = { 'pylsp' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
--# pyright
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/microsoft/pyright
|
||||
--- __Installation__: `pip install pyright` or `npm install -g pyright`
|
||||
lspconfig.pyright = add_lsp {
|
||||
name = "pyright",
|
||||
language = "python",
|
||||
file_patterns = { "%.py$" },
|
||||
command = { "pyright-langserver", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# quick-lint-js
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/quick-lint/quick-lint-js
|
||||
--- __Installation__: Arch Linux: `yay -Syu quick-lint-js`
|
||||
lspconfig.quicklintjs = add_lsp {
|
||||
name = "quick-lint-js",
|
||||
language = {
|
||||
{ id = "javascriptreact", pattern = "%.jsx$" },
|
||||
{ id = "javascript", pattern = "%.js$" },
|
||||
{ id = "typescriptdefinition", pattern = "%.d%.ts$" },
|
||||
{ id = "typescriptsource", pattern = "%.ts$" },
|
||||
{ id = "typescriptreact", pattern = "%.tsx$" },
|
||||
{ id = "typescript", pattern = ".*" },
|
||||
},
|
||||
file_patterns = { "%.[mc]?jsx?$", "%.tsx?$" },
|
||||
command = { "quick-lint-js", "--lsp-server" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# R
|
||||
-- __Status__: Works
|
||||
-- __Site__:https://github.com/REditorSupport/languageserver#installation
|
||||
-- __Installation__: `paru -S r-languageserver`
|
||||
lspconfig.rlanguageserver = add_lsp {
|
||||
name = "rlanguageserver",
|
||||
language = "r",
|
||||
file_patterns = { "%.r$", "%.R$" },
|
||||
command = {'R', '--slave', '-e', 'languageserver::run()'},
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Rust Language Server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/rust-lang/rls
|
||||
--- __Installation__: Install rust on your system
|
||||
lspconfig.rls = add_lsp {
|
||||
name = "rust-language-server",
|
||||
language = "rust",
|
||||
file_patterns = { "%.rs$" },
|
||||
command = { 'rls' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Ruby LSP
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/Shopify/ruby-lsp
|
||||
--- __Instalation__: gem install ruby-lsp
|
||||
--- __Note__: Also don't forget to install any additional optional dependecies
|
||||
--- for additional features (see official site for details).
|
||||
lspconfig.ruby_lsp = add_lsp {
|
||||
name = "ruby-lsp",
|
||||
language = "ruby",
|
||||
file_patterns = { "%.rb$" },
|
||||
command = { 'ruby-lsp' },
|
||||
-- Override command to one below if You want to use it with bundler
|
||||
-- command = { 'bundle', 'exec', 'ruby-lsp'},
|
||||
incremental_changes = true,
|
||||
init_options = {
|
||||
enabledFeatures = {
|
||||
"codeActions",
|
||||
"diagnostics",
|
||||
-- semanticHighlighting should be use only when running with bundle at the moment
|
||||
--"semanticHighlighting",
|
||||
"documentHighlights",
|
||||
"documentLink",
|
||||
"documentSymbols",
|
||||
"foldingRanges",
|
||||
"formatting",
|
||||
"hover",
|
||||
"inlayHint",
|
||||
"onTypeFormatting",
|
||||
"selectionRanges",
|
||||
"completion"
|
||||
},
|
||||
-- enableExperimentalFeatures = true,
|
||||
-- rubyVersionManager = "",
|
||||
},
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Rust Analyzer
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://rust-analyzer.github.io/
|
||||
--- __Installation__: See official website for instructions
|
||||
lspconfig.rust_analyzer = add_lsp {
|
||||
name = "rust-analyzer",
|
||||
language = "rust",
|
||||
file_patterns = { "%.rs$" },
|
||||
command = { 'rust-analyzer' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Solargraph
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/castwide/solargraph
|
||||
--- __Installation__: `gem install solargraph`
|
||||
lspconfig.solargraph = add_lsp {
|
||||
name = "solargraph",
|
||||
language = "ruby",
|
||||
file_patterns = { "%.rb$" },
|
||||
command = { 'solargraph', 'stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# sql-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/joe-re/sql-language-server
|
||||
--- __Installation__: `npm i -g sql-language-server`
|
||||
lspconfig.sqlls = add_lsp {
|
||||
name = "sql-language-server",
|
||||
language = "sql",
|
||||
file_patterns = { "%.sql$" },
|
||||
command = { 'sql-language-server', 'up', '--method', 'stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# lua-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/sumneko/lua-language-server
|
||||
--- __Installation__: https://github.com/sumneko/lua-language-server/wiki/Build-and-Run-(Standalone)
|
||||
lspconfig.sumneko_lua = add_lsp {
|
||||
name = "lua-language-server",
|
||||
language = "lua",
|
||||
file_patterns = { "%.lua$" },
|
||||
command = { 'lua-language-server' },
|
||||
verbose = false,
|
||||
settings = {
|
||||
Lua = {
|
||||
completion = {
|
||||
enable = true,
|
||||
callSnippet = snippets and "Replace" or "Disable",
|
||||
keywordSnippet = snippets and "Replace" or "Disable"
|
||||
},
|
||||
develop = {
|
||||
enable = false,
|
||||
debuggerPort = 11412,
|
||||
debuggerWait = false
|
||||
},
|
||||
diagnostics = {
|
||||
enable = true,
|
||||
},
|
||||
hover = {
|
||||
enable = true,
|
||||
viewNumber = true,
|
||||
viewString = true,
|
||||
viewStringMax = 1000
|
||||
},
|
||||
runtime = {
|
||||
version = 'Lua 5.4',
|
||||
path = {
|
||||
"?.lua",
|
||||
"?/init.lua",
|
||||
"?/?.lua",
|
||||
"/usr/share/5.4/?.lua",
|
||||
"/usr/share/lua/5.4/?/init.lua"
|
||||
}
|
||||
},
|
||||
signatureHelp = {
|
||||
enable = true
|
||||
},
|
||||
workspace = {
|
||||
library = {
|
||||
DATADIR,
|
||||
USERDIR
|
||||
},
|
||||
maxPreload = 2000,
|
||||
preloadFileSize = 1000
|
||||
},
|
||||
telemetry = {
|
||||
enable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---# svelte-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/sveltejs/language-tools/tree/master/packages/language-server
|
||||
--- __Installation__: `npm install -g svelte-language-server`
|
||||
--- __Note__: Also don't forget to install any additional optional dependencies
|
||||
--- for additional features (see official site for details).
|
||||
lspconfig.sveltels = add_lsp {
|
||||
name = "sveltels",
|
||||
language = "svelte",
|
||||
file_patterns = { "%.svelte$" },
|
||||
command = { 'svelteserver', '--stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Tailwind CSS
|
||||
--- __Status__: Broken (freezes when writing class names inside html doc, requires new implementation of json.lua)
|
||||
--- __Site__: https://github.com/tailwindlabs/tailwindcss-intellisense
|
||||
--- __Installation__: Arch Linux: `sudo pacman -S tailwindcss-language-server`
|
||||
lspconfig.tailwindcss = add_lsp {
|
||||
name = "tailwindcss",
|
||||
language = "html",
|
||||
file_patterns = { "%.html$"},
|
||||
command = {'tailwindcss-language-server', '--stdio'},
|
||||
fake_snippets = true,
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# LaTeX Texlab language server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/latex-lsp/texlab
|
||||
--- __Installation__: git clone https://github.com/latex-lsp/texlab.git , then inside the texlab folder, run: cargo build --release
|
||||
--- __Note__: Rust has to be installed
|
||||
lspconfig.texlab = add_lsp {
|
||||
name = "texlab",
|
||||
language = "latex",
|
||||
file_patterns = { "%.tex$", "%.bib$" , "%.dtx$", "%.sty$", "%.ins$", "%.cls$" },
|
||||
command = { 'texlab' }
|
||||
}
|
||||
|
||||
---# TOML - Taplo
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/tamasfe/taplo
|
||||
--- __Installation__: 'sudo pacman -S taplo-cli'
|
||||
lspconfig.taplo = add_lsp {
|
||||
name = "taplo",
|
||||
language = "toml",
|
||||
file_patterns = { "%.toml$" },
|
||||
command = { "taplo", "lsp", "stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# typescript-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/typescript-language-server/typescript-language-server
|
||||
--- __Installation__: `npm install -g typescript-language-server typescript`
|
||||
lspconfig.tsserver = add_lsp {
|
||||
name = "typescript-language-server",
|
||||
language = {
|
||||
{ id = "javascript", pattern = "%.[cm]?js$" },
|
||||
{ id = "javascriptreact", pattern = "%.jsx$" },
|
||||
{ id = "typescript", pattern = "%.ts$" },
|
||||
{ id = "typescriptreact", pattern = "%.tsx$" },
|
||||
},
|
||||
file_patterns = { "%.jsx?$", "%.[cm]js$", "%.tsx?$" },
|
||||
command = { 'typescript-language-server', '--stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# typst-lsp
|
||||
--- __Status: Works
|
||||
--- __Site__: https://github.com/nvarner/typst-lsp
|
||||
--- __Instalation__: `yay typst-lsp-bin`
|
||||
lspconfig.typst_lsp = add_lsp {
|
||||
name = "typst-lsp",
|
||||
language = "typst",
|
||||
file_patterns = { "%.typ$" },
|
||||
command = { 'typst-lsp' },
|
||||
verbose = false,
|
||||
settings = {
|
||||
exportPdf = "never", -- Choose onType, onSave or never.
|
||||
experimentalFormatterMode = "on" -- Choose on, or off
|
||||
}
|
||||
}
|
||||
|
||||
---# vim-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/iamcco/vim-language-server
|
||||
--- __Installation__: `npm install -g vim-language-server`
|
||||
lspconfig.vimls = add_lsp {
|
||||
name = "vim-language-server",
|
||||
language = "vim",
|
||||
file_patterns = { "%.vim$" },
|
||||
command = { 'vim-language-server', '--stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# V
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/vlang/v-analyzer
|
||||
--- __Installation__: https://github.com/vlang/v-analyzer?tab=readme-ov-file#installation
|
||||
lspconfig.v_analyzer = add_lsp {
|
||||
name = "v_analyzer",
|
||||
language = "v",
|
||||
file_patterns = { "%.vv?$", "%.vsh$" },
|
||||
command = { "v-analyzer", "--stdio" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Vala - vala-language-server
|
||||
--- __Status__: Works
|
||||
--- __Site__: https://github.com/vala-lang/vala-language-server
|
||||
--- __Installation__: `paru -S vala-language-server`
|
||||
lspconfig.vala_ls = add_lsp {
|
||||
name = "vala_ls",
|
||||
language = "vala",
|
||||
file_patterns = { "%.vala$" },
|
||||
command = { "vala-language-server" },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# vlang-vls
|
||||
--- __Status__: doesn't respond to completion requests (no longer officially maintained in favor of v-analyzer)
|
||||
--- __Site__: https://github.com/vlang/vls
|
||||
--- __Installation__: https://github.com/vlang/vls?tab=readme-ov-file#installation
|
||||
lspconfig.vls = add_lsp {
|
||||
name = "vlang-vls",
|
||||
language = "v",
|
||||
file_patterns = { "%.vv?$", "%.vsh$" },
|
||||
command = { 'vlang-vls' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# yaml-language-server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/redhat-developer/yaml-language-server
|
||||
--- __Installation__: See official website for instructions
|
||||
lspconfig.yamlls = add_lsp {
|
||||
name = "yaml-language-server",
|
||||
language = "yaml",
|
||||
file_patterns = { "%.yml$", "%.yaml$" },
|
||||
command = { 'yaml-language-server', '--stdio' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
---# Zig Language Server
|
||||
--- __Status__: Untested
|
||||
--- __Site__: https://github.com/zigtools/zls
|
||||
--- __Installation__: See official website for instructions
|
||||
lspconfig.zls = add_lsp {
|
||||
name = "zls",
|
||||
language = "zig",
|
||||
file_patterns = { "%.zig$" },
|
||||
command = { 'zls' },
|
||||
verbose = false
|
||||
}
|
||||
|
||||
return lspconfig
|
||||
305
.config/lite-xl/plugins/lsp/diagnostics.lua
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
-- 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 filename string
|
||||
---@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
|
||||
|
||||
---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)
|
||||
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 == 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 == 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]
|
||||
|
||||
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]
|
||||
|
||||
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
.config/lite-xl/plugins/lsp/fonts/README.md
Normal file
|
|
@ -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
.config/lite-xl/plugins/lsp/fonts/generate-fonts.py
Executable file
|
|
@ -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
.config/lite-xl/plugins/lsp/fonts/symbols.ttf
Normal file
32
.config/lite-xl/plugins/lsp/helpdoc.lua
Normal file
|
|
@ -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
|
||||
2618
.config/lite-xl/plugins/lsp/init.lua
Normal file
532
.config/lite-xl/plugins/lsp/json.lua
Normal file
|
|
@ -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
|
||||
580
.config/lite-xl/plugins/lsp/listbox.lua
Normal file
|
|
@ -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 = system.get_window_size()
|
||||
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 = system.get_window_size() - 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
.config/lite-xl/plugins/lsp/manifest.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"addons": [
|
||||
{
|
||||
"id": "lsp",
|
||||
"name": "Language Server Protocol",
|
||||
"description": "Provides intellisense by leveraging the LSP protocol.",
|
||||
"version": "0.8",
|
||||
"mod_version": "3",
|
||||
"dependencies": {
|
||||
"widget": { "version": ">=0.1" },
|
||||
"lintplus": { "version": ">=0.2", "optional": true },
|
||||
"lsp_snippets": { "optional": true }
|
||||
},
|
||||
"tags": [
|
||||
"language server protocol"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
.config/lite-xl/plugins/lsp/screenshots/completion01.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/completion02.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/completion03.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/completion04.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/diagnostics01.png
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/docsym01.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/docsym02.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/gotodef01.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/hover01.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/hover02.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
.config/lite-xl/plugins/lsp/screenshots/signatures01.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
1662
.config/lite-xl/plugins/lsp/server.lua
Normal file
121
.config/lite-xl/plugins/lsp/symbolresults.lua
Normal file
|
|
@ -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
|
||||
|
||||
101
.config/lite-xl/plugins/lsp/timer.lua
Normal file
|
|
@ -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
|
||||
534
.config/lite-xl/plugins/lsp/util.lua
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
-- 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)
|
||||
-- make path separator consistent
|
||||
abs_filename = abs_filename:gsub("\\", "/")
|
||||
for _, doc in ipairs(core.docs) do
|
||||
---@cast doc core.doc
|
||||
if doc.abs_filename then
|
||||
local doc_path = doc.abs_filename:gsub("\\", "/")
|
||||
if doc_path == abs_filename then
|
||||
return true;
|
||||
end
|
||||
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)
|
||||
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
|
||||
|
||||
---Check if a command exists on the system by inspecting the PATH envar.
|
||||
---@param command string
|
||||
---@return boolean
|
||||
function util.command_exists(command)
|
||||
local command_win = nil
|
||||
|
||||
if PLATFORM == "Windows" then
|
||||
if not command:find("%.exe$") then
|
||||
command_win = command .. ".exe"
|
||||
end
|
||||
end
|
||||
|
||||
if
|
||||
util.file_exists(command)
|
||||
or
|
||||
(command_win and util.file_exists(command_win))
|
||||
then
|
||||
return true
|
||||
end
|
||||
|
||||
local env_path = os.getenv("PATH")
|
||||
local path_list = {}
|
||||
|
||||
if PLATFORM ~= "Windows" then
|
||||
path_list = util.split(env_path, ":")
|
||||
else
|
||||
path_list = util.split(env_path, ";")
|
||||
end
|
||||
|
||||
-- Automatic support for brew, macports, etc...
|
||||
if PLATFORM == "Mac OS X" then
|
||||
if
|
||||
system.get_file_info("/usr/local/bin")
|
||||
and
|
||||
not string.find(env_path, "/usr/local/bin", 1, true)
|
||||
then
|
||||
table.insert(path_list, 1, "/usr/local/bin")
|
||||
end
|
||||
end
|
||||
|
||||
for _, path in pairs(path_list) do
|
||||
local path_fix = path:gsub("[/\\]$", "") .. PATHSEP
|
||||
if util.file_exists(path_fix .. command) then
|
||||
return true
|
||||
elseif command_win and util.file_exists(path_fix .. command_win) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---From a list of executable names get the one that is installed
|
||||
---on the system or first one if none of them exists.
|
||||
---@param executables table<integer,string>
|
||||
---@return string executable_name
|
||||
function util.get_best_executable(executables)
|
||||
for _, executable in ipairs(executables) do
|
||||
if util.command_exists(executable) then
|
||||
return executable
|
||||
end
|
||||
end
|
||||
return executables[1]
|
||||
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
|
||||