mirror of
https://github.com/Cian-H/dotfiles.git
synced 2026-05-07 06:11:42 +01:00
Changed . token to _dot
This change allows the dotfiles to work with chezmoi (e.g: on windows) and improves grepability with neovim/telescope
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Ignore .md files, because 2 spaces at end-of-line has meaning
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,109 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.4.0] - 2018-10-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Issues with Lua's `io.popen` on some systems by using Micro's built-in `RunShellCommand` instead, [thanks to @scottbilas](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/38)
|
||||
|
||||
### Added
|
||||
|
||||
- Adds the option `filemanager-openonstart` to allow auto-opening the file tree when Micro is started (default OFF)
|
||||
|
||||
### Changed
|
||||
|
||||
- Update README's option's documentation
|
||||
|
||||
## [3.3.1] - 2018-10-03
|
||||
|
||||
### Changed
|
||||
|
||||
- Performance improvement by removing unnecessary refresh of the opened file, [thanks to @jackwilsdon](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/37)
|
||||
|
||||
## [3.3.0] - 2018-09-13
|
||||
|
||||
### Added
|
||||
|
||||
- The ability to sort folders above files, [thanks to @cbrown1](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/33)
|
||||
|
||||
### Fixed
|
||||
|
||||
- The displayed filenames are now correctly only showing their "basename" on Windows
|
||||
|
||||
## [3.2.0] - 2018-02-15
|
||||
|
||||
### Added
|
||||
|
||||
- The ability to go to parent directory with left arrow (when not minimizing). Thanks @avently
|
||||
- The ability to jump to the `..` as a "parent directory". Thanks @avently
|
||||
|
||||
## [3.1.2] - 2018-02-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- The minimum Micro version, which was incorrectly set to v1.4.0. Ref [issue #28](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/28)
|
||||
|
||||
## [3.1.1] - 2018-02-04
|
||||
|
||||
### Fixed
|
||||
|
||||
Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes.
|
||||
|
||||
- The syntax parser not loading correctly (mostly block comments) on opened files. **Requires Micro >= v1.4.0**
|
||||
- An errant tab being inserted into the newly opened file.
|
||||
|
||||
## [3.1.0] - 2018-01-30
|
||||
|
||||
### Added
|
||||
|
||||
- The ability to hide dotfiles using the `filemanager-showdotfiles` option.
|
||||
- The ability to hide files ignored in your VCS (aka `.gitignore`'d) using the `filemanager-showignored` option. Only works with Git at the moment.
|
||||
- This `CHANGELOG.md`
|
||||
|
||||
### Fixed
|
||||
|
||||
- A bug with the `rm` command that caused weird, undefined behaviour to contents within the same dir as the file/dir deleted.
|
||||
- Issue [#24](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/24)
|
||||
|
||||
## [3.0.0] - 2018-01-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- Issues [#13](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/13), [#14](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/14), [#15](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/15), [#19](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/19), [#20](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/20)
|
||||
- The broken syntax highlighting
|
||||
|
||||
### Added
|
||||
|
||||
- Directory expansion/compression below itself for viewing more akin to a file tree.
|
||||
- The `rm` command, which deletes the file/directory under the cursor.
|
||||
- The `touch` command, which creates a file with the passed filename.
|
||||
- The `mkdir` command, which creates a directory with the passed filename.
|
||||
- An API, of sorts, for the user to rebind their keys to if they dislike the defaults.
|
||||
- An [editorconfig](http://editorconfig.org/) file.
|
||||
|
||||
### Changed
|
||||
|
||||
- The view that it spawns in to read-only, which requires Micro version >= 1.3.5
|
||||
- The functionality of some keybindings (when in the view) so they work safetly, or at all, with the plugin.
|
||||
- From the `enter` key to `tab` for opening/going into files/dirs (a side-effect of using the read-only setting)
|
||||
|
||||
### Removed
|
||||
|
||||
- The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit.
|
||||
- The pointless `.gitignore` file.
|
||||
|
||||
[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.4.0...HEAD
|
||||
[3.4.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.1...v3.4.0
|
||||
[3.3.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.0...v3.3.1
|
||||
[3.3.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.2.0...v3.3.0
|
||||
[3.2.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.2...v3.2.0
|
||||
[3.1.2]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.1...v3.1.2
|
||||
[3.1.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.0...v3.1.1
|
||||
[3.1.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.0.0...v3.1.0
|
||||
[3.0.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v2.1.1...v3.0.0
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Nicolai Søborg
|
||||
|
||||
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,56 @@
|
||||
# Filemanager Plugin
|
||||
|
||||
A simple plugin that allows for easy navigation of a file tree.
|
||||
|
||||

|
||||
|
||||
**Installation:** run `plugin install filemanager` and restart Micro.
|
||||
|
||||
## Basics
|
||||
|
||||
The top line always has the current directory's path to show you where you are.\
|
||||
The `..` near the top is used to move back a directory, from your current position.
|
||||
|
||||
All directories have a `/` added to the end of it, and are syntax-highlighted as a `special` character.\
|
||||
If the directory is expanded, there will be a `+` to the left of it. If it is collapsed there will be a `-` instead.
|
||||
|
||||
**NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree.
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Purpose | Default |
|
||||
| :--------------------------- | :----------------------------------------------------------- | :------ |
|
||||
| `filemanager-showdotfiles` | Show dotfiles (hidden if false) | `true` |
|
||||
| `filemanager-showignored` | Show gitignore'd files (hidden if false) | `true` |
|
||||
| `filemanager-compressparent` | Collapse the parent dir when left is pressed on a child file | `true` |
|
||||
| `filemanager-foldersfirst` | Sorts folders above any files | `true` |
|
||||
| `filemanager-openonstart` | Automatically open the file tree when starting Micro | `false` |
|
||||
|
||||
### Commands and Keybindings
|
||||
|
||||
The keybindings below are the equivalent to Micro's defaults, and not actually set by the plugin. If you've changed any of those keybindings, then that key is used instead.
|
||||
|
||||
If you want to [keybind](https://github.com/zyedidia/micro/blob/master/runtime/help/keybindings.md#rebinding-keys) any of the operations/commands, bind to the labeled API in the table below.
|
||||
|
||||
| Command | Keybinding(s) | What it does | API for `bindings.json` |
|
||||
| :------- | :------------------------- | :------------------------------------------------------------------------------------------ | :------------------------------------ |
|
||||
| `tree` | - | Open/close the tree | `filemanager.toggle_tree` |
|
||||
| - | <kbd>Tab</kbd> & MouseLeft | Open a file, or go into the directory. Goes back a dir if on `..` | `filemanager.try_open_at_cursor` |
|
||||
| - | <kbd>→</kbd> | Expand directory in tree listing | `filemanager.uncompress_at_cursor` |
|
||||
| - | <kbd>←</kbd> | Collapse directory listing | `filemanager.compress_at_cursor` |
|
||||
| - | <kbd>Shift ⬆</kbd> | Go to the target's parent directory | `filemanager.goto_parent_dir` |
|
||||
| - | <kbd>Alt Shift {</kbd> | Jump to the previous directory in the view | `filemanager.goto_next_dir` |
|
||||
| - | <kbd>Alt Shift }</kbd> | Jump to the next directory in the view | `filemanager.goto_prev_dir` |
|
||||
| `rm` | - | Prompt to delete the target file/directory your cursor is on | `filemanager.prompt_delete_at_cursor` |
|
||||
| `rename` | - | Rename the file/directory your cursor is on, using the passed name | `filemanager.rename_at_cursor` |
|
||||
| `touch` | - | Make a new file under/into the file/directory your cursor is on, using the passed name | `filemanager.new_file` |
|
||||
| `mkdir` | - | Make a new directory under/into the file/directory your cursor is on, using the passed name | `filemanager.new_dir` |
|
||||
|
||||
#### Notes
|
||||
|
||||
- `rename`, `touch`, and `mkdir` require a name to be passed when calling.\
|
||||
Example: `rename newnamehere`, `touch filenamehere`, `mkdir dirnamehere`.\
|
||||
If the passed name already exists in the current dir, it will cancel instead of overwriting (for safety).
|
||||
|
||||
- The <kbd>Ctrl w</kbd> keybinding is to switch which buffer your cursor is on.\
|
||||
This isn't specific to the plugin, it's just part of Micro, but many people seem to not know this.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
[
|
||||
{
|
||||
"Name": "filemanager",
|
||||
"Description": "File manager for Micro",
|
||||
"Tags": ["filetree", "filemanager", "file", "manager"],
|
||||
"Website": "",
|
||||
"Versions": [
|
||||
{
|
||||
"Version": "2.1.1",
|
||||
"Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.1.zip",
|
||||
"Require": {
|
||||
"micro": ">=1.3.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Version": "3.1.0",
|
||||
"Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.0.zip",
|
||||
"Require": {
|
||||
"micro": ">=1.3.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Version": "3.4.0",
|
||||
"Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.4.0.zip",
|
||||
"Require": {
|
||||
"micro": ">=1.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Version": "3.5.0",
|
||||
"Url": "https://github.com/micro-editor/updated-plugins/releases/download/v1.0.0/filemanager-3.5.0.zip",
|
||||
"Require": {
|
||||
"micro": ">=2.0.0-1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Version": "3.5.1",
|
||||
"Url": "https://github.com/micro-editor/updated-plugins/releases/download/v1.0.0/filemanager-3.5.1.zip",
|
||||
"Require": {
|
||||
"micro": ">=2.0.0-1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
filetype: filemanager
|
||||
|
||||
detect:
|
||||
filename: "^filemanager$"
|
||||
|
||||
rules:
|
||||
# The "..", the separator line thing, and directories
|
||||
# Optionally, add this below to highlight the ascii line: "^─*$"
|
||||
- special: "^\\.\\.$|\\-\\s.*|\\+\\s.*"
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2020 Łukasz "MasFlam" Drukała
|
||||
|
||||
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,24 @@
|
||||

|
||||
# jlabbrev
|
||||
The package `jlabbrev` adds all the tab-completions from
|
||||
the Julia prompt that are LaTeX symbol names.
|
||||
There are currently **`2496`** abbreviations on the list.
|
||||
Refer to the documentation page
|
||||
[Unicode Input](https://docs.julialang.org/en/v1/manual/unicode-input)
|
||||
of Julia if you want to get the full list. If all goes well,
|
||||
all non-emoji completions from there should be supported by the plugin.
|
||||
|
||||
# Features
|
||||
Writing `\<abbrev>` and hitting `<tab>` will do one of the following:
|
||||
* If `<abbrev>` is one of the supported abbreviations,
|
||||
the whole sequence gets replaced with the corresponding symbol(s).
|
||||
* If `<abbrev>` if a prefix of an abbreviation, autocompletion suggestion takes place
|
||||
* Otherwise, the tab will be inserted
|
||||
|
||||
# Installation
|
||||
Until the plugin gets onto
|
||||
[the official plugin channel](https://github.com/micro-editor/plugin-channel),
|
||||
the you can install it by `cd`ing into your `~/.config/micro/plug`
|
||||
and `git clone`ing this repository.
|
||||
If jlabbrev ever gets onto the official plugin channel, the way
|
||||
to install it will be running `micro -plugin install jlabbrev`.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
[{
|
||||
"Name": "jlabbrev",
|
||||
"Description": "Julia backslash abbreviations in micro",
|
||||
"Website": "https://github.com/MasFlam/jlabbrev",
|
||||
"Tags": ["julia", "util"],
|
||||
"Versions": [{
|
||||
"Version": "1.0.0",
|
||||
"Url": "https://github.com/MasFlam/jlabbrev/archive/v1.0.0.zip",
|
||||
"Require": {
|
||||
"micro": ">=2.0"
|
||||
}
|
||||
}, {
|
||||
"Version": "1.0.1",
|
||||
"Url": "https://github.com/MasFlam/jlabbrev/archive/v1.0.1.zip",
|
||||
"Require": {
|
||||
"micro": ">=2.0"
|
||||
}
|
||||
}]
|
||||
}]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
Reference in New Issue
Block a user