commit
abf6c9c47d
13 changed files with 1824 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||||||
|
/backups |
||||||
|
/buffers |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"Alt-/": "lua:comment.comment", |
||||||
|
"Alt-d": "command:definition", |
||||||
|
"Alt-f": "command:format", |
||||||
|
"Alt-k": "command:hover", |
||||||
|
"Alt-r": "command:references", |
||||||
|
"CtrlSpace": "command:lspcompletion", |
||||||
|
"CtrlUnderscore": "lua:comment.comment" |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
color-link comment "#00FF00" |
||||||
|
color-link identifier "#66D9EF" |
||||||
|
color-link constant "#00AAFF" |
||||||
|
color-link constant.number "#55FFFF" |
||||||
|
color-link constant.string "#FF55FF" |
||||||
|
color-link constant.string.char "#FF55FF" |
||||||
|
color-link statement "#00AAFF" |
||||||
|
color-link symbol "#FF0000" |
||||||
|
color-link symbol.operator "#FF5500" |
||||||
|
color-link symbol.brackets "#FF5500" |
||||||
|
color-link preproc "#7F0000" |
||||||
|
color-link type "#FFAA00" |
||||||
|
color-link selection "#FFFFFF,#6EA1F1" |
||||||
|
color-link special "#AAFFFF" |
||||||
|
color-link underlined "bold #FF55FF" |
||||||
|
color-link error "bold #FF0000" |
||||||
|
color-link todo "bold #FFAA00" |
||||||
|
color-link hlsearch "#282828,#E6DB74" |
||||||
|
color-link statusline "#FFFFFF,#0055FF" |
||||||
|
color-link tabbar "#282828,#F8F8F2" |
||||||
|
color-link indent-char "#505050" |
||||||
|
color-link line-number "#888888,#444444" |
||||||
|
color-link current-line-number "#FFFFFF,#222222" |
||||||
|
color-link diff-added "#00AF00" |
||||||
|
color-link diff-modified "#FFAF00" |
||||||
|
color-link diff-deleted "#D70000" |
||||||
|
color-link gutter-error "#CB4B16" |
||||||
|
color-link gutter-warning "#E6DB74" |
||||||
|
color-link cursor-line "#323232" |
||||||
|
color-link color-column "#323232" |
||||||
|
#No extended types; Plain brackets. |
||||||
|
color-link type.extended "default" |
||||||
|
#color-link symbol.brackets "default" |
||||||
|
color-link symbol.tag "#AE81FF" |
||||||
@ -0,0 +1,93 @@ |
|||||||
|
VERSION = "1.0.0" |
||||||
|
|
||||||
|
local uutil = import("micro/util") |
||||||
|
local utf8 = import("utf8") |
||||||
|
local autoclose_pairs = {'""', "''", "``", "()", "{}", "[]"} |
||||||
|
local conditional_autoclose_pairs = {"<>", "||"} |
||||||
|
local auto_newline_pairs = {"()", "{}", "[]"} |
||||||
|
|
||||||
|
function char_at(str, i) |
||||||
|
-- lua indexing is one off from go |
||||||
|
return uutil.RuneAt(str, i - 1) |
||||||
|
end |
||||||
|
|
||||||
|
function onRune(bp, r) |
||||||
|
for i = 1, #autoclose_pairs do |
||||||
|
local pair_at_index = autoclose_pairs[i] |
||||||
|
|
||||||
|
if r == char_at(pair_at_index, 2) then |
||||||
|
local cur_line = bp.Buf:Line(bp.Cursor.Y) |
||||||
|
|
||||||
|
if char_at(cur_line, bp.Cursor.X + 1) == char_at(pair_at_index, 2) then |
||||||
|
bp:Backspace() |
||||||
|
bp:CursorRight() |
||||||
|
|
||||||
|
break |
||||||
|
end |
||||||
|
|
||||||
|
if bp.Cursor.X > 1 |
||||||
|
and (uutil.IsWordChar(char_at(cur_line, bp.Cursor.X - 1)) |
||||||
|
or char_at(cur_line, bp.Cursor.X - 1) == char_at(pair_at_index, 1)) |
||||||
|
then |
||||||
|
break |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if r == char_at(pair_at_index, 1) then |
||||||
|
local cur_line = bp.Buf:Line(bp.Cursor.Y) |
||||||
|
|
||||||
|
if bp.Cursor.X == uutil.CharacterCountInString(cur_line) |
||||||
|
or not uutil.IsWordChar(char_at(cur_line, bp.Cursor.X + 1)) |
||||||
|
then |
||||||
|
-- the '-' here is to derefence the pointer to bp.Cursor.Loc which is automatically made |
||||||
|
-- when converting go structs to lua |
||||||
|
-- It needs to be dereferenced because the function expects a non pointer struct |
||||||
|
bp.Buf:Insert(bp.Cursor.Loc * -1, char_at(pair_at_index, 2)) |
||||||
|
bp:CursorLeft() |
||||||
|
|
||||||
|
break |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return true |
||||||
|
end |
||||||
|
|
||||||
|
function preInsertNewline(bp) |
||||||
|
local cur_line = bp.Buf:Line(bp.Cursor.Y) |
||||||
|
local curRune = char_at(cur_line, bp.Cursor.X) |
||||||
|
local nextRune = char_at(cur_line, bp.Cursor.X + 1) |
||||||
|
local ws = uutil.GetLeadingWhitespace(cur_line) |
||||||
|
|
||||||
|
for i = 1, #auto_newline_pairs do |
||||||
|
local autoNewLinePairsAtIndex = auto_newline_pairs[i] |
||||||
|
|
||||||
|
if curRune == char_at(autoNewlinePairsAtIndex, 1) then |
||||||
|
if nextRune == char_at(autoNewlinePairsAtIndex, 2) then |
||||||
|
bp:InsertNewline() |
||||||
|
bp:InsertTab() |
||||||
|
bp.Buf:Insert(bp.Cursor.Loc * -1, "\n" .. ws) |
||||||
|
bp:StartOfLine() |
||||||
|
bp:CursorLeft() |
||||||
|
return false |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return true |
||||||
|
end |
||||||
|
|
||||||
|
function preBackspace(bp) |
||||||
|
for i = 1, #autoclose_pairs do |
||||||
|
local pair_at_index = autoclose_pairs[i] |
||||||
|
local cur_line = bp.Buf:Line(bp.Cursor.Y) |
||||||
|
|
||||||
|
if char_at(cur_line, bp.Cursor.X + 1) == char_at(pair_at_index, 2) |
||||||
|
and char_at(cur_line, bp.Cursor.X) == char_at(pair_at_index, 1) |
||||||
|
then |
||||||
|
bp:Delete() |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return true |
||||||
|
end |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
MIT License |
||||||
|
|
||||||
|
Copyright (c) 2022 Robert Kunze |
||||||
|
|
||||||
|
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. |
||||||
@ -0,0 +1,93 @@ |
|||||||
|
# Micro Plugin LSP Client |
||||||
|
|
||||||
|
**Please note:** This software is very much not finished. It is more like a |
||||||
|
proof of concept and might break if you call it names. |
||||||
|
|
||||||
|
Provides LSP methods as actions to Micro that can subsequently be mapped to key |
||||||
|
bindings. |
||||||
|
|
||||||
|
Currently implemented methods: |
||||||
|
|
||||||
|
- textDocument/hover |
||||||
|
- textDocument/definition |
||||||
|
- textDocument/completion |
||||||
|
- textDocument/formatting |
||||||
|
- textDocument/references |
||||||
|
|
||||||
|
If possible, this plugin will register the following shortcuts: |
||||||
|
|
||||||
|
- Alt-k for hover |
||||||
|
- Alt-d for definition lookup |
||||||
|
- Alt-f for formatting |
||||||
|
- Alt-r for looking up references |
||||||
|
- Ctrl-space for completion |
||||||
|
|
||||||
|
## Installation |
||||||
|
|
||||||
|
Clone this repo into micro's plug folder: |
||||||
|
|
||||||
|
``` |
||||||
|
$ git clone https://github.com/AndCake/micro-plugin-lsp ~/.config/micro/plug/lsp |
||||||
|
``` |
||||||
|
|
||||||
|
## Configuration |
||||||
|
|
||||||
|
In your `settings.json`, you add the `lsp.server` option in order to enable |
||||||
|
using it for your languages' server. |
||||||
|
|
||||||
|
Example: |
||||||
|
|
||||||
|
``` |
||||||
|
{ |
||||||
|
"lsp.server": "python=pyls,go=gopls,typescript=deno lsp,rust=rls", |
||||||
|
"lsp.formatOnSave": true, |
||||||
|
"lsp.ignoreMessages": "LS message1 to ignore|LS message 2 to ignore|...", |
||||||
|
"lsp.tabcompletion": true, |
||||||
|
"lsp.ignoreTriggerCharacters": "completion,signature", |
||||||
|
"lsp.autocompleteDetails": false |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The format for the `lsp.server` value is a comma-separated list for each file |
||||||
|
type you want to boot up a language server: |
||||||
|
|
||||||
|
``` |
||||||
|
<file type>=<executable with arguments where necessary>[=<initialization options passed to language server>][,...] |
||||||
|
``` |
||||||
|
|
||||||
|
You can also use an environment variable called `MICRO_LSP` to define the same |
||||||
|
information. If set, it will override the `lsp.server` from the `settings.json`. |
||||||
|
You can add a line such as the following to your shell profile (e.g. .bashrc): |
||||||
|
|
||||||
|
``` |
||||||
|
export MICRO_LSP='python=pyls,go=gopls,typescript=deno lsp={"importMap":"import_map.json"},rust=rls' |
||||||
|
``` |
||||||
|
|
||||||
|
If neither the MICRO_LSP nor the lsp.server is set, then the plugin falls back |
||||||
|
to the following settings: |
||||||
|
|
||||||
|
``` |
||||||
|
python=pylsp,go=gopls,typescript=deno lsp,javascript=deno lsp,markdown=deno lsp,json=deno lsp,jsonc=deno lsp,rust=rls,lua=lua-lsp,c++=clangd |
||||||
|
``` |
||||||
|
|
||||||
|
The option `lsp.autocompleteDetails` allows for showing all auto-completions in |
||||||
|
a horizontally split buffer view (true) instead of the status line (false). |
||||||
|
|
||||||
|
## Testing |
||||||
|
|
||||||
|
This plugin has been tested briefly with the following language servers: |
||||||
|
|
||||||
|
- C++ [clangd](https://clangd.llvm.org) / |
||||||
|
[ccls](https://github.com/MaskRay/ccls) |
||||||
|
- go: [gopls](https://pkg.go.dev/golang.org/x/tools/gopls#section-readme) |
||||||
|
- markdown, JSON, typescript, javascript (including JSX/TSX): |
||||||
|
[deno](https://deno.land/) |
||||||
|
- python: pyls, [pylsp](https://github.com/python-lsp/python-lsp-server) |
||||||
|
- rust: [rls](https://github.com/rust-lang/rls) |
||||||
|
- lua: [lua-lsp](https://github.com/Alloyed/lua-lsp) |
||||||
|
- zig: [zls](https://github.com/zigtools/zls) |
||||||
|
|
||||||
|
## Known issues |
||||||
|
|
||||||
|
Not all possible types of modification events to the file are currently being |
||||||
|
sent to the language server. Saving the file will re-synchronize it, though. |
||||||
@ -0,0 +1,315 @@ |
|||||||
|
# Micro Plugin LSP Client |
||||||
|
|
||||||
|
LSP is a Language Server Protocol client. Features include function signatures |
||||||
|
and jump to definition. |
||||||
|
|
||||||
|
This help page can be viewed in Micro editor with Ctrl-E 'help lsp' |
||||||
|
|
||||||
|
## Features and Shortcuts |
||||||
|
|
||||||
|
- Show function signature on status bar (alt-K) (textDocument/hover) |
||||||
|
- Open function definition in a new tab (alt-D) (textDocument/definition) |
||||||
|
- Format document (alt-F) (textDocument/formatting) |
||||||
|
- Show references to the current symbol in a buffer (alt-R) |
||||||
|
(textDocument/references), pressing return on the reference line, the |
||||||
|
reference's location is opened in a new tab |
||||||
|
|
||||||
|
There is initial support for completion (ctrl-space) (textDocument/completion). |
||||||
|
|
||||||
|
## Supported languages |
||||||
|
|
||||||
|
Installation instructions for Go and Python are provided below. LSP Plugin has |
||||||
|
been briefly tested with |
||||||
|
|
||||||
|
- C++: [clangd](https://clangd.llvm.org) / |
||||||
|
[ccls](https://github.com/MaskRay/ccls) |
||||||
|
- go: [gopls](https://pkg.go.dev/golang.org/x/tools/gopls#section-readme) |
||||||
|
- markdown, JSON, typescript, javascript (including JSX/TSX): |
||||||
|
[deno](https://deno.land/) |
||||||
|
- python: pyls and [pylsp](https://github.com/python-lsp/python-lsp-server) |
||||||
|
- rust: [rls](https://github.com/rust-lang/rls) |
||||||
|
- lua: [lua-lsp](https://github.com/Alloyed/lua-lsp) |
||||||
|
|
||||||
|
## Install LSP plugin |
||||||
|
|
||||||
|
$ micro --plugin install lsp |
||||||
|
|
||||||
|
To configure the LSP Plugin, you can add two lines to settings.json |
||||||
|
|
||||||
|
$ micro settings.json |
||||||
|
|
||||||
|
Add lines |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"lsp.server": "python=pylsp,go=gopls,typescript=deno lsp={\"importMap\": \"./import_map.json\"}", |
||||||
|
"lsp.formatOnSave": true |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Remember to add comma to previous line. Depending on the language server, |
||||||
|
automatic code formating can be quite opinionated. In that case, you can simply |
||||||
|
set lsp.formatOnSave to false. |
||||||
|
|
||||||
|
For Python language server, the currently maintained fork is 'pylsp'. If you |
||||||
|
wish to use the Palantir version (last updated in 2020) instead, set |
||||||
|
"python=pyls" in lsp.server. |
||||||
|
|
||||||
|
If your lsp.server settings are autoremoved, you can |
||||||
|
|
||||||
|
$ export MICRO_LSP='python=pylsp,go=gopls,typescript=deno lsp={"importMap":"import_map.json"},rust=rls' |
||||||
|
|
||||||
|
The lsp.server default settings (if no others are defined) are: |
||||||
|
|
||||||
|
``` |
||||||
|
python=pylsp,go=gopls,typescript=deno lsp,javascript=deno lsp,markdown=deno lsp,json=deno lsp,jsonc=deno lsp,rust=rls,lua=lua-lsp,c++=clangd |
||||||
|
``` |
||||||
|
|
||||||
|
## Install Language Server |
||||||
|
|
||||||
|
To support each language, LSP plugin uses language servers. To use LSP plugin, |
||||||
|
you must install at least one language server. |
||||||
|
|
||||||
|
If you want to quickly test LSP plugin, Go language server gopls is simple to |
||||||
|
install. |
||||||
|
|
||||||
|
### gopls, Go language server |
||||||
|
|
||||||
|
You will need command 'gopls' |
||||||
|
|
||||||
|
$ gopls version |
||||||
|
golang.org/x/tools/gopls v0.7.3 |
||||||
|
|
||||||
|
In Debian, this is installed with |
||||||
|
|
||||||
|
$ sudo apt-get update |
||||||
|
$ sudo apt-get -y install golang-go gopls |
||||||
|
|
||||||
|
To test it, write a short go program |
||||||
|
|
||||||
|
$ micro hello.go |
||||||
|
|
||||||
|
```go |
||||||
|
package main |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
func main() { |
||||||
|
fmt.Println("hello world") |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Move cursor over Println and press alt-k. The function signature is shown on the |
||||||
|
bottom of the screen, in Micro status bar. It shows you what parameters the |
||||||
|
function can take. The signature should look similar to this: "func |
||||||
|
fmt.Println(a ...interface{}) (n int, err error)Println formats using the |
||||||
|
default formats..." |
||||||
|
|
||||||
|
Can you see the function signature with alt-k? If you can, you have succesfully |
||||||
|
installed Micro LSP plugin and GoPLS language server. |
||||||
|
|
||||||
|
Keep your cursor over Println, and press alt-d. The file defining Println opens. |
||||||
|
In this case, it's fmt/print.go. As Go reference documentation is in code |
||||||
|
comments, this is very convenient. You can navigate between tabs with atl-, |
||||||
|
(alt-comma) and alt-. (alt - full stop). To close the tab, press Ctrl-Q. |
||||||
|
|
||||||
|
### Markdown, JSON/JSONC, Typescript, Javascript |
||||||
|
|
||||||
|
The Deno LSP server will provide full support for Typescript and Javascript. |
||||||
|
Additionally, it supports formatting for Markdown and JSON files. The |
||||||
|
installation of this is fairly straight forward: |
||||||
|
|
||||||
|
On Mac/Linux: |
||||||
|
|
||||||
|
$ curl -fsSL https://deno.land/install.sh | sh |
||||||
|
|
||||||
|
On Powershell: |
||||||
|
|
||||||
|
$ iwr https://deno.land/install.ps1 -useb | iex |
||||||
|
|
||||||
|
### typescript-language-server |
||||||
|
|
||||||
|
This LSP server will allow for Javascript as well as Typescript support. For |
||||||
|
using it, you first need to install it using NPM: |
||||||
|
|
||||||
|
$ npm install -g typescript-language-server typescript |
||||||
|
|
||||||
|
Once it has been installed, you can use it like so: |
||||||
|
|
||||||
|
$ micro hello.js |
||||||
|
|
||||||
|
Press ctrl-e and type in: |
||||||
|
|
||||||
|
set lsp.server "typescript=typescript-language-server --stdio,javascript=typescript-language-server --stdio" |
||||||
|
|
||||||
|
After you restarted micro, you can use the features for typescript and |
||||||
|
javascript accordingly. |
||||||
|
|
||||||
|
### pylsp, Python language server |
||||||
|
|
||||||
|
Installing Python language server PyLSP is a bit more involved. |
||||||
|
|
||||||
|
You will need 'virtualenv' command to create virtual environments and 'pip' to |
||||||
|
install Python packages. You can also use one of the many other commands for |
||||||
|
keeping your 'pip' packages in order. |
||||||
|
|
||||||
|
In Debian, these are installed with |
||||||
|
|
||||||
|
$ sudo apt-get update |
||||||
|
$ sudo apt-get install python-pip virtualenv |
||||||
|
|
||||||
|
Create a new virtual environment |
||||||
|
|
||||||
|
$ mkdir somePythonProject; cd somePythonProject |
||||||
|
$ virtualenv -p python3 env/ |
||||||
|
$ source env/bin/activate |
||||||
|
|
||||||
|
Your prompt likely shows "(env)" to confirm you're inside your virtual |
||||||
|
environment. |
||||||
|
|
||||||
|
List the packages you want installed. |
||||||
|
|
||||||
|
$ micro requirements.txt |
||||||
|
|
||||||
|
This list is to provide the most useful suggestions. If you would like to get a |
||||||
|
lot more opinionated advice, such as adding two empty lines between functions, |
||||||
|
you could use "python-lsp-server[all]". The mypy package provides optional |
||||||
|
static type checking. requirements.txt: |
||||||
|
|
||||||
|
``` |
||||||
|
python-lsp-server[rope,pyflakes,mccabe,pylsp-mypy] |
||||||
|
pylsp-mypy |
||||||
|
``` |
||||||
|
|
||||||
|
And actually install |
||||||
|
|
||||||
|
$ pip install -r requirements.txt |
||||||
|
|
||||||
|
No you can test your Python environment |
||||||
|
|
||||||
|
$ micro hello.py |
||||||
|
|
||||||
|
```python |
||||||
|
def helloWorld(): |
||||||
|
return a |
||||||
|
``` |
||||||
|
|
||||||
|
Save with Ctrl-S. A red warning sign ">>" lists up in the gutter, on the left |
||||||
|
side of Micro. Move cursor to the line "return a". The status bar shows the |
||||||
|
warning: "undefined name 'a'". Well done, you have now installed Python LSP |
||||||
|
support for Micro. |
||||||
|
|
||||||
|
MyPy provides optional static type setting. You can write normally, and type |
||||||
|
checking is ignored. You can define types for some functions, and you get |
||||||
|
automatic warnings for incorrect use of types. This is how types are marked: |
||||||
|
|
||||||
|
```python |
||||||
|
def square(x: int) -> int: |
||||||
|
return x*x |
||||||
|
``` |
||||||
|
|
||||||
|
Depending on your project, taste and installed linters, pylsp sometimes shows |
||||||
|
warnings you would like to hide. Hiding messages is possible using |
||||||
|
lsp.ignoreMessages, explained in later in this help document. |
||||||
|
|
||||||
|
### lua-lsp, Lua language server |
||||||
|
|
||||||
|
These are the initial installation instructions. This installation will support |
||||||
|
linter messages in the gutter (on the left of editing area) and jump to |
||||||
|
definition inside the same file (alt-D). All LSP features are not yet supported |
||||||
|
with Lua. |
||||||
|
|
||||||
|
Install 'luarocks' command using your package manager. For example, on Debian |
||||||
|
|
||||||
|
$ sudo apt-get update |
||||||
|
$ sudo apt-get -y install luarocks |
||||||
|
|
||||||
|
Use luarocks to install helper packages used by lua-lsp |
||||||
|
|
||||||
|
$ sudo luarocks install luacheck |
||||||
|
$ sudo luarocks install Formatter |
||||||
|
$ sudo luarocks install lcf |
||||||
|
|
||||||
|
Install lua-lsp, the Lua language server |
||||||
|
|
||||||
|
$ sudo luarocks install --server=ssh://luarocks.org/dev lua-lsp |
||||||
|
|
||||||
|
This command uses different URL from official lua-lsp instructions due to |
||||||
|
[a change in how packages are downloaded](https://github.com/Alloyed/lua-lsp/issues/45). |
||||||
|
This command uses ssh instead of http. |
||||||
|
|
||||||
|
To test it, open a Lua file |
||||||
|
|
||||||
|
$ micro $HOME/.config/micro/plug/lsp/main.lua |
||||||
|
|
||||||
|
Can you see some linter warnings ">>" in the gutter? Can you jump to functions |
||||||
|
inside the same file with Alt-D? Well done, you've installed Lua LSP support for |
||||||
|
micro. |
||||||
|
|
||||||
|
All features don't work yet with Lua LSP. |
||||||
|
|
||||||
|
### zls, ZIG language server |
||||||
|
|
||||||
|
The ZIG language server provides formatting, goto definition, auto-completion as |
||||||
|
well as hover and references. It can be installed by following |
||||||
|
[these instruction](https://github.com/zigtools/zls). |
||||||
|
|
||||||
|
Once installed, open micro, press ctrl+e and type the following command: |
||||||
|
|
||||||
|
set lsp.server zig=zls |
||||||
|
|
||||||
|
Close micro again and open a zig file. |
||||||
|
|
||||||
|
## Ignoring unhelpful messages |
||||||
|
|
||||||
|
In addition to providing assistance while coding, some language servers can show |
||||||
|
spurious, unnecessary or too oppinionated messages. Sometimes, it's not obvious |
||||||
|
how these messages are disable using language server settings. |
||||||
|
|
||||||
|
This plugin allows you to selectively ignore unwanted warnings while keeping |
||||||
|
others. This is done my matching the start of the message. By default, nothing |
||||||
|
is ignored. |
||||||
|
|
||||||
|
Consider a case where you're working with an external Python project that |
||||||
|
indents with tabs. When joining an existing project, you might not want to |
||||||
|
impose your own conventions to every code file. On the other hand, LSP support |
||||||
|
is not useful if nearly every line is marked with a warning. |
||||||
|
|
||||||
|
Moving the cursor to a line with the warning, you see that the line starts with |
||||||
|
"W191 indentation contains tabs". This, and similar unhelpful messages (in the |
||||||
|
context of your current project) can be ignored by editing |
||||||
|
~/.config/micro/settings.json |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"lsp.ignoreMessages": "Skipping analyzing |W191 indentation contains tabs|E101 indentation contains mixed spaces and tabs|See https://mypy.readthedocs.io/en" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
As you now open the same file, you can see that warning "W191 indentation |
||||||
|
contains tabs" is no longer shown. Also the warning mark ">>" in the gutter is |
||||||
|
gone. Try referring to a variable that does not exist, and you can see a helpful |
||||||
|
warning appear. You have now disabled the warnings you don't need, while keeping |
||||||
|
the useful ones. |
||||||
|
|
||||||
|
## See also |
||||||
|
|
||||||
|
[Official repostory](https://github.com/AndCake/micro-plugin-lsp) |
||||||
|
|
||||||
|
[Usage examples with screenshots](https://terokarvinen.com/2022/micro-editor-lsp-support-python-and-go-jump-to-definition-show-function-signature/) |
||||||
|
|
||||||
|
[Language Server Protocol](https://microsoft.github.io/language-server-protocol/) |
||||||
|
|
||||||
|
[gopls - the Go language server](https://pkg.go.dev/golang.org/x/tools/gopls) |
||||||
|
|
||||||
|
[pylsp - Python LSP Server](https://github.com/python-lsp/python-lsp-server) |
||||||
|
|
||||||
|
[mypy - Optional Static Typing for Python](http://mypy-lang.org/) |
||||||
|
|
||||||
|
[rls - Rust Language Server](https://github.com/rust-lang/rls) |
||||||
|
|
||||||
|
[deno](https://deno.land/) |
||||||
|
|
||||||
|
[typescript-language-server](https://www.npmjs.com/package/typescript-language-server) |
||||||
|
|
||||||
|
[lua-lsp - A Lua language server](https://github.com/Alloyed/lua-lsp) |
||||||
@ -0,0 +1,933 @@ |
|||||||
|
VERSION = "0.6.2" |
||||||
|
|
||||||
|
local micro = import("micro") |
||||||
|
local config = import("micro/config") |
||||||
|
local shell = import("micro/shell") |
||||||
|
local util = import("micro/util") |
||||||
|
local buffer = import("micro/buffer") |
||||||
|
local fmt = import("fmt") |
||||||
|
local go_os = import("os") |
||||||
|
local path = import("path") |
||||||
|
local filepath = import("path/filepath") |
||||||
|
|
||||||
|
local cmd = {} |
||||||
|
local id = {} |
||||||
|
local version = {} |
||||||
|
local currentAction = {} |
||||||
|
local capabilities = {} |
||||||
|
local filetype = '' |
||||||
|
local rootUri = '' |
||||||
|
local message = '' |
||||||
|
local completionCursor = 0 |
||||||
|
local lastCompletion = {} |
||||||
|
local splitBP = nil |
||||||
|
local tabCount = 0 |
||||||
|
|
||||||
|
local json = {} |
||||||
|
|
||||||
|
function toBytes(str) |
||||||
|
local result = {} |
||||||
|
for i=1,#str do |
||||||
|
local b = str:byte(i) |
||||||
|
if b < 32 then |
||||||
|
table.insert(result, b) |
||||||
|
end |
||||||
|
end |
||||||
|
return result |
||||||
|
end |
||||||
|
|
||||||
|
function getUriFromBuf(buf) |
||||||
|
if buf == nil then return; end |
||||||
|
local file = buf.AbsPath |
||||||
|
local uri = fmt.Sprintf("file://%s", file) |
||||||
|
return uri |
||||||
|
end |
||||||
|
|
||||||
|
function mysplit (inputstr, sep) |
||||||
|
if sep == nil then |
||||||
|
sep = "%s" |
||||||
|
end |
||||||
|
local t={} |
||||||
|
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do |
||||||
|
table.insert(t, str) |
||||||
|
end |
||||||
|
return t |
||||||
|
end |
||||||
|
|
||||||
|
function parseOptions(inputstr) |
||||||
|
local t = {} |
||||||
|
inputstr = inputstr:gsub("[%w+_-]+=[^=,]+={.-}", function (str) |
||||||
|
table.insert(t, str) |
||||||
|
return ''; |
||||||
|
end) |
||||||
|
inputstr = inputstr:gsub("[%w+_-]+=[^=,]+", function (str) |
||||||
|
table.insert(t, str) |
||||||
|
return ''; |
||||||
|
end) |
||||||
|
return t |
||||||
|
end |
||||||
|
|
||||||
|
function startServer(filetype, callback) |
||||||
|
local wd, _ = go_os.Getwd() |
||||||
|
rootUri = fmt.Sprintf("file://%s", wd) |
||||||
|
local envSettings, _ = go_os.Getenv("MICRO_LSP") |
||||||
|
local settings = config.GetGlobalOption("lsp.server") |
||||||
|
local fallback = "python=pylsp,go=gopls,typescript=deno lsp,javascript=deno lsp,markdown=deno lsp,json=deno lsp,jsonc=deno lsp,rust=rls,lua=lua-lsp,c++=clangd" |
||||||
|
if envSettings ~= nil and #envSettings > 0 then |
||||||
|
settings = envSettings |
||||||
|
end |
||||||
|
if settings ~= nil and #settings > 0 then |
||||||
|
settings = settings .. "," .. fallback |
||||||
|
else |
||||||
|
settings = fallback |
||||||
|
end |
||||||
|
local server = parseOptions(settings) |
||||||
|
micro.Log("Server Options", server) |
||||||
|
for i in pairs(server) do |
||||||
|
local part = mysplit(server[i], "=") |
||||||
|
local run = mysplit(part[2], "%s") |
||||||
|
local initOptions = part[3] or '{}' |
||||||
|
local runCmd = table.remove(run, 1) |
||||||
|
local args = run |
||||||
|
if filetype == part[1] then |
||||||
|
local send = withSend(part[1]) |
||||||
|
if cmd[part[1]] ~= nil then return; end |
||||||
|
id[part[1]] = 0 |
||||||
|
micro.Log("Starting server", part[1]) |
||||||
|
cmd[part[1]] = shell.JobSpawn(runCmd, args, onStdout(part[1]), onStderr, onExit(part[1]), {}) |
||||||
|
currentAction[part[1]] = { method = "initialize", response = function (bp, data) |
||||||
|
send("initialized", "{}", true) |
||||||
|
capabilities[filetype] = data.result and data.result.capabilities or {} |
||||||
|
callback(bp.Buf, filetype) |
||||||
|
end } |
||||||
|
send(currentAction[part[1]].method, fmt.Sprintf('{"processId": %.0f, "rootUri": "%s", "workspaceFolders": [{"name": "root", "uri": "%s"}], "initializationOptions": %s, "capabilities": {"textDocument": {"hover": {"contentFormat": ["plaintext", "markdown"]}, "publishDiagnostics": {"relatedInformation": false, "versionSupport": false, "codeDescriptionSupport": true, "dataSupport": true}, "signatureHelp": {"signatureInformation": {"documentationFormat": ["plaintext", "markdown"]}}}}}', go_os.Getpid(), rootUri, rootUri, initOptions)) |
||||||
|
return |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function init() |
||||||
|
config.RegisterCommonOption("lsp", "server", "python=pylsp,go=gopls,typescript=deno lsp,javascript=deno lsp,markdown=deno lsp,json=deno lsp,jsonc=deno lsp,rust=rls,lua=lua-lsp,c++=clangd") |
||||||
|
config.RegisterCommonOption("lsp", "formatOnSave", true) |
||||||
|
config.RegisterCommonOption("lsp", "autocompleteDetails", false) |
||||||
|
config.RegisterCommonOption("lsp", "ignoreMessages", "") |
||||||
|
config.RegisterCommonOption("lsp", "tabcompletion", true) |
||||||
|
config.RegisterCommonOption("lsp", "ignoreTriggerCharacters", "completion") |
||||||
|
-- example to ignore all LSP server message starting with these strings: |
||||||
|
-- "lsp.ignoreMessages": "Skipping analyzing |See https://" |
||||||
|
|
||||||
|
config.MakeCommand("hover", hoverAction, config.NoComplete) |
||||||
|
config.MakeCommand("definition", definitionAction, config.NoComplete) |
||||||
|
config.MakeCommand("lspcompletion", completionAction, config.NoComplete) |
||||||
|
config.MakeCommand("format", formatAction, config.NoComplete) |
||||||
|
config.MakeCommand("references", referencesAction, config.NoComplete) |
||||||
|
|
||||||
|
config.TryBindKey("Alt-k", "command:hover", false) |
||||||
|
config.TryBindKey("Alt-d", "command:definition", false) |
||||||
|
config.TryBindKey("Alt-f", "command:format", false) |
||||||
|
config.TryBindKey("Alt-r", "command:references", false) |
||||||
|
config.TryBindKey("CtrlSpace", "command:lspcompletion", false) |
||||||
|
|
||||||
|
config.AddRuntimeFile("lsp", config.RTHelp, "help/lsp.md") |
||||||
|
|
||||||
|
-- @TODO register additional actions here |
||||||
|
end |
||||||
|
|
||||||
|
function withSend(filetype) |
||||||
|
return function (method, params, isNotification) |
||||||
|
if cmd[filetype] == nil then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local msg = fmt.Sprintf('{"jsonrpc": "2.0", %s"method": "%s", "params": %s}', not isNotification and fmt.Sprintf('"id": %.0f, ', id[filetype]) or "", method, params) |
||||||
|
id[filetype] = id[filetype] + 1 |
||||||
|
msg = fmt.Sprintf("Content-Length: %.0f\r\n\r\n%s", #msg, msg) |
||||||
|
--micro.Log("send", filetype, "sending", method or msg, msg) |
||||||
|
shell.JobSend(cmd[filetype], msg) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function preRune(bp, r) |
||||||
|
if splitBP ~= nil then |
||||||
|
pcall(function () splitBP:Unsplit(); end) |
||||||
|
splitBP = nil |
||||||
|
local cur = bp.Buf:GetActiveCursor() |
||||||
|
cur:Deselect(false); |
||||||
|
cur:GotoLoc(buffer.Loc(cur.X + 1, cur.Y)) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- when a new character is types, the document changes |
||||||
|
function onRune(bp, r) |
||||||
|
local filetype = bp.Buf:FileType() |
||||||
|
if cmd[filetype] == nil then |
||||||
|
return |
||||||
|
end |
||||||
|
if splitBP ~= nil then |
||||||
|
pcall(function () splitBP:Unsplit(); end) |
||||||
|
splitBP = nil |
||||||
|
end |
||||||
|
|
||||||
|
local send = withSend(filetype) |
||||||
|
local uri = getUriFromBuf(bp.Buf) |
||||||
|
if r ~= nil then |
||||||
|
lastCompletion = {} |
||||||
|
end |
||||||
|
-- allow the document contents to be escaped properly for the JSON string |
||||||
|
local content = util.String(bp.Buf:Bytes()):gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\r", "\\r"):gsub('"', '\\"'):gsub("\t", "\\t") |
||||||
|
-- increase change version |
||||||
|
version[uri] = (version[uri] or 0) + 1 |
||||||
|
send("textDocument/didChange", fmt.Sprintf('{"textDocument": {"version": %.0f, "uri": "%s"}, "contentChanges": [{"text": "%s"}]}', version[uri], uri, content), true) |
||||||
|
local ignored = mysplit(config.GetGlobalOption("lsp.ignoreTriggerCharacters") or '', ",") |
||||||
|
if r and capabilities[filetype] then |
||||||
|
if not contains(ignored, "completion") and capabilities[filetype].completionProvider and capabilities[filetype].completionProvider.triggerCharacters and contains(capabilities[filetype].completionProvider.triggerCharacters, r) then |
||||||
|
completionAction(bp) |
||||||
|
elseif not contains(ignored, "signature") and capabilities[filetype].signatureHelpProvider and capabilities[filetype].signatureHelpProvider.triggerCharacters and contains(capabilities[filetype].signatureHelpProvider.triggerCharacters, r) then |
||||||
|
hoverAction(bp) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- alias functions for any kind of change to the document |
||||||
|
-- @TODO: add missing ones |
||||||
|
function onBackspace(bp) onRune(bp); end |
||||||
|
function onCut(bp) onRune(bp); end |
||||||
|
function onCutLine(bp) onRune(bp); end |
||||||
|
function onDuplicateLine(bp) onRune(bp); end |
||||||
|
function onDeleteLine(bp) onRune(bp); end |
||||||
|
function onDelete(bp) onRune(bp); end |
||||||
|
function onUndo(bp) onRune(bp); end |
||||||
|
function onRedo(bp) onRune(bp); end |
||||||
|
function onIndent(bp) onRune(bp); end |
||||||
|
function onIndentSelection(bp) onRune(bp); end |
||||||
|
function onPaste(bp) onRune(bp); end |
||||||
|
function onSave(bp) onRune(bp); end |
||||||
|
|
||||||
|
function onEscape(bp) |
||||||
|
if splitBP ~= nil then |
||||||
|
pcall(function () splitBP:Unsplit(); end) |
||||||
|
splitBP = nil |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function preInsertNewline(bp) |
||||||
|
if bp.Buf.Path == "References found" then |
||||||
|
local cur = bp.Buf:GetActiveCursor() |
||||||
|
cur:SelectLine() |
||||||
|
local data = util.String(cur:GetSelection()) |
||||||
|
local file, line, character = data:match("(./[^:]+):([^:]+):([^:]+)") |
||||||
|
local doc, _ = file:gsub("^file://", "") |
||||||
|
buf, _ = buffer.NewBufferFromFile(doc) |
||||||
|
bp:AddTab() |
||||||
|
micro.CurPane():OpenBuffer(buf) |
||||||
|
buf:GetActiveCursor():GotoLoc(buffer.Loc(character * 1, line * 1)) |
||||||
|
micro.CurPane():Center() |
||||||
|
return false |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function preSave(bp) |
||||||
|
if config.GetGlobalOption("lsp.formatOnSave") then |
||||||
|
onRune(bp) |
||||||
|
formatAction(bp, function () |
||||||
|
bp:Save() |
||||||
|
end) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function handleInitialized(buf, filetype) |
||||||
|
if cmd[filetype] == nil then return; end |
||||||
|
micro.Log("Found running lsp server for ", filetype, "firing textDocument/didOpen...") |
||||||
|
local send = withSend(filetype) |
||||||
|
local uri = getUriFromBuf(buf) |
||||||
|
local content = util.String(buf:Bytes()):gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\r", "\\r"):gsub('"', '\\"'):gsub("\t", "\\t") |
||||||
|
send("textDocument/didOpen", fmt.Sprintf('{"textDocument": {"uri": "%s", "languageId": "%s", "version": 1, "text": "%s"}}', uri, filetype, content), true) |
||||||
|
end |
||||||
|
|
||||||
|
function onBufferOpen(buf) |
||||||
|
local filetype = buf:FileType() |
||||||
|
micro.Log("ONBUFFEROPEN", filetype) |
||||||
|
if filetype ~= "unknown" and rootUri == "" and not cmd[filetype] then return startServer(filetype, handleInitialized); end |
||||||
|
if cmd[filetype] then |
||||||
|
handleInitialized(buf, filetype) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function contains(list, x) |
||||||
|
for _, v in pairs(list) do |
||||||
|
if v == x then return true; end |
||||||
|
end |
||||||
|
return false |
||||||
|
end |
||||||
|
|
||||||
|
function string.starts(String, Start) |
||||||
|
return string.sub(String, 1, #Start) == Start |
||||||
|
end |
||||||
|
|
||||||
|
function string.ends(String, End) |
||||||
|
return string.sub(String, #String - (#End - 1), #String) == End |
||||||
|
end |
||||||
|
|
||||||
|
function string.random(CharSet, Length, prefix) |
||||||
|
|
||||||
|
local _CharSet = CharSet or '.' |
||||||
|
|
||||||
|
if _CharSet == '' then |
||||||
|
return '' |
||||||
|
else |
||||||
|
local Result = prefix or "" |
||||||
|
math.randomseed(os.time()) |
||||||
|
for Loop = 1,Length do |
||||||
|
local char = math.random(1, #CharSet) |
||||||
|
Result = Result .. CharSet:sub(char,char) |
||||||
|
end |
||||||
|
|
||||||
|
return Result |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function string.parse(text) |
||||||
|
if not text:find('"jsonrpc":') then return {}; end |
||||||
|
local start,fin = text:find("\n%s*\n") |
||||||
|
local cleanedText = text |
||||||
|
if fin ~= nil then |
||||||
|
cleanedText = text:sub(fin) |
||||||
|
end |
||||||
|
local status, res = pcall(json.parse, cleanedText) |
||||||
|
if status then |
||||||
|
return res |
||||||
|
end |
||||||
|
return false |
||||||
|
end |
||||||
|
|
||||||
|
function isIgnoredMessage(msg) |
||||||
|
-- Return true if msg matches one of the ignored starts of messages |
||||||
|
-- Useful for linters that show spurious, hard to disable warnings |
||||||
|
local ignoreList = mysplit(config.GetGlobalOption("lsp.ignoreMessages"), "|") |
||||||
|
for i, ignore in pairs(ignoreList) do |
||||||
|
if string.match(msg, ignore) then -- match from start of string |
||||||
|
micro.Log("Ignore message: '", msg, "', because it matched: '", ignore, "'.") |
||||||
|
return true -- ignore this message, dont show to user |
||||||
|
end |
||||||
|
end |
||||||
|
return false -- show this message to user |
||||||
|
end |
||||||
|
|
||||||
|
function onStdout(filetype) |
||||||
|
return function (text) |
||||||
|
if text:starts("Content-Length:") then |
||||||
|
message = text |
||||||
|
else |
||||||
|
message = message .. text |
||||||
|
end |
||||||
|
if not text:ends("}") then |
||||||
|
return |
||||||
|
end |
||||||
|
local data = message:parse() |
||||||
|
if data == false then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
if data.method == "workspace/configuration" then |
||||||
|
-- actually needs to respond with the same ID as the received JSON |
||||||
|
local message = fmt.Sprintf('{"jsonrpc": "2.0", "id": %.0f, "result": [{"enable": true}]}', data.id) |
||||||
|
shell.JobSend(cmd[filetype], fmt.Sprintf('Content-Length: %.0f\n\n%s', #message, message)) |
||||||
|
elseif data.method == "textDocument/publishDiagnostics" or data.method == "textDocument\\/publishDiagnostics" then |
||||||
|
-- react to server-published event |
||||||
|
local bp = micro.CurPane().Buf |
||||||
|
bp:ClearMessages("lsp") |
||||||
|
bp:AddMessage(buffer.NewMessage("lsp", "", buffer.Loc(0, 10000000), buffer.Loc(0, 10000000), buffer.MTInfo)) |
||||||
|
local uri = getUriFromBuf(bp) |
||||||
|
if data.params.uri == uri then |
||||||
|
for _, diagnostic in ipairs(data.params.diagnostics) do |
||||||
|
local type = buffer.MTInfo |
||||||
|
if diagnostic.severity == 1 then |
||||||
|
type = buffer.MTError |
||||||
|
elseif diagnostic.severity == 2 then |
||||||
|
type = buffer.MTWarning |
||||||
|
end |
||||||
|
local mstart = buffer.Loc(diagnostic.range.start.character, diagnostic.range.start.line) |
||||||
|
local mend = buffer.Loc(diagnostic.range["end"].character, diagnostic.range["end"].line) |
||||||
|
|
||||||
|
if not isIgnoredMessage(diagnostic.message) then |
||||||
|
msg = buffer.NewMessage("lsp", diagnostic.message, mstart, mend, type) |
||||||
|
bp:AddMessage(msg) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
elseif currentAction[filetype] and currentAction[filetype].method and not data.method and currentAction[filetype].response and data.jsonrpc then -- react to custom action event |
||||||
|
local bp = micro.CurPane() |
||||||
|
micro.Log("Received message for ", filetype, data) |
||||||
|
currentAction[filetype].response(bp, data) |
||||||
|
currentAction[filetype] = {} |
||||||
|
elseif data.method == "window/showMessage" or data.method == "window\\/showMessage" then |
||||||
|
if filetype == micro.CurPane().Buf:FileType() then |
||||||
|
micro.InfoBar():Message(data.params.message) |
||||||
|
else |
||||||
|
micro.Log(filetype .. " message " .. data.params.message) |
||||||
|
end |
||||||
|
elseif data.method == "window/logMessage" or data.method == "window\\/logMessage" then |
||||||
|
micro.Log(data.params.message) |
||||||
|
elseif message:starts("Content-Length:") then |
||||||
|
if message:find('"') and not message:find('"result":null') then |
||||||
|
micro.Log("Unhandled message 1", filetype, message) |
||||||
|
end |
||||||
|
else |
||||||
|
-- enable for debugging purposes |
||||||
|
micro.Log("Unhandled message 2", filetype, message) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function onStderr(text) |
||||||
|
micro.Log("ONSTDERR", text) |
||||||
|
--micro.InfoBar():Message(text) |
||||||
|
end |
||||||
|
|
||||||
|
function onExit(filetype) |
||||||
|
return function (str) |
||||||
|
currentAction[filetype] = nil |
||||||
|
cmd[filetype] = nil |
||||||
|
micro.Log("ONEXIT", filetype, str) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- the actual hover action request and response |
||||||
|
-- the hoverActionResponse is hooked up in |
||||||
|
function hoverAction(bp) |
||||||
|
local filetype = bp.Buf:FileType() |
||||||
|
if cmd[filetype] ~= nil then |
||||||
|
local send = withSend(filetype) |
||||||
|
local file = bp.Buf.AbsPath |
||||||
|
local line = bp.Buf:GetActiveCursor().Y |
||||||
|
local char = bp.Buf:GetActiveCursor().X |
||||||
|
currentAction[filetype] = { method = "textDocument/hover", response = hoverActionResponse } |
||||||
|
send(currentAction[filetype].method, fmt.Sprintf('{"textDocument": {"uri": "file://%s"}, "position": {"line": %.0f, "character": %.0f}}', file, line, char)) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function hoverActionResponse(buf, data) |
||||||
|
if data.result and data.result.contents ~= nil and data.result.contents ~= "" then |
||||||
|
if data.result.contents.value then |
||||||
|
micro.InfoBar():Message(data.result.contents.value) |
||||||
|
elseif #data.result.contents > 0 then |
||||||
|
micro.InfoBar():Message(data.result.contents[1].value) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- the definition action request and response |
||||||
|
function definitionAction(bp) |
||||||
|
local filetype = bp.Buf:FileType() |
||||||
|
if cmd[filetype] == nil then return; end |
||||||
|
|
||||||
|
local send = withSend(filetype) |
||||||
|
local file = bp.Buf.AbsPath |
||||||
|
local line = bp.Buf:GetActiveCursor().Y |
||||||
|
local char = bp.Buf:GetActiveCursor().X |
||||||
|
currentAction[filetype] = { method = "textDocument/definition", response = definitionActionResponse } |
||||||
|
send(currentAction[filetype].method, fmt.Sprintf('{"textDocument": {"uri": "file://%s"}, "position": {"line": %.0f, "character": %.0f}}', file, line, char)) |
||||||
|
end |
||||||
|
|
||||||
|
function definitionActionResponse(bp, data) |
||||||
|
local results = data.result or data.partialResult |
||||||
|
if results == nil then return; end |
||||||
|
local file = bp.Buf.AbsPath |
||||||
|
if results.uri ~= nil then |
||||||
|
-- single result |
||||||
|
results = { results } |
||||||
|
end |
||||||
|
if #results <= 0 then return; end |
||||||
|
local uri = (results[1].uri or results[1].targetUri) |
||||||
|
local doc = uri:gsub("^file://", "") |
||||||
|
local buf = bp.Buf |
||||||
|
if file ~= doc then |
||||||
|
-- it's from a different file, so open it as a new tab |
||||||
|
buf, _ = buffer.NewBufferFromFile(doc) |
||||||
|
bp:AddTab() |
||||||
|
micro.CurPane():OpenBuffer(buf) |
||||||
|
-- shorten the displayed name in status bar |
||||||
|
name = buf:GetName() |
||||||
|
local wd, _ = go_os.Getwd() |
||||||
|
if name:starts(wd) then |
||||||
|
buf:SetName("." .. name:sub(#wd + 1, #name + 1)) |
||||||
|
else |
||||||
|
if #name > 30 then |
||||||
|
buf:SetName("..." .. name:sub(-30, #name + 1)) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
local range = results[1].range or results[1].targetSelectionRange |
||||||
|
buf:GetActiveCursor():GotoLoc(buffer.Loc(range.start.character, range.start.line)) |
||||||
|
bp:Center() |
||||||
|
end |
||||||
|
|
||||||
|
function completionAction(bp) |
||||||
|
local filetype = bp.Buf:FileType() |
||||||
|
local send = withSend(filetype) |
||||||
|
local file = bp.Buf.AbsPath |
||||||
|
local line = bp.Buf:GetActiveCursor().Y |
||||||
|
local char = bp.Buf:GetActiveCursor().X |
||||||
|
|
||||||
|
if lastCompletion[1] == file and lastCompletion[2] == line and lastCompletion[3] == char then |
||||||
|
completionCursor = completionCursor + 1 |
||||||
|
else |
||||||
|
completionCursor = 0 |
||||||
|
if bp.Cursor:HasSelection() then |
||||||
|
-- we have a selection |
||||||
|
-- assume we want to indent the selection |
||||||
|
bp:IndentSelection() |
||||||
|
return |
||||||
|
end |
||||||
|
if char == 0 then |
||||||
|
-- we are at the very first character of a line |
||||||
|
-- assume we want to indent |
||||||
|
bp:IndentLine() |
||||||
|
return |
||||||
|
end |
||||||
|
local cur = bp.Buf:GetActiveCursor() |
||||||
|
cur:SelectLine() |
||||||
|
local lineContent = util.String(cur:GetSelection()) |
||||||
|
cur:ResetSelection() |
||||||
|
cur:GotoLoc(buffer.Loc(char, line)) |
||||||
|
local startOfLine = "" .. lineContent:sub(1, char) |
||||||
|
if startOfLine:match("^%s+$") then |
||||||
|
-- we are at the beginning of a line |
||||||
|
-- assume we want to indent the line |
||||||
|
bp:IndentLine() |
||||||
|
return |
||||||
|
end |
||||||
|
end |
||||||
|
if cmd[filetype] == nil then return; end |
||||||
|
lastCompletion = {file, line, char} |
||||||
|
currentAction[filetype] = { method = "textDocument/completion", response = completionActionResponse } |
||||||
|
send(currentAction[filetype].method, fmt.Sprintf('{"textDocument": {"uri": "file://%s"}, "position": {"line": %.0f, "character": %.0f}}', file, line, char)) |
||||||
|
end |
||||||
|
|
||||||
|
table.filter = function(t, filterIter) |
||||||
|
local out = {} |
||||||
|
|
||||||
|
for k, v in pairs(t) do |
||||||
|
if filterIter(v, k, t) then table.insert(out, v) end |
||||||
|
end |
||||||
|
|
||||||
|
return out |
||||||
|
end |
||||||
|
|
||||||
|
function findCommon(input, list) |
||||||
|
local commonLen = 0 |
||||||
|
local prefixList = {} |
||||||
|
local str = input.textEdit and input.textEdit.newText or input.label |
||||||
|
for i = 1,#str,1 do |
||||||
|
local prefix = str:sub(1, i) |
||||||
|
prefixList[prefix] = 0 |
||||||
|
for idx, entry in ipairs(list) do |
||||||
|
local currentEntry = entry.textEdit and entry.textEdit.newText or entry.label |
||||||
|
if currentEntry:starts(prefix) then |
||||||
|
prefixList[prefix] = prefixList[prefix] + 1 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
local longest = "" |
||||||
|
for idx, entry in pairs(prefixList) do |
||||||
|
if entry >= #list then |
||||||
|
if #longest < #idx then |
||||||
|
longest = idx |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
if #list == 1 then |
||||||
|
return list[1].textEdit and list[1].textEdit.newText or list[1].label |
||||||
|
end |
||||||
|
return longest |
||||||
|
end |
||||||
|
|
||||||
|
function completionActionResponse(bp, data) |
||||||
|
local results = data.result |
||||||
|
if results == nil then |
||||||
|
return |
||||||
|
end |
||||||
|
if results.items then |
||||||
|
results = results.items |
||||||
|
end |
||||||
|
|
||||||
|
local xy = buffer.Loc(bp.Cursor.X, bp.Cursor.Y) |
||||||
|
local start = xy |
||||||
|
if bp.Cursor:HasSelection() then |
||||||
|
bp.Cursor:DeleteSelection() |
||||||
|
end |
||||||
|
|
||||||
|
local found = false |
||||||
|
local prefix = "" |
||||||
|
local reversed = "" |
||||||
|
-- if we have no defined ranges in the result |
||||||
|
-- try to find out what our prefix is we want to filter against |
||||||
|
if not results[1] or not results[1].textEdit or not results[1].textEdit.range then |
||||||
|
if capabilities[bp.Buf:FileType()] and capabilities[bp.Buf:FileType()].completionProvider and capabilities[bp.Buf:FileType()].completionProvider.triggerCharacters then |
||||||
|
local cur = bp.Buf:GetActiveCursor() |
||||||
|
cur:SelectLine() |
||||||
|
local lineContent = util.String(cur:GetSelection()) |
||||||
|
reversed = string.reverse(lineContent:gsub("\r?\n$", ""):sub(1, xy.X)) |
||||||
|
local triggerChars = capabilities[bp.Buf:FileType()].completionProvider.triggerCharacters |
||||||
|
for i = 1,#reversed,1 do |
||||||
|
local char = reversed:sub(i,i) |
||||||
|
-- try to find a trigger character or any other non-word character |
||||||
|
if contains(triggerChars, char) or contains({" ", ":", "/", "-", "\t", ";"}, char) then |
||||||
|
found = true |
||||||
|
start = buffer.Loc(#reversed - (i - 1), bp.Cursor.Y) |
||||||
|
bp.Cursor:SetSelectionStart(start) |
||||||
|
bp.Cursor:SetSelectionEnd(xy) |
||||||
|
prefix = util.String(cur:GetSelection()) |
||||||
|
bp.Cursor:DeleteSelection() |
||||||
|
bp.Cursor:ResetSelection() |
||||||
|
break |
||||||
|
end |
||||||
|
end |
||||||
|
if not found then |
||||||
|
prefix = lineContent:gsub("\r?\n$", '') |
||||||
|
end |
||||||
|
end |
||||||
|
-- if we have found a prefix |
||||||
|
if prefix ~= "" then |
||||||
|
-- filter it down to what is suggested by the prefix |
||||||
|
results = table.filter(results, function (entry) |
||||||
|
return entry.label:starts(prefix) |
||||||
|
end) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
table.sort(results, function (left, right) |
||||||
|
return (left.sortText or left.label) < (right.sortText or right.label) |
||||||
|
end) |
||||||
|
|
||||||
|
entry = results[(completionCursor % #results) + 1] |
||||||
|
-- if no matching results are found |
||||||
|
if entry == nil then |
||||||
|
-- reposition cursor and stop |
||||||
|
bp.Cursor:GotoLoc(xy) |
||||||
|
return |
||||||
|
end |
||||||
|
local commonStart = '' |
||||||
|
local toInsert = entry.textEdit and entry.textEdit.newText or entry.label |
||||||
|
local isTabCompletion = config.GetGlobalOption("lsp.tabcompletion") |
||||||
|
if isTabCompletion and not entry.textEdit then |
||||||
|
commonStart = findCommon(entry, results) |
||||||
|
bp.Buf:Insert(start, commonStart) |
||||||
|
if prefix ~= commonStart then |
||||||
|
return |
||||||
|
end |
||||||
|
start = buffer.Loc(start.X + #prefix, start.Y) |
||||||
|
else |
||||||
|
prefix = '' |
||||||
|
end |
||||||
|
|
||||||
|
if entry.textEdit and entry.textEdit.range then |
||||||
|
start = buffer.Loc(entry.textEdit.range.start.character, entry.textEdit.range.start.line) |
||||||
|
bp.Cursor:SetSelectionStart(start) |
||||||
|
bp.Cursor:SetSelectionEnd(xy) |
||||||
|
bp.Cursor:DeleteSelection() |
||||||
|
bp.Cursor:ResetSelection() |
||||||
|
elseif capabilities[bp.Buf:FileType()] and capabilities[bp.Buf:FileType()].completionProvider and capabilities[bp.Buf:FileType()].completionProvider.triggerCharacters then |
||||||
|
if not found then |
||||||
|
-- we found nothing - so assume we need the beginning of the line |
||||||
|
if reversed:starts(" ") or reversed:starts("\t") then |
||||||
|
-- if we end with some indentation, keep it |
||||||
|
start = buffer.Loc(#reversed, bp.Cursor.Y) |
||||||
|
else |
||||||
|
start = buffer.Loc(0, bp.Cursor.Y) |
||||||
|
end |
||||||
|
bp.Cursor:SetSelectionStart(start) |
||||||
|
bp.Cursor:SetSelectionEnd(xy) |
||||||
|
bp.Cursor:DeleteSelection() |
||||||
|
bp.Cursor:ResetSelection() |
||||||
|
end |
||||||
|
end |
||||||
|
local inserting = "" .. toInsert:gsub(prefix, "") |
||||||
|
bp.Buf:Insert(start, inserting) |
||||||
|
|
||||||
|
if #results > 1 then |
||||||
|
if entry.textEdit then |
||||||
|
bp.Cursor:GotoLoc(start) |
||||||
|
bp.Cursor:SetSelectionStart(start) |
||||||
|
else |
||||||
|
-- if we had to calculate everything outselves |
||||||
|
-- go back to the original location |
||||||
|
bp.Cursor:GotoLoc(xy) |
||||||
|
bp.Cursor:SetSelectionStart(xy) |
||||||
|
end |
||||||
|
bp.Cursor:SetSelectionEnd(buffer.Loc(start.X + #toInsert, start.Y)) |
||||||
|
else |
||||||
|
bp.Cursor:GotoLoc(buffer.Loc(start.X + #inserting, start.Y)) |
||||||
|
end |
||||||
|
|
||||||
|
local startLoc = buffer.Loc(0, 0) |
||||||
|
local endLoc = buffer.Loc(0, 0) |
||||||
|
local msg = '' |
||||||
|
local insertion = '' |
||||||
|
if entry.detail or entry.documentation then |
||||||
|
insertion = fmt.Sprintf("%s", entry.detail or entry.documentation or '') |
||||||
|
for idx, result in ipairs(results) do |
||||||
|
if #msg > 0 then |
||||||
|
msg = msg .. "\n" |
||||||
|
end |
||||||
|
local insertion = fmt.Sprintf("%s %s", result.detail or '', result.documentation or '') |
||||||
|
if idx == (completionCursor % #results) + 1 then |
||||||
|
local msglines = mysplit(msg, "\n") |
||||||
|
startLoc = buffer.Loc(0, #msglines) |
||||||
|
endLoc = buffer.Loc(#insertion - 1, #msglines) |
||||||
|
end |
||||||
|
msg = msg .. insertion |
||||||
|
end |
||||||
|
else |
||||||
|
insertion = entry.label |
||||||
|
for idx, result in ipairs(results) do |
||||||
|
if #msg > 0 then |
||||||
|
local msglines = mysplit(msg, "\n") |
||||||
|
local lastLine = msglines[#msglines] |
||||||
|
local len = #result.label + 4 |
||||||
|
if #lastLine + len >= bp:GetView().Width then |
||||||
|
msg = msg .. "\n " |
||||||
|
else |
||||||
|
msg = msg .. ' ' |
||||||
|
end |
||||||
|
else |
||||||
|
msg = " " |
||||||
|
end |
||||||
|
if idx == (completionCursor % #results) + 1 then |
||||||
|
local msglines = mysplit(msg, "\n") |
||||||
|
local prefixLen = 0 |
||||||
|
if #msglines > 0 then |
||||||
|
prefixLen = #msglines[#msglines] |
||||||
|
else |
||||||
|
prefixLen = #msg |
||||||
|
end |
||||||
|
startLoc = buffer.Loc(prefixLen or 0, #msglines - 1) |
||||||
|
endLoc = buffer.Loc(prefixLen + #result.label, #msglines - 1) |
||||||
|
end |
||||||
|
msg = msg .. result.label |
||||||
|
end |
||||||
|
end |
||||||
|
if config.GetGlobalOption("lsp.autocompleteDetails") then |
||||||
|
if not splitBP then |
||||||
|
local tmpName = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"):random(32) |
||||||
|
local logBuf = buffer.NewBuffer(msg, tmpName) |
||||||
|
splitBP = bp:HSplitBuf(logBuf) |
||||||
|
bp:NextSplit() |
||||||
|
else |
||||||
|
splitBP:SelectAll() |
||||||
|
splitBP.Cursor:DeleteSelection() |
||||||
|
splitBP.Cursor:ResetSelection() |
||||||
|
splitBP.Buf:insert(buffer.Loc(1, 1), msg) |
||||||
|
end |
||||||
|
splitBP.Cursor:ResetSelection() |
||||||
|
splitBP.Cursor:SetSelectionStart(startLoc) |
||||||
|
splitBP.Cursor:SetSelectionEnd(endLoc) |
||||||
|
else |
||||||
|
if entry.detail or entry.documentation then |
||||||
|
micro.InfoBar():Message(insertion) |
||||||
|
else |
||||||
|
local cleaned = " " .. msg:gsub("%s+", " ") |
||||||
|
local replaced, _ = cleaned:gsub(".*%s" .. insertion .. "%s?", " [" .. insertion .. "] ") |
||||||
|
micro.InfoBar():Message(replaced) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function formatAction(bp, callback) |
||||||
|
local filetype = bp.Buf:FileType() |
||||||
|
if cmd[filetype] == nil then return; end |
||||||
|
local send = withSend(filetype) |
||||||
|
local file = bp.Buf.AbsPath |
||||||
|
|
||||||
|
currentAction[filetype] = { method = "textDocument/formatting", response = formatActionResponse(callback) } |
||||||
|
send(currentAction[filetype].method, fmt.Sprintf('{"textDocument": {"uri": "file://%s"}, "options": {"tabSize": 4, "insertSpaces": true}}', file)) |
||||||
|
end |
||||||
|
|
||||||
|
function formatActionResponse(callback) |
||||||
|
return function (bp, data) |
||||||
|
if data.result == nil then return; end |
||||||
|
local edits = data.result |
||||||
|
-- make sure we apply the changes from back to front |
||||||
|
-- this allows for changes to not need position updates |
||||||
|
table.sort(edits, function (left, right) |
||||||
|
-- go by lines first |
||||||
|
return left.range['end'].line > right.range['end'].line or |
||||||
|
-- if lines match, go by end character |
||||||
|
left.range['end'].line == right.range['end'].line and left.range['end'].character > right.range['end'].character or |
||||||
|
-- if they match too, go by start character |
||||||
|
left.range['end'].line == right.range['end'].line and left.range['end'].character == right.range['end'].character and left.range.start.line == left.range['end'].line and left.range.start.character > right.range.start.character |
||||||
|
end) |
||||||
|
|
||||||
|
-- save original cursor position |
||||||
|
local xy = buffer.Loc(bp.Cursor.X, bp.Cursor.Y) |
||||||
|
for _idx, edit in ipairs(edits) do |
||||||
|
rangeStart = buffer.Loc(edit.range.start.character, edit.range.start.line) |
||||||
|
rangeEnd = buffer.Loc(edit.range['end'].character, edit.range['end'].line) |
||||||
|
-- apply each change |
||||||
|
bp.Cursor:GotoLoc(rangeStart) |
||||||
|
bp.Cursor:SetSelectionStart(rangeStart) |
||||||
|
bp.Cursor:SetSelectionEnd(rangeEnd) |
||||||
|
bp.Cursor:DeleteSelection() |
||||||
|
bp.Cursor:ResetSelection() |
||||||
|
|
||||||
|
if edit.newText ~= "" then |
||||||
|
bp.Buf:insert(rangeStart, edit.newText) |
||||||
|
end |
||||||
|
end |
||||||
|
-- put the cursor back where it was |
||||||
|
bp.Cursor:GotoLoc(xy) |
||||||
|
-- if any changes were applied |
||||||
|
if #edits > 0 then |
||||||
|
-- tell the server about the changed document |
||||||
|
onRune(bp) |
||||||
|
end |
||||||
|
|
||||||
|
if callback ~= nil then |
||||||
|
callback(bp) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- the references action request and response |
||||||
|
function referencesAction(bp) |
||||||
|
local filetype = bp.Buf:FileType() |
||||||
|
if cmd[filetype] == nil then return; end |
||||||
|
|
||||||
|
local send = withSend(filetype) |
||||||
|
local file = bp.Buf.AbsPath |
||||||
|
local line = bp.Buf:GetActiveCursor().Y |
||||||
|
local char = bp.Buf:GetActiveCursor().X |
||||||
|
currentAction[filetype] = { method = "textDocument/references", response = referencesActionResponse } |
||||||
|
send(currentAction[filetype].method, fmt.Sprintf('{"textDocument": {"uri": "file://%s"}, "position": {"line": %.0f, "character": %.0f}, "context": {"includeDeclaration":true}}', file, line, char)) |
||||||
|
end |
||||||
|
|
||||||
|
function referencesActionResponse(bp, data) |
||||||
|
if data.result == nil then return; end |
||||||
|
local results = data.result or data.partialResult |
||||||
|
if results == nil or #results <= 0 then return; end |
||||||
|
|
||||||
|
local file = bp.Buf.AbsPath |
||||||
|
|
||||||
|
local msg = '' |
||||||
|
for _idx, ref in ipairs(results) do |
||||||
|
if msg ~= '' then msg = msg .. '\n'; end |
||||||
|
local doc = (ref.uri or ref.targetUri) |
||||||
|
msg = msg .. "." .. doc:sub(#rootUri + 1, #doc) .. ":" .. ref.range.start.line .. ":" .. ref.range.start.character |
||||||
|
end |
||||||
|
|
||||||
|
local logBuf = buffer.NewBuffer(msg, "References found") |
||||||
|
local splitBP = bp:HSplitBuf(logBuf) |
||||||
|
end |
||||||
|
|
||||||
|
-- |
||||||
|
-- @TODO implement additional functions here... |
||||||
|
-- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- |
||||||
|
-- JSON |
||||||
|
-- |
||||||
|
-- Internal functions. |
||||||
|
|
||||||
|
local function kind_of(obj) |
||||||
|
if type(obj) ~= 'table' then return type(obj) end |
||||||
|
local i = 1 |
||||||
|
for _ in pairs(obj) do |
||||||
|
if obj[i] ~= nil then i = i + 1 else return 'table' end |
||||||
|
end |
||||||
|
if i == 1 then return 'table' else return 'array' end |
||||||
|
end |
||||||
|
|
||||||
|
local function escape_str(s) |
||||||
|
local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} |
||||||
|
local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} |
||||||
|
for i, c in ipairs(in_char) do |
||||||
|
s = s:gsub(c, '\\' .. out_char[i]) |
||||||
|
end |
||||||
|
return s |
||||||
|
end |
||||||
|
|
||||||
|
-- Returns pos, did_find; there are two cases: |
||||||
|
-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true. |
||||||
|
-- 2. Delimiter not found: pos = pos after leading space; did_find = false. |
||||||
|
-- This throws an error if err_if_missing is true and the delim is not found. |
||||||
|
local function skip_delim(str, pos, delim, err_if_missing) |
||||||
|
pos = pos + #str:match('^%s*', pos) |
||||||
|
if str:sub(pos, pos) ~= delim then |
||||||
|
if err_if_missing then |
||||||
|
error('Expected ' .. delim .. ' near position ' .. pos) |
||||||
|
end |
||||||
|
return pos, false |
||||||
|
end |
||||||
|
return pos + 1, true |
||||||
|
end |
||||||
|
|
||||||
|
-- Expects the given pos to be the first character after the opening quote. |
||||||
|
-- Returns val, pos; the returned pos is after the closing quote character. |
||||||
|
local function parse_str_val(str, pos, val) |
||||||
|
val = val or '' |
||||||
|
local early_end_error = 'End of input found while parsing string.' |
||||||
|
if pos > #str then error(early_end_error) end |
||||||
|
local c = str:sub(pos, pos) |
||||||
|
if c == '"' then return val, pos + 1 end |
||||||
|
if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end |
||||||
|
-- We must have a \ character. |
||||||
|
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} |
||||||
|
local nextc = str:sub(pos + 1, pos + 1) |
||||||
|
if not nextc then error(early_end_error) end |
||||||
|
return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc)) |
||||||
|
end |
||||||
|
|
||||||
|
-- Returns val, pos; the returned pos is after the number's final character. |
||||||
|
local function parse_num_val(str, pos) |
||||||
|
local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) |
||||||
|
local val = tonumber(num_str) |
||||||
|
if not val then error('Error parsing number at position ' .. pos .. '.') end |
||||||
|
return val, pos + #num_str |
||||||
|
end |
||||||
|
|
||||||
|
json.null = {} -- This is a one-off table to represent the null value. |
||||||
|
|
||||||
|
function json.parse(str, pos, end_delim) |
||||||
|
pos = pos or 1 |
||||||
|
if pos > #str then error('Reached unexpected end of input.' .. str) end |
||||||
|
local pos = pos + #str:match('^%s*', pos) -- Skip whitespace. |
||||||
|
local first = str:sub(pos, pos) |
||||||
|
if first == '{' then -- Parse an object. |
||||||
|
local obj, key, delim_found = {}, true, true |
||||||
|
pos = pos + 1 |
||||||
|
while true do |
||||||
|
key, pos = json.parse(str, pos, '}') |
||||||
|
if key == nil then return obj, pos end |
||||||
|
if not delim_found then error('Comma missing between object items.') end |
||||||
|
pos = skip_delim(str, pos, ':', true) -- true -> error if missing. |
||||||
|
obj[key], pos = json.parse(str, pos) |
||||||
|
pos, delim_found = skip_delim(str, pos, ',') |
||||||
|
end |
||||||
|
elseif first == '[' then -- Parse an array. |
||||||
|
local arr, val, delim_found = {}, true, true |
||||||
|
pos = pos + 1 |
||||||
|
while true do |
||||||
|
val, pos = json.parse(str, pos, ']') |
||||||
|
if val == nil then return arr, pos end |
||||||
|
if not delim_found then error('Comma missing between array items.') end |
||||||
|
arr[#arr + 1] = val |
||||||
|
pos, delim_found = skip_delim(str, pos, ',') |
||||||
|
end |
||||||
|
elseif first == '"' then -- Parse a string. |
||||||
|
return parse_str_val(str, pos + 1) |
||||||
|
elseif first == '-' or first:match('%d') then -- Parse a number. |
||||||
|
return parse_num_val(str, pos) |
||||||
|
elseif first == end_delim then -- End of an object or array. |
||||||
|
return nil, pos + 1 |
||||||
|
else -- Parse true, false, or null. |
||||||
|
local literals = {['true'] = true, ['false'] = false, ['null'] = json.null} |
||||||
|
for lit_str, lit_val in pairs(literals) do |
||||||
|
local lit_end = pos + #lit_str - 1 |
||||||
|
if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end |
||||||
|
end |
||||||
|
local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10) |
||||||
|
error('Invalid json syntax starting at ' .. pos_info_str .. ': ' .. str) |
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,78 @@ |
|||||||
|
[{ |
||||||
|
"Name": "lsp", |
||||||
|
"Description": "Generic LSP Client for Micro", |
||||||
|
"Website": "https://github.com/AndCake/micro-plugin-lsp", |
||||||
|
"Tags": ["lsp"], |
||||||
|
"Versions": [ |
||||||
|
{ |
||||||
|
"Version": "0.4.1", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.4.1.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.10" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.4.2", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.4.2.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.4.3", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.4.3.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.5.0", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.5.0.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.5.1", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.5.1.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.5.2", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.5.2.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.5.3", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.5.3.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.6.0", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.6.0.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.6.1", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.6.1.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"Version": "0.6.2", |
||||||
|
"Url": "https://github.com/AndCake/micro-plugin-lsp/archive/v0.6.2.zip", |
||||||
|
"Require": { |
||||||
|
"micro": ">=2.0.8" |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}] |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
{ |
||||||
|
"colorscheme": "vol-scheme", |
||||||
|
"lsp.ignoreMessages": "LS message1 to ignore|LS message 2 to ignore|...", |
||||||
|
"lsp.ignoreTriggerCharacters": "completion,signature", |
||||||
|
"lsp.server": "python=pyls,go=gopls,typescript=deno lsp,rust=rust-analyzer" |
||||||
|
} |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
filetype: 65xx asm |
||||||
|
|
||||||
|
detect: |
||||||
|
filename: "\\.(A|a|)$" |
||||||
|
|
||||||
|
rules: |
||||||
|
# This file is made for ACME assembly |
||||||
|
|
||||||
|
## Instructions |
||||||
|
# 65xx |
||||||
|
- statement: "\\b(?i)(adc|and|asl|bcc|bcs|beq|bmi|bne|bpl|bvc|bvs|bit|brk|clc)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(cld|cli|clv|cmp|cpx|cpy|dec|dex|dey|eor|inc|inx|iny|jmp)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(jsr|lda|ldx|ldy|lsr|nop|ora|pha|php|pla|plp|rol|ror|rti)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(rts|sbc|sec|sed|sei|sta|stx|sty|tax|tay|tsx|txa|txs|tya)(?-i)\\b" |
||||||
|
## Constants |
||||||
|
# Number - it doesn't work |
||||||
|
- constant.number: "\\b[0-9 a-f A-F]+\\b" |
||||||
|
- constant.number: "(\\#|\\$|%)" |
||||||
|
|
||||||
|
## Other |
||||||
|
- identifier: "[a-z A-Z 0-9 _]+:" |
||||||
|
- preproc: "\\*\\=" |
||||||
|
- preproc: "![a-z A-Z]+(.*, [0-9 a-z A-Z]+|)" |
||||||
|
|
||||||
|
- constant.string: |
||||||
|
start: "\"" |
||||||
|
end: "\"" |
||||||
|
skip: "\\\\." |
||||||
|
rules: |
||||||
|
- constant.specialChar: "\\\\." |
||||||
|
|
||||||
|
- constant.string: |
||||||
|
start: "'" |
||||||
|
end: "'" |
||||||
|
skip: "\\\\." |
||||||
|
rules: |
||||||
|
- constant.specialChar: "\\\\." |
||||||
|
|
||||||
|
- comment: |
||||||
|
start: ";" |
||||||
|
end: "$" |
||||||
|
rules: |
||||||
|
- todo: "(TODO|XXX|FIXME):?" |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,122 @@ |
|||||||
|
filetype: asm |
||||||
|
|
||||||
|
detect: |
||||||
|
filename: "\\.(S|s|asm)$" |
||||||
|
|
||||||
|
rules: |
||||||
|
# This file is made for NASM assembly |
||||||
|
|
||||||
|
## Instructions |
||||||
|
# x86 |
||||||
|
- statement: "\\b(?i)(mov|aaa|aad|aam|aas|adc|add|and|call|cbw|clc|cld|cli|cmc|cmp|cmpsb|cmpsw|cwd|daa|das|dec|div|esc|hlt|idiv|imul|in|inc|int|into|iret|ja|jae|jb|jbe|jc|je|jg|jge|jl|jle|jna|jnae|jnb|jnbe|jnc|jne|jng|jnge|jnl|jnle|jno|jnp|jns|jnz|jo|jp|jpe|jpo|js|jz|jcxz|jmp|lahf|lds|lea|les|lock|lodsb|lodsw|loop|loope|loopne|loopnz|loopz|movsb|movsw|mul|neg|nop|or|pop|popf|push|pushf|rcl|rcr|rep|repe|repne|repnz|repz|ret|retn|retf|rol|ror|sahf|sal|sar|sbb|scasb|scasw|shl|shr|stc|std|sti|stosb|stosw|sub|test|wait|xchg|xlat|xor)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(bound|enter|ins|leave|outs|popa|pusha)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(arpl|clts|lar|lgdt|lidt|lldt|lmsw|loadall|lsl|ltr|sgdt|sidt|sldt|smsw|str|verr|verw)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(bsf|bsr|bt|btc|btr|bts|cdq|cmpsd|cwde|insd|iret|iretd|iretf|jecxz|lfs|lgs|lss|lodsd|loopw|loopew|loopnew|loopnzw|loopzw|loopd|looped|loopned|loopnzd|loopzd|cr|tr|dr|movsd|movsx|movzx|outsd|popad|popfd|pushad|pushfd|scasd|seta|setae|setb|setbe|setc|sete|setg|setge|setl|setle|setna|setnae|setnb|setnbe|setnc|setne|setng|setnge|setnl|setnle|setno|setnp|setns|setnz|seto|setp|setpe|setpo|sets|setz|shdl|shrd|stosd)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(bswap|cmpxcgh|invd|invlpg|wbinvd|xadd)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(cpuid|cmpxchg8b|rdmsr|rdtsc|wrmsr|rsm)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(rdpmc)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(syscall|sysret)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(cmova|cmovae|cmovb|cmovbe|cmovc|cmove|cmovg|cmovge|cmovl|cmovle|cmovna|cmovnae|cmovnb|cmovnbe|cmovnc|cmovne|cmovng|cmovnge|cmovnle|cmovno|cmovpn|cmovns|cmovnz|cmovo|cmovp|cmovpe|cmovpo|cmovs|cmovz|sysenter|sysexit|ud2)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(maskmovq|movntps|movntq|prefetch0|prefetch1|prefetch2|prefetchnta|sfence)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(clflush|lfence|maskmovdqu|mfence|movntdq|movnti|movntpd|pause)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(monitor|mwait)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(cdqe|cqo|cmpsq|cmpxchg16b|iretq|jrcxz|lodsq|movsdx|popfq|pushfq|rdtscp|scasq|stosq|swapgs)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(clgi|invlpga|skinit|stgi|vmload|vmmcall|vmrun|vmsave)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(vmptrdl|vmptrst|vmclear|vmread|vmwrite|vmcall|vmlaunch|vmresume|vmxoff|vmxon)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(lzcnt|popcnt)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(bextr|blcfill|blci|blcic|blcmask|blcs|blsfill|blsic|t1mskc|tzmsk)(?-i)\\b" |
||||||
|
|
||||||
|
# x87 |
||||||
|
- statement: "\\b(?i)(f2xm1|fabs|fadd|faddp|fbld|fbstp|fchs|fclex|fcom|fcomp|fcompp|fdecstp|fdisi|fdiv|fvidp|fdivr|fdivrp|feni|ffree|fiadd|ficom|ficomp|fidiv|fidivr|fild|fimul|fincstp|finit|fist|fistp|fisub|fisubr|fld|fld1|fldcw|fldenv|fldenvw|fldl2e|fldl2t|fldlg2|fldln2|fldpi|fldz|fmul|fmulp|fnclex|fndisi|fneni|fninit|fnop|fnsave|fnsavenew|fnstcw|fnstenv|fnstenvw|fnstsw|fpatan|fprem|fptan|frndint|frstor|frstorw|fsave|fsavew|fscale|fsqrt|fst|fstcw|fstenv|fstenvw|fstp|fstpsw|fsub|fsubp|fsubr|fsubrp|ftst|fwait|fxam|fxch|fxtract|fyl2x|fyl2xp1)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(fsetpm)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(fcos|fldenvd|fsaved|fstenvd|fprem1|frstord|fsin|fsincos|fstenvd|fucom|fucomp|fucompp)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(fcmovb|fcmovbe|fcmove|fcmove|fcmovnb|fcmovnbe|fcmovne|fcmovnu|fcmovu)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(fcomi|fcomip|fucomi|fucomip)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(fxrstor|fxsave)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(fisttp)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(ffreep)(?-i)\\b" |
||||||
|
|
||||||
|
# SIMD |
||||||
|
- statement: "\\b(?i)(emms|movd|movq|packssdw|packsswb|packuswb|paddb|paddw|paddd|paddsb|paddsw|paddusb|paddusw|pand|pandn|por|pxor|pcmpeqb|pcmpeqw|pcmpeqd|pcmpgtb|pcmpgtw|pcmpgtd|pmaddwd|pmulhw|pmullw|psllw|pslld|psllq|psrad|psraw|psrlw|psrld|psrlq|psubb|psubw|psubd|psubsb|psubsw|psubusb|punpckhbw|punpckhwd|punpckhdq|punkcklbw|punpckldq|punpcklwd)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(paveb|paddsiw|pmagw|pdistib|psubsiw|pmwzb|pmulhrw|pmvnzb|pmvlzb|pmvgezb|pmulhriw|pmachriw)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(femms|pavgusb|pf2id|pfacc|pfadd|pfcmpeq|pfcmpge|pfcmpgt|pfmax|pfmin|pfmul|pfrcp|pfrcpit1|pfrcpit2|pfrsqit1|pfrsqrt|pfsub|pfsubr|pi2fd|pmulhrw|prefetch|prefetchw)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(pf2iw|pfnacc|pfpnacc|pi2fw|pswapd)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(pfrsqrtv|pfrcpv)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(addps|addss|cmpps|cmpss|comiss|cvtpi2ps|cvtps2pi|cvtsi2ss|cvtss2si|cvttps2pi|cvttss2si|divps|divss|ldmxcsr|maxps|maxss|minps|minss|movaps|movhlps|movhps|movlhps|movlps|movmskps|movntps|movss|movups|mulps|mulss|rcpps|rcpss|rsqrtps|rsqrtss|shufps|sqrtps|sqrtss|stmxcsr|subps|subss|ucomiss|unpckhps|unpcklps)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(andnps|andps|orps|pavgb|pavgw|pextrw|pinsrw|pmaxsw|pmaxub|pminsw|pminub|pmovmskb|pmulhuw|psadbw|pshufw|xorps)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(movups|movss|movlps|movhlps|movlps|unpcklps|unpckhps|movhps|movlhps|prefetchnta|prefetch0|prefetch1|prefetch2|nop|movaps|cvtpi2ps|cvtsi2ss|cvtps2pi|cvttss2si|cvtps2pi|cvtss2si|ucomiss|comiss|sqrtps|sqrtss|rsqrtps|rsqrtss|rcpps|andps|orps|xorps|addps|addss|mulps|mulss|subps|subss|minps|minss|divps|divss|maxps|maxss|pshufw|ldmxcsr|stmxcsr|sfence|cmpps|cmpss|pinsrw|pextrw|shufps|pmovmskb|pminub|pmaxub|pavgb|pavgw|pmulhuw|movntq|pminsw|pmaxsw|psadbw|maskmovq)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(addpd|addsd|addnpd|cmppd|cmpsd)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(addpd|addsd|andnpd|andpd|cmppd|cmpsd|comisd|cvtdq2pd|cvtdq2ps|cvtpd2dq|cvtpd2pi|cvtpd2ps|cvtpi2pd|cvtps2dq|cvtps2pd|cvtsd2si|cvtsd2ss|cvtsi2sd|cvtss2sd|cvttpd2dq|cvttpd2pi|cvttps2dq|cvttsd2si|divpd|divsd|maxpd|maxsd|minpd|minsd|movapd|movhpd|movlpd|movmskpd|movsd|movupd|mulpd|mulsd|orpd|shufpd|sqrtpd|sqrtsd|subpd|subsd|ucomisd|unpckhpd|unpcklpd|xorpd)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(movdq2q|movdqa|movdqu|movq2dq|paddq|psubq|pmuludq|pshufhw|pshuflw|pshufd|pslldq|psrldq|punpckhqdq|punpcklqdq)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(addsubpd|addsubps|haddpd|haddps|hsubpd|hsubps|movddup|movshdup|movsldu)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(lddqu)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(psignw|psignd|psignb|pshufb|pmulhrsw|pmaddubsw|phsubw|phsubsw|phsubd|phaddw|phaddsw|phaddd|palignr|pabsw|pabsd|pabsb)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(dpps|dppd|blendps|blendpd|blendvps|blendvpd|roundps|roundss|roundpd|roundsd|insertps|extractps)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(mpsadbw|phminposuw|pmulld|pmuldq|pblendvb|pblendw|pminsb|pmaxsb|pminuw|pmaxuw|pminud|pmaxud|pminsd|pmaxsd|pinsrb|pinsrd/pinsrq|pextrb|pextrw|pextrd/pextrq|pmovsxbw|pmovzxbw|pmovsxbd|pmovzxbd|pmovsxbq|pmovzxbq|pmovsxwd|pmovzxwd|pmovsxwq|pmovzxwq|pmovsxdq|pmovzxdq|ptest|pcmpeqq|packusdw|movntdqa)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(extrq|insertq|movntsd|movntss)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(crc32|pcmpestri|pcmpestrm|pcmpistri|pcmpistrm|pcmpgtq)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(vfmaddpd|vfmaddps|vfmaddsd|vfmaddss|vfmaddsubpd|vfmaddsubps|vfmsubaddpd|vfmsubaddps|vfmsubpd|vfmsubps|vfmsubsd|vfmsubss|vfnmaddpd|vfnmaddps|vfnmaddsd|vfnmaddss|vfnmsubps|vfnmsubsd|vfnmsubss)(?-i)\\b" |
||||||
|
|
||||||
|
# Crypto |
||||||
|
- statement: "\\b(?i)(aesenc|aesenclast|aesdec|aesdeclast|aeskeygenassist|aesimc)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(sha1rnds4|sha1nexte|sha1msg1|sha1msg2|sha256rnds2|sha256msg1|sha256msg2)(?-i)\\b" |
||||||
|
|
||||||
|
# Undocumented |
||||||
|
- statement: "\\b(?i)(aam|aad|salc|icebp|loadall|loadalld|ud1)(?-i)\\b" |
||||||
|
|
||||||
|
## Registers |
||||||
|
- identifier: "\\b(?i)(al|ah|bl|bh|cl|ch|dl|dh|bpl|sil|r8b|r9b|r10b|r11b|dil|spl|r12b|r13b|r14b|r15)(?-i)\\b" |
||||||
|
- identifier: "\\b(?i)(cw|sw|tw|fp_ds|fp_opc|fp_ip|fp_dp|fp_cs|cs|ss|ds|es|fs|gs|gdtr|idtr|tr|ldtr|ax|bx|cx|dx|bp|si|r8w|r9w|r10w|r11w|di|sp|r12w|r13w|r14w|r15w|ip)(?-i)\\b" |
||||||
|
- identifier: "\\b(?i)(fp_dp|fp_ip|eax|ebx|ecx|edx|ebp|esi|r8d|r9d|r10d|r11d|edi|esp|r12d|r13d|r14d|r15d|eip|eflags|mxcsr)(?-i)\\b" |
||||||
|
- identifier: "\\b(?i)(mm0|mm1|mm2|mm3|mm4|mm5|mm6|mm7|rax|rbx|rcx|rdx|rbp|rsi|r8|r9|r10|r11|rdi|rsp|r12|r13|r14|r15|rip|rflags|cr0|cr1|cr2|cr3|cr4|cr5|cr6|cr7|cr8|cr9|cr10|cr11|cr12|cr13|cr14|cr15|msw|dr0|dr1|dr2|dr3|r4|dr5|dr6|dr7|dr8|dr9|dr10|dr11|dr12|dr13|dr14|dr15)(?-i)\\b" |
||||||
|
- identifier: "\\b(?i)(st0|st1|st2|st3|st4|st5|st6|st7)(?-i)\\b" |
||||||
|
- identifier: "\\b(?i)(xmm0|xmm1|xmm2|xmm3|xmm4|xmm5|xmm6|xmm7|xmm8|xmm9|xmm10|xmm11|xmm12|xmm13|xmm14|xmm15)(?-i)\\b" |
||||||
|
- identifier: "\\b(?i)(ymm0|ymm1|ymm2|ymm3|ymm4|ymm5|ymm6|ymm7|ymm8|ymm9|ymm10|ymm11|ymm12|ymm13|ymm14|ymm15)(?-i)\\b" |
||||||
|
- identifier: "\\b(?i)(zmm0|zmm1|zmm2|zmm3|zmm4|zmm5|zmm6|zmm7|zmm8|zmm9|zmm10|zmm11|zmm12|zmm13|zmm14|zmm15|zmm16|zmm17|zmm18|zmm19|zmm20|zmm21|zmm22|zmm23|zmm24|zmm25|zmm26|zmm27|zmm28|zmm29|zmm30|zmm31)(?-i)\\b" |
||||||
|
|
||||||
|
# 65xx |
||||||
|
- statement: "\\b(?i)(adc|and|asl|bcc|bcs|beq|bmi|bne|bpl|bvc|bvs|bit|brk|clc)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(cld|cli|clv|cmp|cpx|cpy|dec|dex|dey|eor|inc|inx|iny|jmp)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(jsr|lda|ldx|ldy|lsr|nop|ora|pha|php|pla|plp|rol|ror|rti)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(rts|sbc|sec|sed|sei|sta|stx|sty|tax|tay|tsx|txa|txs|tya)(?-i)\\b" |
||||||
|
|
||||||
|
## Preprocessor (NASM) |
||||||
|
- preproc: "%+(\\+|\\?|\\?\\?|)[a-z A-Z 0-9]+" |
||||||
|
- preproc: "%\\[[. a-z A-Z 0-9]*\\]" |
||||||
|
|
||||||
|
## Constants |
||||||
|
# Number - it works |
||||||
|
# x86 |
||||||
|
- constant.number: "\\b(|h|A|0x)+[0-9]+(|h|A)+\\b" |
||||||
|
- constant.number: "\\b0x[0-9 a-f A-F]+\\b" |
||||||
|
# Number - it doesn't work |
||||||
|
# 65xx |
||||||
|
#- constant.number: "\\b(|\\#)(\\#|$$|\\%)[0-9 a-f A-F]+\\b" |
||||||
|
- constant.number: "(\\#|\\$|%)" |
||||||
|
|
||||||
|
## Other |
||||||
|
- statement: "\\b(?i)(extern|global|section|segment|_start|\\.text|\\.data|\\.bss)(?-i)\\b" |
||||||
|
- statement: "\\b(?i)(db|dw|dd|dq|dt|ddq|do)(?-i)\\b" |
||||||
|
- identifier: "[a-z A-Z 0-9 _]+:" |
||||||
|
|
||||||
|
- constant.string: |
||||||
|
start: "\"" |
||||||
|
end: "\"" |
||||||
|
skip: "\\\\." |
||||||
|
rules: |
||||||
|
- constant.specialChar: "\\\\." |
||||||
|
|
||||||
|
- constant.string: |
||||||
|
start: "'" |
||||||
|
end: "'" |
||||||
|
skip: "\\\\." |
||||||
|
rules: |
||||||
|
- constant.specialChar: "\\\\." |
||||||
|
|
||||||
|
- comment: |
||||||
|
start: ";" |
||||||
|
end: "$" |
||||||
|
rules: |
||||||
|
- todo: "(TODO|XXX|FIXME):?" |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,73 @@ |
|||||||
|
filetype: rust |
||||||
|
|
||||||
|
detect: |
||||||
|
filename: "\\.rs$" |
||||||
|
|
||||||
|
rules: |
||||||
|
# function definition |
||||||
|
- identifier: "fn [a-z0-9_]+" |
||||||
|
# Modules |
||||||
|
- symbol: "mod [a-z0-9_]+" |
||||||
|
- symbol: "[a-z0-9_]+::" |
||||||
|
# Reserved words |
||||||
|
- statement: "\\b(abstract|alignof|as|become|box|break|const|continue|crate|do|dyn|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|offsetof|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\\b" |
||||||
|
# macros |
||||||
|
- special: "[a-z_]+!" |
||||||
|
# Constants |
||||||
|
- constant: "\\b[A-Z][A-Z_0-9]+\\b" |
||||||
|
# Numbers |
||||||
|
- constant.number: "\\b[0-9]+\\b" |
||||||
|
# Booleans |
||||||
|
- constant: "\\b(true|false)\\b" |
||||||
|
# Traits/Enums/Structs/Types/etc. |
||||||
|
- type: "\\b[A-Z]+[a-zA-Z_0-9]*[a-z]+[a-zA-Z_0-9]*\\b" |
||||||
|
# Builtin types that start with lowercase. |
||||||
|
- type: "\\b(bool|str|isize|usize|((i|u)(8|16|32|64|128))|f32|f64)\\b" |
||||||
|
# Symbols |
||||||
|
- symbol.brackets: "[(){}\\[\\]]" |
||||||
|
- symbol: "(\\*|//|/|%|\\+|-|\\^|>|>=|<|<=|!=|=|[\\.]{2,3}|#)" |
||||||
|
- symbol.operator: "[-+/*=<>!%&|^\\?]" |
||||||
|
# Attributes |
||||||
|
- preproc: "#!?\\[.*\\]" |
||||||
|
|
||||||
|
|
||||||
|
- constant.string: |
||||||
|
start: "\"" |
||||||
|
end: "\"" |
||||||
|
skip: '\\.' |
||||||
|
rules: |
||||||
|
- constant.specialChar: '\\.' |
||||||
|
|
||||||
|
- constant.string: |
||||||
|
start: "r#+\"" |
||||||
|
end: "\"#+" |
||||||
|
rules: [] |
||||||
|
|
||||||
|
# Character literals |
||||||
|
# NOTE: This is an ugly hack to work around the fact that rust uses |
||||||
|
# single quotes both for character literals and lifetimes. |
||||||
|
# Match all character literals. |
||||||
|
- constant.string: "'(\\\\.|.)'" |
||||||
|
# Match the '"' literal which would otherwise match |
||||||
|
# as a double quoted string and destroy the highlighting. |
||||||
|
- constant.string: |
||||||
|
start: "'\"" |
||||||
|
end: "'" |
||||||
|
rules: [] |
||||||
|
|
||||||
|
- comment: |
||||||
|
start: "//" |
||||||
|
end: "$" |
||||||
|
rules: |
||||||
|
- todo: "(TODO|XXX|FIXME):?" |
||||||
|
|
||||||
|
- comment: |
||||||
|
start: "/\\*" |
||||||
|
end: "\\*/" |
||||||
|
rules: |
||||||
|
- todo: "(TODO|XXX|FIXME):?" |
||||||
|
|
||||||
|
- special: |
||||||
|
start: "#!\\[" |
||||||
|
end: "\\]" |
||||||
|
rules: [] |
||||||
Loading…
Reference in new issue