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