Lsp completion logic
This commit is contained in:
27
README.md
27
README.md
@@ -6,12 +6,13 @@ A TUI IDE.
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
- [ ] Do auto complete box rendering functions.
|
||||||
- [ ] Finish autocomplete box.
|
- [ ] Finish autocomplete box.
|
||||||
- [ ] Add status bar & RUNNER mode
|
- [ ] Add status bar & RUNNER mode
|
||||||
- [ ] Get code context from tree-sitter
|
- [ ] Get code context from tree-sitter
|
||||||
- [ ] Maybe hide boxes in !`normal` mode
|
- [ ] Maybe hide boxes in !`normal` mode
|
||||||
- [ ] expand color regex to match css colors if in css file
|
- [ ] expand color regex to match css colors if in css file
|
||||||
- [ ] Fix indentation logic
|
- [ ] Fix indentation logic - tree-sitter indents too if possible
|
||||||
- Make it work by one getting the identation used in a file by first checking if it has any line with 2 or more spaces then the least one is set to be the indent or if it is tabs then tabs but if there are none then use a table of file type to its indentation or use 2 spaces as default. store this info as `1 = tab` and `2 or more = those many spaces`.
|
- Make it work by one getting the identation used in a file by first checking if it has any line with 2 or more spaces then the least one is set to be the indent or if it is tabs then tabs but if there are none then use a table of file type to its indentation or use 2 spaces as default. store this info as `1 = tab` and `2 or more = those many spaces`.
|
||||||
- Use this when indenting and unindenting. And also when getting the identation of a line.
|
- Use this when indenting and unindenting. And also when getting the identation of a line.
|
||||||
- Also indent when going immediately to newline should follow indent of previous line regardless of file default.
|
- Also indent when going immediately to newline should follow indent of previous line regardless of file default.
|
||||||
@@ -19,25 +20,11 @@ A TUI IDE.
|
|||||||
- [ ] For `"insertTextFormat": 2` in `clangd` and similar use only the last word in the signature when replacing
|
- [ ] For `"insertTextFormat": 2` in `clangd` and similar use only the last word in the signature when replacing
|
||||||
- [ ] Keep a list of words in the current buffer. (for auto completion) (maybe?)
|
- [ ] Keep a list of words in the current buffer. (for auto completion) (maybe?)
|
||||||
- [ ] Add ecma to js and make tsx
|
- [ ] Add ecma to js and make tsx
|
||||||
- [ ] Add support for LSP & autocomplete / snippets.
|
- [ ] Switch to like `RapidJSON` ro something more basic but faster than rn
|
||||||
- First research
|
- also decrease use of `std::string` so much in ui stuff and lsp and warnings etc.
|
||||||
- `textDocument/documentHighlight` - for highlighting stuff (probably tree-sitter is enough)
|
- [ ] Add lsp jumping support for goto definition, hover etc.
|
||||||
- `textDocument/selectionRange` //
|
- [ ] Add lsp rename support for renaming a symbol. (also see what tree-sitter can do here)
|
||||||
- `textDocument/completion` - Obviously
|
- [ ] Check into more lsp stuff i can add.
|
||||||
- `textDocument/onTypeFormatting` - seems promising for auto formatting (indentation etc)
|
|
||||||
- `textDocument/formatting` & `textDocument/rangeFormatting`
|
|
||||||
- `textDocument/semanticTokens/*` (probably tree-sitter is enough)
|
|
||||||
- `textDocument/linkedEditingRange` - probably useful
|
|
||||||
- `textDocument/foldingRange` - i will never use this for folding but it might be useful for other things.
|
|
||||||
- `textDocument/rename` & `textDocument/prepareRename` - probably useful
|
|
||||||
- And a lot more (just go through each for `clangd` and then expand to say `solargraph`).
|
|
||||||
- Make a universal plug for lsp. So focus more on making a general purpose solid communication interface. Instead of something specific.
|
|
||||||
- With a 4ish pass system. (more like each returned value from the lsp is used in 4 ways)
|
|
||||||
1. One for stuff like jump to x position. or rename symbol x to y. (stuff that explicitly requires user request to do something)
|
|
||||||
- Maybe even hover goes here
|
|
||||||
2. One for stuff that only affects highlighting and styles . like symbol highlighting etc.
|
|
||||||
3. One for Warnings/errors and inlay hints etc. (stuff that adds virtual text to the editor)
|
|
||||||
4. One for fromatting and stuff like that. (stuff that edits the buffer text)
|
|
||||||
- [ ] Add codeium/copilot support for auto-completion (uses the VAI virtual text) as a test phase.
|
- [ ] Add codeium/copilot support for auto-completion (uses the VAI virtual text) as a test phase.
|
||||||
- [ ] Add a whitespace highlighter (nerd font). for spaces and tabs at start/end of line. not as virtual but instead at render time.
|
- [ ] Add a whitespace highlighter (nerd font). for spaces and tabs at start/end of line. not as virtual but instead at render time.
|
||||||
- [ ] Once renderer is proven to work well (i.e. redo this commit) merge `experimental` branch into `main`. commit `43f443e` on `experimental`.
|
- [ ] Once renderer is proven to work well (i.e. redo this commit) merge `experimental` branch into `main`. commit `43f443e` on `experimental`.
|
||||||
|
|||||||
44
include/editor/completions.h
Normal file
44
include/editor/completions.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef EDITOR_COMPLETIONS_H
|
||||||
|
#define EDITOR_COMPLETIONS_H
|
||||||
|
|
||||||
|
#include "editor/decl.h"
|
||||||
|
#include "pch.h"
|
||||||
|
#include "ui/completionbox.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
|
||||||
|
struct CompletionItem {
|
||||||
|
std::string label; // Shown in the autocomplete box
|
||||||
|
uint8_t kind; // Function, variable, class, etc.
|
||||||
|
std::optional<std::string> detail; // Shown greyed in autocomplete box
|
||||||
|
std::optional<std::string> documentation; // Hover box (can be lazy-loaded)
|
||||||
|
bool is_markup = false;
|
||||||
|
bool deprecated = false; // Shown with strikethrough, may push down in list
|
||||||
|
std::string sort; // Used for sorting
|
||||||
|
std::string filter; // Used for filtering (default: label)
|
||||||
|
bool snippet = false;
|
||||||
|
std::vector<TextEdit> edits;
|
||||||
|
json original;
|
||||||
|
std::vector<char> end_chars; // Ends completion session if typed
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompletionSession {
|
||||||
|
std::shared_mutex mtx;
|
||||||
|
|
||||||
|
bool active = false;
|
||||||
|
Coord hook; // set to start of word
|
||||||
|
std::optional<std::string> prefix; // text between hook and cursor
|
||||||
|
uint8_t select = 0; // index of selected item (defualts to preselcted one
|
||||||
|
// when data requested)
|
||||||
|
std::vector<CompletionItem> items;
|
||||||
|
std::vector<uint8_t> visible;
|
||||||
|
bool complete = true; // If false, client may request more items on filter
|
||||||
|
// (but doesnt try filtering on its own)
|
||||||
|
std::optional<char> trigger_char; // Character that triggered completion sent
|
||||||
|
// to lsp for isIncomplete resolving
|
||||||
|
uint8_t trigger = 0; // Type of trigger (1: manual, 2: trigger char, 3: auto)
|
||||||
|
CompletionBox box;
|
||||||
|
|
||||||
|
CompletionSession() : box(this) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -3,6 +3,14 @@
|
|||||||
|
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
|
||||||
|
struct TextEdit {
|
||||||
|
// NOTE: start.col is in utf16 index and not clusters or utf8
|
||||||
|
Coord start;
|
||||||
|
// NOTE: end.col is in utf16 index and not clusters or utf8
|
||||||
|
Coord end;
|
||||||
|
std::string text;
|
||||||
|
};
|
||||||
|
|
||||||
struct Fold {
|
struct Fold {
|
||||||
uint32_t start;
|
uint32_t start;
|
||||||
uint32_t end;
|
uint32_t end;
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#ifndef EDITOR_H
|
#ifndef EDITOR_H
|
||||||
#define EDITOR_H
|
#define EDITOR_H
|
||||||
|
|
||||||
|
#include "editor/completions.h"
|
||||||
#include "editor/spans.h"
|
#include "editor/spans.h"
|
||||||
#include "io/knot.h"
|
#include "io/knot.h"
|
||||||
#include "io/sysio.h"
|
#include "io/sysio.h"
|
||||||
#include "ts/decl.h"
|
#include "ts/decl.h"
|
||||||
|
#include "ui/completionbox.h"
|
||||||
#include "ui/diagnostics.h"
|
#include "ui/diagnostics.h"
|
||||||
#include "ui/hover.h"
|
#include "ui/hover.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
@@ -49,6 +51,7 @@ struct Editor {
|
|||||||
bool diagnostics_active;
|
bool diagnostics_active;
|
||||||
DiagnosticBox diagnostics;
|
DiagnosticBox diagnostics;
|
||||||
int lsp_version = 1;
|
int lsp_version = 1;
|
||||||
|
CompletionSession completion;
|
||||||
};
|
};
|
||||||
|
|
||||||
Editor *new_editor(const char *filename_arg, Coord position, Coord size);
|
Editor *new_editor(const char *filename_arg, Coord position, Coord size);
|
||||||
@@ -73,6 +76,8 @@ void ensure_scroll(Editor *editor);
|
|||||||
void handle_editor_event(Editor *editor, KeyEvent event);
|
void handle_editor_event(Editor *editor, KeyEvent event);
|
||||||
void edit_erase(Editor *editor, Coord pos, int64_t len);
|
void edit_erase(Editor *editor, Coord pos, int64_t len);
|
||||||
void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len);
|
void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len);
|
||||||
|
void edit_replace(Editor *editor, Coord start, Coord end, const char *text,
|
||||||
|
uint32_t len);
|
||||||
Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y);
|
Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y);
|
||||||
char *get_selection(Editor *editor, uint32_t *out_len, Coord *out_start);
|
char *get_selection(Editor *editor, uint32_t *out_len, Coord *out_start);
|
||||||
void editor_worker(Editor *editor);
|
void editor_worker(Editor *editor);
|
||||||
@@ -90,6 +95,13 @@ uint32_t leading_indent(const char *line, uint32_t len);
|
|||||||
uint32_t get_indent(Editor *editor, Coord cursor);
|
uint32_t get_indent(Editor *editor, Coord cursor);
|
||||||
bool closing_after_cursor(const char *line, uint32_t len, uint32_t col);
|
bool closing_after_cursor(const char *line, uint32_t len, uint32_t col);
|
||||||
void editor_lsp_handle(Editor *editor, json msg);
|
void editor_lsp_handle(Editor *editor, json msg);
|
||||||
|
void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits);
|
||||||
|
void completion_resolve_doc(Editor *editor);
|
||||||
|
void complete_accept(Editor *editor);
|
||||||
|
void complete_next(Editor *editor);
|
||||||
|
void complete_prev(Editor *editor);
|
||||||
|
void complete_select(Editor *editor, uint8_t index);
|
||||||
|
void handle_completion(Editor *editor, KeyEvent event);
|
||||||
|
|
||||||
inline void apply_hook_insertion(Editor *editor, uint32_t line, uint32_t rows) {
|
inline void apply_hook_insertion(Editor *editor, uint32_t line, uint32_t rows) {
|
||||||
for (auto &hook : editor->hooks)
|
for (auto &hook : editor->hooks)
|
||||||
|
|||||||
@@ -62,19 +62,33 @@ struct ScreenCell {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct KeyEvent {
|
struct KeyEvent {
|
||||||
|
/* KEY_CHAR, KEY_SPECIAL, KEY_MOUSE, KEY_PASTE, KEY_NONE */
|
||||||
uint8_t key_type;
|
uint8_t key_type;
|
||||||
|
|
||||||
|
/* the character / string if key_type == KEY_CHAR or KEY_PASTE */
|
||||||
char *c;
|
char *c;
|
||||||
|
/* length of c */
|
||||||
uint32_t len;
|
uint32_t len;
|
||||||
|
|
||||||
|
/* KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_DELETE if key_type ==
|
||||||
|
* KEY_SPECIAL */
|
||||||
uint8_t special_key;
|
uint8_t special_key;
|
||||||
|
/* ALT, CNTRL, CNTRL_ALT, SHIFT if key_type == KEY_SPECIAL */
|
||||||
uint8_t special_modifier;
|
uint8_t special_modifier;
|
||||||
|
|
||||||
|
/* column of mouse click */
|
||||||
uint8_t mouse_x;
|
uint8_t mouse_x;
|
||||||
|
/* row of mouse click */
|
||||||
uint8_t mouse_y;
|
uint8_t mouse_y;
|
||||||
|
/* LEFT_BTN, MIDDLE_BTN, RIGHT_BTN, SCROLL_BTN, NONE_BTN if key_type ==
|
||||||
|
* KEY_MOUSE */
|
||||||
uint8_t mouse_button;
|
uint8_t mouse_button;
|
||||||
|
/* PRESS, RELEASE, DRAG, SCROLL if key_type == KEY_MOUSE */
|
||||||
uint8_t mouse_state;
|
uint8_t mouse_state;
|
||||||
|
/* SCROLL_UP, SCROLL_DOWN, SCROLL_LEFT, SCROLL_RIGHT if key_type ==
|
||||||
|
* KEY_MOUSE and mouse_state == SCROLL */
|
||||||
uint8_t mouse_direction;
|
uint8_t mouse_direction;
|
||||||
|
/* ALT, CNTRL, CNTRL_ALT, SHIFT if key_type == KEY_MOUSE */
|
||||||
uint8_t mouse_modifier;
|
uint8_t mouse_modifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +111,8 @@ void update(uint32_t row, uint32_t col, std::string utf8, uint32_t fg,
|
|||||||
uint32_t bg, uint8_t flags, uint32_t ul_color);
|
uint32_t bg, uint8_t flags, uint32_t ul_color);
|
||||||
void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg,
|
void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg,
|
||||||
uint32_t bg, uint8_t flags, uint32_t ul_color);
|
uint32_t bg, uint8_t flags, uint32_t ul_color);
|
||||||
void set_cursor(int row, int col, int type, bool show_cursor_param);
|
void set_cursor(uint32_t row, uint32_t col, uint32_t type,
|
||||||
|
bool show_cursor_param);
|
||||||
void render();
|
void render();
|
||||||
Coord get_size();
|
Coord get_size();
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ struct LSPInstance {
|
|||||||
bool incremental_sync = false;
|
bool incremental_sync = false;
|
||||||
bool allow_hover = false;
|
bool allow_hover = false;
|
||||||
bool allow_completion = false;
|
bool allow_completion = false;
|
||||||
std::string trigger_chars;
|
bool allow_resolve = false;
|
||||||
|
std::vector<char> trigger_chars;
|
||||||
|
std::vector<char> end_chars;
|
||||||
uint32_t last_id = 0;
|
uint32_t last_id = 0;
|
||||||
Queue<json> inbox;
|
Queue<json> inbox;
|
||||||
Queue<json> outbox;
|
Queue<json> outbox;
|
||||||
@@ -53,12 +55,15 @@ static json client_capabilities = {
|
|||||||
{"hover", {{"contentFormat", {"markdown", "plaintext"}}}},
|
{"hover", {{"contentFormat", {"markdown", "plaintext"}}}},
|
||||||
{"completion",
|
{"completion",
|
||||||
{{"completionItem",
|
{{"completionItem",
|
||||||
{{"snippetSupport", true},
|
{{"commitCharactersSupport", true},
|
||||||
|
{"dynamicRegistration", false},
|
||||||
|
{"snippetSupport", true},
|
||||||
{"documentationFormat", {"markdown", "plaintext"}},
|
{"documentationFormat", {"markdown", "plaintext"}},
|
||||||
{"resolveSupport", {{"properties", {"documentation", "detail"}}}},
|
{"resolveSupport", {{"properties", {"documentation"}}}},
|
||||||
{"insertReplaceSupport", true},
|
{"insertReplaceSupport", true},
|
||||||
{"labelDetailsSupport", true},
|
{"labelDetailsSupport", true},
|
||||||
{"insertTextModeSupport", {{"valueSet", {1}}}}}},
|
{"insertTextModeSupport", {{"valueSet", {1}}}},
|
||||||
|
{"deprecatedSupport", true}}},
|
||||||
{"completionItemKind", {{"valueSet", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}}},
|
{"completionItemKind", {{"valueSet", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}}},
|
||||||
{"contextSupport", true},
|
{"contextSupport", true},
|
||||||
{"insertTextMode", 1}}}}}};
|
{"insertTextMode", 1}}}}}};
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||||
#define PCRE_WORKSPACE_SIZE 512
|
#define PCRE_WORKSPACE_SIZE 512
|
||||||
|
|
||||||
|
#include <magic.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <pcre2.h>
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "libgrapheme/grapheme.h"
|
#include "libgrapheme/grapheme.h"
|
||||||
#include "unicode_width/unicode_width.h"
|
#include "unicode_width/unicode_width.h"
|
||||||
@@ -25,12 +28,9 @@ extern "C" {
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <magic.h>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <pcre2.h>
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
#define TS_DEF(name) extern "C" const TSLanguage *LANG(name)()
|
#define TS_DEF(name) extern "C" const TSLanguage *LANG(name)()
|
||||||
|
|
||||||
struct Language {
|
struct Language {
|
||||||
std::string name;
|
std::string name = "unknown";
|
||||||
const TSLanguage *(*fn)();
|
const TSLanguage *(*fn)() = nullptr;
|
||||||
uint8_t lsp_id;
|
uint8_t lsp_id = 0;
|
||||||
uint32_t color = 0xFFFFFF;
|
uint32_t color = 0xFFFFFF;
|
||||||
const char *symbol = " ";
|
const char *symbol = " ";
|
||||||
};
|
};
|
||||||
|
|||||||
20
include/ui/completionbox.h
Normal file
20
include/ui/completionbox.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef UI_COMPLETIONBOX_H
|
||||||
|
#define UI_COMPLETIONBOX_H
|
||||||
|
|
||||||
|
#include "io/sysio.h"
|
||||||
|
#include "pch.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
|
||||||
|
struct CompletionBox {
|
||||||
|
struct CompletionSession *session;
|
||||||
|
bool hidden = true;
|
||||||
|
std::vector<ScreenCell> cells;
|
||||||
|
Coord size;
|
||||||
|
Coord position;
|
||||||
|
|
||||||
|
CompletionBox(CompletionSession *s) : session(s) {}
|
||||||
|
void render_update();
|
||||||
|
void render(Coord pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -9,8 +9,7 @@
|
|||||||
struct DiagnosticBox {
|
struct DiagnosticBox {
|
||||||
std::vector<VWarn> warnings;
|
std::vector<VWarn> warnings;
|
||||||
std::vector<ScreenCell> cells;
|
std::vector<ScreenCell> cells;
|
||||||
uint32_t box_width;
|
Coord size;
|
||||||
uint32_t box_height;
|
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
void render_first();
|
void render_first();
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ struct HoverBox {
|
|||||||
std::atomic<bool> is_markup;
|
std::atomic<bool> is_markup;
|
||||||
uint32_t scroll_;
|
uint32_t scroll_;
|
||||||
std::vector<ScreenCell> cells;
|
std::vector<ScreenCell> cells;
|
||||||
uint32_t box_width;
|
Coord size;
|
||||||
uint32_t box_height;
|
|
||||||
std::vector<Highlight> highlights;
|
std::vector<Highlight> highlights;
|
||||||
std::vector<Span> hover_spans;
|
std::vector<Span> hover_spans;
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ struct Match {
|
|||||||
|
|
||||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
#define UNUSED(x) (void)(x)
|
||||||
|
#define USING(x) UNUSED(sizeof(x))
|
||||||
|
|
||||||
std::string clean_text(const std::string &input);
|
std::string clean_text(const std::string &input);
|
||||||
std::string percent_encode(const std::string &s);
|
std::string percent_encode(const std::string &s);
|
||||||
@@ -74,6 +76,7 @@ uint32_t get_visual_col_from_bytes(const char *line, uint32_t len,
|
|||||||
uint32_t get_bytes_from_visual_col(const char *line, uint32_t len,
|
uint32_t get_bytes_from_visual_col(const char *line, uint32_t len,
|
||||||
uint32_t target_visual_col);
|
uint32_t target_visual_col);
|
||||||
int utf8_byte_offset_to_utf16(const char *s, size_t byte_pos);
|
int utf8_byte_offset_to_utf16(const char *s, size_t byte_pos);
|
||||||
|
size_t utf16_offset_to_utf8(const char *s, int utf16_pos);
|
||||||
|
|
||||||
void log(const char *fmt, ...);
|
void log(const char *fmt, ...);
|
||||||
|
|
||||||
|
|||||||
346
src/editor/completions.cc
Normal file
346
src/editor/completions.cc
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
#include "editor/editor.h"
|
||||||
|
#include "io/knot.h"
|
||||||
|
#include "io/sysio.h"
|
||||||
|
#include "lsp/lsp.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
|
||||||
|
void completion_request(Editor *editor) {
|
||||||
|
Coord hook = editor->cursor;
|
||||||
|
word_boundaries(editor, editor->cursor, &hook.col, nullptr, nullptr, nullptr);
|
||||||
|
LineIterator *it = begin_l_iter(editor->root, hook.row);
|
||||||
|
char *line = next_line(it, nullptr);
|
||||||
|
if (!line) {
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hook.col = utf8_byte_offset_to_utf16(line, hook.col);
|
||||||
|
editor->completion.hook = hook;
|
||||||
|
LSPPending *pending = new LSPPending();
|
||||||
|
pending->editor = editor;
|
||||||
|
pending->method = "textDocument/completion";
|
||||||
|
pending->callback = [line, it](Editor *editor, std::string, json message) {
|
||||||
|
auto &session = editor->completion;
|
||||||
|
std::unique_lock lock(session.mtx);
|
||||||
|
session.active = true;
|
||||||
|
session.items.clear();
|
||||||
|
session.select = 0;
|
||||||
|
std::vector<json> items_json;
|
||||||
|
std::vector<char> end_chars_def;
|
||||||
|
int insert_text_format = 1;
|
||||||
|
if (message.contains("result")) {
|
||||||
|
auto &result = message["result"];
|
||||||
|
if (result.is_array()) {
|
||||||
|
items_json = result.get<std::vector<json>>();
|
||||||
|
session.complete = true;
|
||||||
|
} else if (result.is_object() && result.contains("items")) {
|
||||||
|
auto &list = result;
|
||||||
|
items_json = list["items"].get<std::vector<json>>();
|
||||||
|
session.complete = !list.value("isIncomplete", false);
|
||||||
|
if (list.contains("itemDefaults")) {
|
||||||
|
auto &defs = list["itemDefaults"];
|
||||||
|
if (defs.contains("insertTextFormat"))
|
||||||
|
insert_text_format = defs["insertTextFormat"].get<int>();
|
||||||
|
if (defs.contains("textEdit"))
|
||||||
|
if (defs["textEdit"].is_array())
|
||||||
|
for (auto &c : defs["textEdit"]) {
|
||||||
|
std::string str = c.get<std::string>();
|
||||||
|
if (str.size() != 1)
|
||||||
|
continue;
|
||||||
|
end_chars_def.push_back(str[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.items.reserve(items_json.size());
|
||||||
|
for (auto &item_json : items_json) {
|
||||||
|
CompletionItem item;
|
||||||
|
item.original = item_json;
|
||||||
|
item.label = item_json.value("label", "");
|
||||||
|
item.kind = item_json.value("kind", 0);
|
||||||
|
if (item_json.contains("detail"))
|
||||||
|
item.detail = item_json["detail"].get<std::string>();
|
||||||
|
if (item_json.contains("documentation")) {
|
||||||
|
if (item_json["documentation"].is_string()) {
|
||||||
|
item.documentation = item_json["documentation"].get<std::string>();
|
||||||
|
} else if (item_json["documentation"].contains("value")) {
|
||||||
|
item.is_markup =
|
||||||
|
item_json["documentation"]["kind"].get<std::string>() ==
|
||||||
|
"markdown";
|
||||||
|
item.documentation =
|
||||||
|
item_json["documentation"]["value"].get<std::string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item_json.contains("deprecated"))
|
||||||
|
item.deprecated = item_json["deprecated"].get<bool>();
|
||||||
|
auto tags = item_json.value("tags", std::vector<int>());
|
||||||
|
for (auto tag : tags)
|
||||||
|
if (tag == 1)
|
||||||
|
item.deprecated = true;
|
||||||
|
item.sort = item_json.value("sortText", item.label);
|
||||||
|
item.filter = item_json.value("filterText", item.label);
|
||||||
|
if (item_json.contains("preselect") && item_json["preselect"].get<bool>())
|
||||||
|
session.select = session.items.size() - 1;
|
||||||
|
TextEdit edit;
|
||||||
|
if (item_json.contains("textEdit")) {
|
||||||
|
auto &te = item_json["textEdit"];
|
||||||
|
if (te.contains("newText")) {
|
||||||
|
edit.text = te.value("newText", "");
|
||||||
|
if (te.contains("replace")) {
|
||||||
|
edit.start.row = te["replace"]["start"]["line"];
|
||||||
|
edit.start.col = te["replace"]["start"]["character"];
|
||||||
|
edit.end.row = te["replace"]["end"]["line"];
|
||||||
|
edit.end.col = te["replace"]["end"]["character"];
|
||||||
|
} else if (te.contains("insert")) {
|
||||||
|
edit.start.row = te["insert"]["start"]["line"];
|
||||||
|
edit.start.col = te["insert"]["start"]["character"];
|
||||||
|
edit.end.row = te["insert"]["end"]["line"];
|
||||||
|
edit.end.col = te["insert"]["end"]["character"];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
edit.text = te.value("newText", "");
|
||||||
|
edit.start.row = te["range"]["start"]["line"];
|
||||||
|
edit.start.col = te["range"]["start"]["character"];
|
||||||
|
edit.end.row = te["range"]["end"]["line"];
|
||||||
|
edit.end.col = te["range"]["end"]["character"];
|
||||||
|
}
|
||||||
|
} else if (item_json.contains("insertText")) {
|
||||||
|
edit.text = item_json["insertText"].get<std::string>();
|
||||||
|
edit.start = session.hook;
|
||||||
|
uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col);
|
||||||
|
edit.end = {editor->cursor.row, col};
|
||||||
|
}
|
||||||
|
item.edits.push_back(edit);
|
||||||
|
if (item_json.contains("additionalTextEdits")) {
|
||||||
|
for (auto &te : item_json["additionalTextEdits"]) {
|
||||||
|
TextEdit edit;
|
||||||
|
edit.text = te.value("newText", "");
|
||||||
|
edit.start.row = te["range"]["start"]["line"];
|
||||||
|
edit.start.col = te["range"]["start"]["character"];
|
||||||
|
edit.end.row = te["range"]["end"]["line"];
|
||||||
|
edit.end.col = te["range"]["end"]["character"];
|
||||||
|
item.edits.push_back(edit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.snippet = insert_text_format == 2;
|
||||||
|
if (item_json.contains("insertTextFormat"))
|
||||||
|
item.snippet = item_json["insertTextFormat"].get<int>() == 2;
|
||||||
|
if (item_json.contains("commitCharacters"))
|
||||||
|
for (auto &c : item_json["commitCharacters"])
|
||||||
|
if (c.is_string() && c.get<std::string>().size() == 1)
|
||||||
|
item.end_chars.push_back(c.get<std::string>()[0]);
|
||||||
|
session.items.push_back(std::move(item));
|
||||||
|
session.visible.push_back(session.items.size() - 1);
|
||||||
|
}
|
||||||
|
session.box.hidden = false;
|
||||||
|
session.box.render_update();
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
};
|
||||||
|
uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col);
|
||||||
|
json message = {
|
||||||
|
{"jsonrpc", "2.0"},
|
||||||
|
{"method", "textDocument/completion"},
|
||||||
|
{"params",
|
||||||
|
{{"textDocument", {{"uri", editor->uri}}},
|
||||||
|
{"position", {{"line", editor->cursor.row}, {"character", col}}}}}};
|
||||||
|
if (editor->completion.trigger > 0) {
|
||||||
|
json context = {{"triggerKind", editor->completion.trigger}};
|
||||||
|
if (editor->completion.trigger == 2 && editor->completion.trigger_char)
|
||||||
|
context["triggerCharacter"] =
|
||||||
|
std::string(1, *editor->completion.trigger_char);
|
||||||
|
message["params"]["context"] = context;
|
||||||
|
}
|
||||||
|
lsp_send(editor->lsp, message, pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static std::string completion_prefix(Editor *editor) {
|
||||||
|
Coord hook = editor->completion.hook;
|
||||||
|
Coord cur = editor->cursor;
|
||||||
|
if (hook.row != cur.row || cur.col < hook.col)
|
||||||
|
return "";
|
||||||
|
LineIterator *it = begin_l_iter(editor->root, hook.row);
|
||||||
|
char *line = next_line(it, nullptr);
|
||||||
|
if (!line) {
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
uint32_t start = utf16_offset_to_utf8(line, hook.col);
|
||||||
|
uint32_t end = editor->cursor.col;
|
||||||
|
std::string prefix(line + start, end - start);
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion_filter(Editor *editor) {
|
||||||
|
auto &session = editor->completion;
|
||||||
|
std::string prefix = completion_prefix(editor);
|
||||||
|
session.visible.clear();
|
||||||
|
for (size_t i = 0; i < session.items.size(); ++i) {
|
||||||
|
const auto &item = session.items[i];
|
||||||
|
const std::string &key = item.filter.empty() ? item.label : item.filter;
|
||||||
|
if (key.size() >= prefix.size() && key.substr(0, prefix.size()) == prefix)
|
||||||
|
session.visible.push_back(i);
|
||||||
|
}
|
||||||
|
if (session.visible.empty()) {
|
||||||
|
session.box.hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
session.box.hidden = false;
|
||||||
|
session.box.render_update();
|
||||||
|
bool found = false;
|
||||||
|
for (int i : session.visible)
|
||||||
|
if (i == session.select) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
session.select = session.visible[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_completion(Editor *editor, KeyEvent event) {
|
||||||
|
if (!editor->lsp || !editor->lsp->allow_completion)
|
||||||
|
return;
|
||||||
|
if (mode != INSERT) {
|
||||||
|
editor->completion.active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.key_type == KEY_PASTE) {
|
||||||
|
editor->completion.active = false;
|
||||||
|
return;
|
||||||
|
} else if (event.key_type == KEY_CHAR) {
|
||||||
|
char ch = *event.c;
|
||||||
|
if (!editor->completion.active) {
|
||||||
|
for (char c : editor->lsp->trigger_chars)
|
||||||
|
if (c == ch) {
|
||||||
|
editor->completion.trigger = 2;
|
||||||
|
editor->completion.trigger_char = c;
|
||||||
|
completion_request(editor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const auto &item = editor->completion.items[editor->completion.select];
|
||||||
|
const std::vector<char> &end_chars =
|
||||||
|
item.end_chars.empty() ? editor->lsp->end_chars : item.end_chars;
|
||||||
|
for (char c : end_chars)
|
||||||
|
if (c == ch) {
|
||||||
|
complete_accept(editor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' ||
|
||||||
|
ch >= '0' && ch <= '9' || ch == '_') {
|
||||||
|
if (editor->completion.active) {
|
||||||
|
if (editor->completion.complete)
|
||||||
|
completion_filter(editor);
|
||||||
|
else
|
||||||
|
completion_request(editor);
|
||||||
|
} else {
|
||||||
|
editor->completion.trigger = 3;
|
||||||
|
completion_request(editor);
|
||||||
|
}
|
||||||
|
} else if (ch == CTRL('\\')) {
|
||||||
|
if (editor->completion.active) {
|
||||||
|
complete_accept(editor);
|
||||||
|
} else {
|
||||||
|
editor->completion.trigger = 1;
|
||||||
|
completion_request(editor);
|
||||||
|
}
|
||||||
|
} else if (ch == CTRL(']')) {
|
||||||
|
if (editor->completion.active)
|
||||||
|
complete_next(editor);
|
||||||
|
} else if (ch == CTRL('[')) {
|
||||||
|
if (editor->completion.active)
|
||||||
|
complete_prev(editor);
|
||||||
|
} else if (ch == 0x7F || ch == 0x08) {
|
||||||
|
if (editor->completion.complete)
|
||||||
|
completion_filter(editor);
|
||||||
|
else
|
||||||
|
completion_request(editor);
|
||||||
|
} else {
|
||||||
|
editor->completion.active = false;
|
||||||
|
}
|
||||||
|
} else if (event.key_type == KEY_MOUSE && event.mouse_modifier == 0) {
|
||||||
|
auto &box = editor->completion.box;
|
||||||
|
Coord normalized = {event.mouse_y - box.position.row,
|
||||||
|
event.mouse_x - box.position.col};
|
||||||
|
if (normalized.row >= 0 && normalized.row < box.size.row &&
|
||||||
|
normalized.col >= 0 && normalized.col < box.size.col) {
|
||||||
|
uint8_t idx = 0;
|
||||||
|
/* todo: calculate idx based on scroll and mouse position */
|
||||||
|
complete_select(editor, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion_resolve_doc(Editor *editor) {
|
||||||
|
std::unique_lock lock(editor->completion.mtx);
|
||||||
|
auto &item = editor->completion.items[editor->completion.select];
|
||||||
|
if (item.documentation)
|
||||||
|
return;
|
||||||
|
item.documentation = "";
|
||||||
|
LSPPending *pending = new LSPPending();
|
||||||
|
pending->editor = editor;
|
||||||
|
pending->method = "completionItem/resolve";
|
||||||
|
pending->callback = [](Editor *editor, std::string, json message) {
|
||||||
|
std::unique_lock lock(editor->completion.mtx);
|
||||||
|
auto &item = editor->completion.items[editor->completion.select];
|
||||||
|
if (message.contains("documentation"))
|
||||||
|
item.documentation = message["documentation"].get<std::string>();
|
||||||
|
else
|
||||||
|
item.documentation = "";
|
||||||
|
};
|
||||||
|
json message = {{"jsonrpc", "2.0"},
|
||||||
|
{"method", "completionItem/resolve"},
|
||||||
|
{"params", item.original}};
|
||||||
|
lsp_send(editor->lsp, message, pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
void complete_accept(Editor *editor) {
|
||||||
|
if (!editor->completion.active || editor->completion.box.hidden)
|
||||||
|
return;
|
||||||
|
auto &item = editor->completion.items[editor->completion.select];
|
||||||
|
apply_lsp_edits(editor, item.edits);
|
||||||
|
editor->completion.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static int visible_index(const CompletionSession &s) {
|
||||||
|
for (size_t i = 0; i < s.visible.size(); ++i)
|
||||||
|
if (s.visible[i] == s.select)
|
||||||
|
return (int)i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void complete_next(Editor *editor) {
|
||||||
|
auto &s = editor->completion;
|
||||||
|
if (!s.active || s.box.hidden || s.visible.empty())
|
||||||
|
return;
|
||||||
|
int vi = visible_index(s);
|
||||||
|
if (vi < 0)
|
||||||
|
vi = 0;
|
||||||
|
else
|
||||||
|
vi = (vi + 1) % s.visible.size();
|
||||||
|
s.select = s.visible[vi];
|
||||||
|
completion_resolve_doc(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void complete_prev(Editor *editor) {
|
||||||
|
auto &s = editor->completion;
|
||||||
|
if (!s.active || s.box.hidden || s.visible.empty())
|
||||||
|
return;
|
||||||
|
int vi = visible_index(s);
|
||||||
|
if (vi < 0)
|
||||||
|
vi = 0;
|
||||||
|
else
|
||||||
|
vi = (vi + s.visible.size() - 1) % s.visible.size();
|
||||||
|
s.select = s.visible[vi];
|
||||||
|
completion_resolve_doc(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void complete_select(Editor *editor, uint8_t index) {
|
||||||
|
editor->completion.select = index;
|
||||||
|
complete_accept(editor);
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "editor/editor.h"
|
#include "editor/editor.h"
|
||||||
#include "editor/folds.h"
|
#include "editor/folds.h"
|
||||||
#include "lsp/lsp.h"
|
#include "lsp/lsp.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
void edit_erase(Editor *editor, Coord pos, int64_t len) {
|
void edit_erase(Editor *editor, Coord pos, int64_t len) {
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
@@ -278,3 +280,28 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void edit_replace(Editor *editor, Coord start, Coord end, const char *text,
|
||||||
|
uint32_t len) {
|
||||||
|
std::shared_lock lock(editor->knot_mtx);
|
||||||
|
uint32_t start_line_byte = line_to_byte(editor->root, start.row, nullptr);
|
||||||
|
uint32_t end_len;
|
||||||
|
uint32_t end_line_byte_start = line_to_byte(editor->root, end.row, &end_len);
|
||||||
|
uint32_t end_line_byte = end_line_byte_start + len;
|
||||||
|
lock.unlock();
|
||||||
|
char *buf =
|
||||||
|
read(editor->root, start_line_byte, end_line_byte - start_line_byte);
|
||||||
|
if (!buf)
|
||||||
|
return;
|
||||||
|
uint32_t start_col = utf16_offset_to_utf8(buf, start.col);
|
||||||
|
uint32_t end_col = utf16_offset_to_utf8(
|
||||||
|
buf + (end_line_byte_start - start_line_byte), end.col);
|
||||||
|
uint32_t erase_len =
|
||||||
|
count_clusters(buf, end_line_byte - start_line_byte, start_col,
|
||||||
|
(end_line_byte_start - start_line_byte) + end_col);
|
||||||
|
free(buf);
|
||||||
|
if (erase_len != 0)
|
||||||
|
edit_erase(editor, end, -erase_len);
|
||||||
|
if (len > 0)
|
||||||
|
edit_insert(editor, start, const_cast<char *>(text), len);
|
||||||
|
}
|
||||||
|
|||||||
@@ -364,45 +364,6 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
|||||||
case CTRL('s'):
|
case CTRL('s'):
|
||||||
save_file(editor);
|
save_file(editor);
|
||||||
break;
|
break;
|
||||||
case CTRL(' '):
|
|
||||||
if (editor->lsp) {
|
|
||||||
json msg = {
|
|
||||||
{"jsonrpc", "2.0"},
|
|
||||||
{"method", "textDocument/completion"},
|
|
||||||
{
|
|
||||||
"params",
|
|
||||||
{
|
|
||||||
{
|
|
||||||
"textDocument",
|
|
||||||
{
|
|
||||||
{"uri", editor->uri},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"position",
|
|
||||||
{
|
|
||||||
{"line", editor->cursor.row},
|
|
||||||
{"character", editor->cursor.col},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context",
|
|
||||||
{
|
|
||||||
{"triggerKind", 1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
LSPPending *pending = new LSPPending();
|
|
||||||
pending->editor = editor;
|
|
||||||
pending->method = "textDocument/completion";
|
|
||||||
pending->callback = [](Editor *editor, std::string, json completion) {
|
|
||||||
log("%s\n", completion.dump().c_str());
|
|
||||||
};
|
|
||||||
lsp_send(editor->lsp, msg, pending);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'p':
|
case 'p':
|
||||||
uint32_t len;
|
uint32_t len;
|
||||||
char *text = get_from_clipboard(&len);
|
char *text = get_from_clipboard(&len);
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
#include "editor/editor.h"
|
#include "editor/editor.h"
|
||||||
|
|
||||||
|
void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits) {
|
||||||
|
std::sort(
|
||||||
|
edits.begin(), edits.end(),
|
||||||
|
[](const TextEdit &a, const TextEdit &b) { return a.start > b.start; });
|
||||||
|
for (const auto &edit : edits)
|
||||||
|
edit_replace(editor, edit.start, edit.end, edit.text.c_str(),
|
||||||
|
edit.text.size());
|
||||||
|
}
|
||||||
|
|
||||||
void editor_lsp_handle(Editor *editor, json msg) {
|
void editor_lsp_handle(Editor *editor, json msg) {
|
||||||
if (msg.contains("method") &&
|
if (msg.contains("method") &&
|
||||||
msg["method"] == "textDocument/publishDiagnostics") {
|
msg["method"] == "textDocument/publishDiagnostics") {
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ void render() {
|
|||||||
first_render = false;
|
first_render = false;
|
||||||
}
|
}
|
||||||
for (uint32_t row = 0; row < rows; ++row) {
|
for (uint32_t row = 0; row < rows; ++row) {
|
||||||
int first_change_col = -1;
|
uint32_t first_change_col = -1;
|
||||||
int last_change_col = -1;
|
uint32_t last_change_col = -1;
|
||||||
for (uint32_t col = 0; col < cols; ++col) {
|
for (uint32_t col = 0; col < cols; ++col) {
|
||||||
uint32_t idx = row * cols + col;
|
uint32_t idx = row * cols + col;
|
||||||
ScreenCell &old_cell = old_screen[idx];
|
ScreenCell &old_cell = old_screen[idx];
|
||||||
@@ -144,7 +144,7 @@ void render() {
|
|||||||
if (first_change_col == -1) {
|
if (first_change_col == -1) {
|
||||||
first_change_col = col;
|
first_change_col = col;
|
||||||
if (first_change_col > 0) {
|
if (first_change_col > 0) {
|
||||||
for (int back = 1; back <= 3 && first_change_col - back >= 0;
|
for (uint32_t back = 1; back <= 3 && first_change_col - back >= 0;
|
||||||
++back) {
|
++back) {
|
||||||
ScreenCell &prev_cell =
|
ScreenCell &prev_cell =
|
||||||
screen[row * cols + (first_change_col - back)];
|
screen[row * cols + (first_change_col - back)];
|
||||||
@@ -163,15 +163,15 @@ void render() {
|
|||||||
char buf[64];
|
char buf[64];
|
||||||
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, first_change_col + 1);
|
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, first_change_col + 1);
|
||||||
out.append(buf);
|
out.append(buf);
|
||||||
for (int col = first_change_col; col <= last_change_col; ++col) {
|
for (uint32_t col = first_change_col; col <= last_change_col; ++col) {
|
||||||
int idx = row * cols + col;
|
uint32_t idx = row * cols + col;
|
||||||
ScreenCell &old_cell = old_screen[idx];
|
ScreenCell &old_cell = old_screen[idx];
|
||||||
ScreenCell &new_cell = screen[idx];
|
ScreenCell &new_cell = screen[idx];
|
||||||
int width = new_cell.width > 0 ? new_cell.width : 1;
|
uint32_t width = new_cell.width > 0 ? new_cell.width : 1;
|
||||||
bool overlap = false;
|
bool overlap = false;
|
||||||
if (width > 1) {
|
if (width > 1) {
|
||||||
for (int i = 1; i < width; ++i) {
|
for (uint32_t i = 1; i < width; ++i) {
|
||||||
int next_col = col + i;
|
uint32_t next_col = col + i;
|
||||||
if (next_col >= cols)
|
if (next_col >= cols)
|
||||||
break;
|
break;
|
||||||
const ScreenCell &next = screen[row * cols + next_col];
|
const ScreenCell &next = screen[row * cols + next_col];
|
||||||
@@ -241,7 +241,7 @@ void render() {
|
|||||||
current_underline = underline;
|
current_underline = underline;
|
||||||
}
|
}
|
||||||
if (width > 1 && overlap) {
|
if (width > 1 && overlap) {
|
||||||
for (int i = 1; i < width; ++i)
|
for (uint32_t i = 1; i < width; ++i)
|
||||||
out.push_back(' ');
|
out.push_back(' ');
|
||||||
} else {
|
} else {
|
||||||
if (!new_cell.utf8.empty()) {
|
if (!new_cell.utf8.empty()) {
|
||||||
@@ -282,10 +282,11 @@ void render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_cursor(int row, int col, int type, bool show_cursor_param) {
|
void set_cursor(uint32_t row, uint32_t col, uint32_t type,
|
||||||
|
bool show_cursor_param) {
|
||||||
char buf[32];
|
char buf[32];
|
||||||
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH\x1b[%d q", row + 1, col + 1,
|
uint32_t n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH\x1b[%d q", row + 1,
|
||||||
type);
|
col + 1, type);
|
||||||
show_cursor = show_cursor_param;
|
show_cursor = show_cursor_param;
|
||||||
write(STDOUT_FILENO, buf, n);
|
write(STDOUT_FILENO, buf, n);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,34 @@ std::shared_ptr<LSPInstance> get_or_init_lsp(uint8_t lsp_id) {
|
|||||||
lsp->allow_hover = caps["hoverProvider"].get<bool>();
|
lsp->allow_hover = caps["hoverProvider"].get<bool>();
|
||||||
else
|
else
|
||||||
lsp->allow_hover = false;
|
lsp->allow_hover = false;
|
||||||
if (caps.contains("completionProvider"))
|
if (caps.contains("completionProvider")) {
|
||||||
lsp->allow_completion = true;
|
lsp->allow_completion = true;
|
||||||
|
if (caps["completionProvider"].contains("resolveProvider"))
|
||||||
|
lsp->allow_resolve =
|
||||||
|
caps["completionProvider"]["resolveProvider"].get<bool>();
|
||||||
|
if (caps["completionProvider"].contains("triggerCharacters")) {
|
||||||
|
auto &chars = caps["completionProvider"]["triggerCharacters"];
|
||||||
|
if (chars.is_array()) {
|
||||||
|
for (auto &c : chars) {
|
||||||
|
std::string str = c.get<std::string>();
|
||||||
|
if (str.size() != 1)
|
||||||
|
continue;
|
||||||
|
lsp->trigger_chars.push_back(str[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (caps["completionProvider"].contains("allCommitCharacters")) {
|
||||||
|
auto &chars = caps["completionProvider"]["allCommitCharacters"];
|
||||||
|
if (chars.is_array()) {
|
||||||
|
for (auto &c : chars) {
|
||||||
|
std::string str = c.get<std::string>();
|
||||||
|
if (str.size() != 1)
|
||||||
|
continue;
|
||||||
|
lsp->end_chars.push_back(str[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
lsp->initialized = true;
|
lsp->initialized = true;
|
||||||
json initialized = {{"jsonrpc", "2.0"},
|
json initialized = {{"jsonrpc", "2.0"},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ void Bar::render() {
|
|||||||
uint32_t row = screen.row - 2;
|
uint32_t row = screen.row - 2;
|
||||||
uint32_t col = 0;
|
uint32_t col = 0;
|
||||||
uint32_t width = screen.col;
|
uint32_t width = screen.col;
|
||||||
|
UNUSED(width);
|
||||||
uint32_t color = 0;
|
uint32_t color = 0;
|
||||||
uint32_t black = 0x0b0e14;
|
uint32_t black = 0x0b0e14;
|
||||||
uint32_t grey = 0x33363c;
|
uint32_t grey = 0x33363c;
|
||||||
@@ -80,7 +81,7 @@ void Bar::handle(KeyEvent event) {
|
|||||||
cursor--;
|
cursor--;
|
||||||
break;
|
break;
|
||||||
case KEY_RIGHT:
|
case KEY_RIGHT:
|
||||||
if (cursor < command.length())
|
if (cursor < (uint32_t)command.length())
|
||||||
cursor++;
|
cursor++;
|
||||||
break;
|
break;
|
||||||
case KEY_UP:
|
case KEY_UP:
|
||||||
|
|||||||
5
src/ui/completionbox.cc
Normal file
5
src/ui/completionbox.cc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#include "ui/completionbox.h"
|
||||||
|
|
||||||
|
void CompletionBox::render_update() {}
|
||||||
|
|
||||||
|
void CompletionBox::render(Coord pos) {}
|
||||||
@@ -3,9 +3,8 @@
|
|||||||
void DiagnosticBox::clear() {
|
void DiagnosticBox::clear() {
|
||||||
warnings.clear();
|
warnings.clear();
|
||||||
cells.clear();
|
cells.clear();
|
||||||
box_width = 0;
|
size = {0, 0};
|
||||||
box_height = 0;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
void DiagnosticBox::render_first() {
|
void DiagnosticBox::render_first() {
|
||||||
if (warnings.empty())
|
if (warnings.empty())
|
||||||
@@ -18,11 +17,11 @@ void DiagnosticBox::render_first() {
|
|||||||
longest_line = MAX(longest_line, (uint32_t)see_also.length() + 4);
|
longest_line = MAX(longest_line, (uint32_t)see_also.length() + 4);
|
||||||
}
|
}
|
||||||
uint32_t content_width = MIN(longest_line, 150u);
|
uint32_t content_width = MIN(longest_line, 150u);
|
||||||
box_width = content_width + 2;
|
size.col = content_width + 2;
|
||||||
cells.assign(box_width * 25, {" ", 0, 0, 0, 0, 0});
|
cells.assign(size.col * 25, {" ", 0, 0, 0, 0, 0});
|
||||||
auto set = [&](uint32_t r, uint32_t c, const char *text, uint32_t fg,
|
auto set = [&](uint32_t r, uint32_t c, const char *text, uint32_t fg,
|
||||||
uint32_t bg, uint8_t flags) {
|
uint32_t bg, uint8_t flags) {
|
||||||
cells[r * box_width + c] = {std::string(text), 0, fg, bg, flags, 0};
|
cells[r * size.col + c] = {std::string(text), 0, fg, bg, flags, 0};
|
||||||
};
|
};
|
||||||
uint32_t base_bg = 0;
|
uint32_t base_bg = 0;
|
||||||
uint32_t border_fg = 0x82AAFF;
|
uint32_t border_fg = 0x82AAFF;
|
||||||
@@ -116,35 +115,35 @@ void DiagnosticBox::render_first() {
|
|||||||
};
|
};
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
box_height = 2 + r;
|
size.row = 2 + r;
|
||||||
set(0, 0, "┌", border_fg, base_bg, 0);
|
set(0, 0, "┌", border_fg, base_bg, 0);
|
||||||
for (uint32_t i = 1; i < box_width - 1; i++)
|
for (uint32_t i = 1; i < size.col - 1; i++)
|
||||||
set(0, i, "─", border_fg, base_bg, 0);
|
set(0, i, "─", border_fg, base_bg, 0);
|
||||||
set(0, box_width - 1, "┐", border_fg, base_bg, 0);
|
set(0, size.col - 1, "┐", border_fg, base_bg, 0);
|
||||||
for (uint32_t r = 1; r < box_height - 1; r++) {
|
for (uint32_t r = 1; r < size.row - 1; r++) {
|
||||||
set(r, 0, "│", border_fg, base_bg, 0);
|
set(r, 0, "│", border_fg, base_bg, 0);
|
||||||
set(r, box_width - 1, "│", border_fg, base_bg, 0);
|
set(r, size.col - 1, "│", border_fg, base_bg, 0);
|
||||||
}
|
}
|
||||||
set(box_height - 1, 0, "└", border_fg, base_bg, 0);
|
set(size.row - 1, 0, "└", border_fg, base_bg, 0);
|
||||||
for (uint32_t i = 1; i < box_width - 1; i++)
|
for (uint32_t i = 1; i < size.col - 1; i++)
|
||||||
set(box_height - 1, i, "─", border_fg, base_bg, 0);
|
set(size.row - 1, i, "─", border_fg, base_bg, 0);
|
||||||
set(box_height - 1, box_width - 1, "┘", border_fg, base_bg, 0);
|
set(size.row - 1, size.col - 1, "┘", border_fg, base_bg, 0);
|
||||||
cells.resize(box_width * box_height);
|
cells.resize(size.col * size.row);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiagnosticBox::render(Coord pos) {
|
void DiagnosticBox::render(Coord pos) {
|
||||||
int32_t start_row = (int32_t)pos.row - (int32_t)box_height;
|
int32_t start_row = (int32_t)pos.row - (int32_t)size.row;
|
||||||
if (start_row < 0)
|
if (start_row < 0)
|
||||||
start_row = pos.row + 1;
|
start_row = pos.row + 1;
|
||||||
int32_t start_col = pos.col;
|
int32_t start_col = pos.col;
|
||||||
if (start_col + box_width > cols) {
|
if (start_col + size.col > cols) {
|
||||||
start_col = cols - box_width;
|
start_col = cols - size.col;
|
||||||
if (start_col < 0)
|
if (start_col < 0)
|
||||||
start_col = 0;
|
start_col = 0;
|
||||||
}
|
}
|
||||||
for (uint32_t r = 0; r < box_height; r++)
|
for (uint32_t r = 0; r < size.row; r++)
|
||||||
for (uint32_t c = 0; c < box_width; c++)
|
for (uint32_t c = 0; c < size.col; c++)
|
||||||
update(start_row + r, start_col + c, cells[r * box_width + c].utf8,
|
update(start_row + r, start_col + c, cells[r * size.col + c].utf8,
|
||||||
cells[r * box_width + c].fg, cells[r * box_width + c].bg,
|
cells[r * size.col + c].fg, cells[r * size.col + c].bg,
|
||||||
cells[r * box_width + c].flags);
|
cells[r * size.col + c].flags);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ void HoverBox::clear() {
|
|||||||
text = "";
|
text = "";
|
||||||
scroll_ = 0;
|
scroll_ = 0;
|
||||||
is_markup = false;
|
is_markup = false;
|
||||||
box_width = 0;
|
size = {0, 0};
|
||||||
box_height = 0;
|
|
||||||
cells.clear();
|
cells.clear();
|
||||||
highlights.clear();
|
highlights.clear();
|
||||||
hover_spans.clear();
|
hover_spans.clear();
|
||||||
@@ -139,7 +138,7 @@ void HoverBox::render_first(bool scroll) {
|
|||||||
// in the loop instead as it was never meant to wrap in the first place
|
// in the loop instead as it was never meant to wrap in the first place
|
||||||
longest_line = MAX(longest_line, current_width) + 1;
|
longest_line = MAX(longest_line, current_width) + 1;
|
||||||
uint32_t content_width = MIN(longest_line, 130u);
|
uint32_t content_width = MIN(longest_line, 130u);
|
||||||
box_width = content_width + 2;
|
size.col = content_width + 2;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
size_t lines_skipped = 0;
|
size_t lines_skipped = 0;
|
||||||
while (i < text.length() && lines_skipped < scroll_) {
|
while (i < text.length() && lines_skipped < scroll_) {
|
||||||
@@ -153,10 +152,10 @@ void HoverBox::render_first(bool scroll) {
|
|||||||
uint32_t base_bg = 0;
|
uint32_t base_bg = 0;
|
||||||
SpanCursor span_cursor(spans);
|
SpanCursor span_cursor(spans);
|
||||||
span_cursor.sync(i);
|
span_cursor.sync(i);
|
||||||
cells.assign(box_width * 26, ScreenCell{" ", 0, 0, 0, 0, 0});
|
cells.assign(size.col * 26, ScreenCell{" ", 0, 0, 0, 0, 0});
|
||||||
auto set = [&](uint32_t r, uint32_t c, const char *text, uint32_t fg,
|
auto set = [&](uint32_t r, uint32_t c, const char *text, uint32_t fg,
|
||||||
uint32_t bg, uint8_t flags) {
|
uint32_t bg, uint8_t flags) {
|
||||||
cells[r * box_width + c] = {std::string(text), 0, fg, bg, flags, 0};
|
cells[r * size.col + c] = {std::string(text), 0, fg, bg, flags, 0};
|
||||||
};
|
};
|
||||||
uint32_t r = 0;
|
uint32_t r = 0;
|
||||||
while (i < text.length() && r < 24) {
|
while (i < text.length() && r < 24) {
|
||||||
@@ -186,35 +185,35 @@ void HoverBox::render_first(bool scroll) {
|
|||||||
r++;
|
r++;
|
||||||
}
|
}
|
||||||
if (!scroll)
|
if (!scroll)
|
||||||
box_height = r + 2;
|
size.row = r + 2;
|
||||||
set(0, 0, "┌", border_fg, base_bg, 0);
|
set(0, 0, "┌", border_fg, base_bg, 0);
|
||||||
for (uint32_t i = 1; i < box_width - 1; i++)
|
for (uint32_t i = 1; i < size.col - 1; i++)
|
||||||
set(0, i, "─", border_fg, base_bg, 0);
|
set(0, i, "─", border_fg, base_bg, 0);
|
||||||
set(0, box_width - 1, "┐", border_fg, base_bg, 0);
|
set(0, size.col - 1, "┐", border_fg, base_bg, 0);
|
||||||
for (uint32_t r = 1; r < box_height - 1; r++) {
|
for (uint32_t r = 1; r < size.row - 1; r++) {
|
||||||
set(r, 0, "│", border_fg, base_bg, 0);
|
set(r, 0, "│", border_fg, base_bg, 0);
|
||||||
set(r, box_width - 1, "│", border_fg, base_bg, 0);
|
set(r, size.col - 1, "│", border_fg, base_bg, 0);
|
||||||
}
|
}
|
||||||
set(box_height - 1, 0, "└", border_fg, base_bg, 0);
|
set(size.row - 1, 0, "└", border_fg, base_bg, 0);
|
||||||
for (uint32_t i = 1; i < box_width - 1; i++)
|
for (uint32_t i = 1; i < size.col - 1; i++)
|
||||||
set(box_height - 1, i, "─", border_fg, base_bg, 0);
|
set(size.row - 1, i, "─", border_fg, base_bg, 0);
|
||||||
set(box_height - 1, box_width - 1, "┘", border_fg, base_bg, 0);
|
set(size.row - 1, size.col - 1, "┘", border_fg, base_bg, 0);
|
||||||
cells.resize(box_width * box_height);
|
cells.resize(size.col * size.row);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HoverBox::render(Coord pos) {
|
void HoverBox::render(Coord pos) {
|
||||||
int32_t start_row = (int32_t)pos.row - (int32_t)box_height;
|
int32_t start_row = (int32_t)pos.row - (int32_t)size.row;
|
||||||
if (start_row < 0)
|
if (start_row < 0)
|
||||||
start_row = pos.row + 1;
|
start_row = pos.row + 1;
|
||||||
int32_t start_col = pos.col;
|
int32_t start_col = pos.col;
|
||||||
if (start_col + box_width > cols) {
|
if (start_col + size.col > cols) {
|
||||||
start_col = cols - box_width;
|
start_col = cols - size.col;
|
||||||
if (start_col < 0)
|
if (start_col < 0)
|
||||||
start_col = 0;
|
start_col = 0;
|
||||||
}
|
}
|
||||||
for (uint32_t r = 0; r < box_height; r++)
|
for (uint32_t r = 0; r < size.row; r++)
|
||||||
for (uint32_t c = 0; c < box_width; c++)
|
for (uint32_t c = 0; c < size.col; c++)
|
||||||
update(start_row + r, start_col + c, cells[r * box_width + c].utf8,
|
update(start_row + r, start_col + c, cells[r * size.col + c].utf8,
|
||||||
cells[r * box_width + c].fg, cells[r * box_width + c].bg,
|
cells[r * size.col + c].fg, cells[r * size.col + c].bg,
|
||||||
cells[r * box_width + c].flags);
|
cells[r * size.col + c].flags);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ Language language_for_file(const char *filename) {
|
|||||||
if (it != kMimeToLang.end())
|
if (it != kMimeToLang.end())
|
||||||
return kLanguages.find(it->second)->second;
|
return kLanguages.find(it->second)->second;
|
||||||
}
|
}
|
||||||
return {"unknown", nullptr};
|
return Language{};
|
||||||
}
|
}
|
||||||
|
|
||||||
char *get_from_clipboard(uint32_t *out_len) {
|
char *get_from_clipboard(uint32_t *out_len) {
|
||||||
|
|||||||
@@ -107,3 +107,25 @@ int utf8_byte_offset_to_utf16(const char *s, size_t byte_pos) {
|
|||||||
}
|
}
|
||||||
return utf16_units;
|
return utf16_units;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t utf16_offset_to_utf8(const char *s, int utf16_pos) {
|
||||||
|
int utf16_units = 0;
|
||||||
|
size_t i = 0;
|
||||||
|
while (utf16_units < utf16_pos) {
|
||||||
|
unsigned char c = s[i];
|
||||||
|
if ((c & 0x80) == 0x00) {
|
||||||
|
i += 1;
|
||||||
|
utf16_units += 1;
|
||||||
|
} else if ((c & 0xE0) == 0xC0) {
|
||||||
|
i += 2;
|
||||||
|
utf16_units += 1;
|
||||||
|
} else if ((c & 0xF0) == 0xE0) {
|
||||||
|
i += 3;
|
||||||
|
utf16_units += 1;
|
||||||
|
} else {
|
||||||
|
i += 4;
|
||||||
|
utf16_units += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user