Initial commit

This commit is contained in:
Patrick Alvin Alcala 2025-06-26 16:53:43 +08:00
commit 209ba130c0
4852 changed files with 1517959 additions and 0 deletions

View 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.

View 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
![Completion](screenshots/completion01.png)
![Completion](screenshots/completion02.png)
![Completion](screenshots/completion03.png)
![Completion](screenshots/completion04.png)
### Symbol hover
![Hover](screenshots/hover01.png)
![Hover](screenshots/hover02.png)
### Function signatures
![Signature](screenshots/signatures01.png)
### Document symbols
![Doc Symbols](screenshots/docsym01.png)
![Doc Symbols](screenshots/docsym02.png)
### Goto definition
![Goto Definition](screenshots/gotodef01.png)
### Diagnostics rendering using Lint+
![Diagnostics](screenshots/diagnostics01.png)
[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

View 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

View 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

View 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

View 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")

Binary file not shown.

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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"
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because it is too large Load diff

View 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

View 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

View 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