Completions bug fixes
This commit is contained in:
@@ -6,10 +6,13 @@ A TUI IDE.
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- [ ] Normalize completions edits if local filtering is used
|
|
||||||
- [ ] Check why fish is behaving soo off with completions filtering
|
- [ ] Check why fish is behaving soo off with completions filtering
|
||||||
- [ ] Also why clangd doesnt show any completions but applys them at 0,0
|
- [ ] Normalize completions edits if local filtering is used
|
||||||
- [ ] Also why solargraph applys edits at 0,0
|
- [ ] Capture ctrl+h,l for scrolling documentation
|
||||||
|
- [ ] Documentation fix position and make it async first render
|
||||||
|
- [ ] Allow completion list to be scrolled up and down and show only x max at a time
|
||||||
|
- [ ] Dont filter case sensitive.
|
||||||
|
- [ ] Do not recompute word under cursor if not changed
|
||||||
- [ ] Finish autocomplete box style functions.
|
- [ ] Finish autocomplete box style functions.
|
||||||
- [ ] Add status bar & RUNNER mode
|
- [ ] Add status bar & RUNNER mode
|
||||||
- [ ] Get code context from tree-sitter
|
- [ ] Get code context from tree-sitter
|
||||||
|
|||||||
@@ -506,29 +506,29 @@
|
|||||||
; Macros & directives
|
; Macros & directives
|
||||||
; ============================================================
|
; ============================================================
|
||||||
|
|
||||||
;; #F29CC3 #000000 0 0 0 0 2
|
;; #F29CC3 #000000 0 0 0 0 3
|
||||||
(preproc_def
|
(preproc_def
|
||||||
name: (_) @constant.macro)
|
name: (_) @constant.macro)
|
||||||
|
|
||||||
;; #F29CC3 #000000 0 0 0 0 2
|
;; #F29CC3 #000000 0 0 0 0 3
|
||||||
(preproc_call
|
(preproc_call
|
||||||
directive: (preproc_directive) @_u
|
directive: (preproc_directive) @_u
|
||||||
argument: (_) @constant.macro
|
argument: (_) @constant.macro
|
||||||
(#match? @_u "^#undef$"))
|
(#match? @_u "^#undef$"))
|
||||||
|
|
||||||
;; #F29CC3 #000000 0 0 0 0 2
|
;; #F29CC3 #000000 0 0 0 0 3
|
||||||
(preproc_ifdef
|
(preproc_ifdef
|
||||||
name: (identifier) @constant.macro)
|
name: (identifier) @constant.macro)
|
||||||
|
|
||||||
;; #F29CC3 #000000 0 0 0 0 2
|
;; #F29CC3 #000000 0 0 0 0 3
|
||||||
(preproc_elifdef
|
(preproc_elifdef
|
||||||
name: (identifier) @constant.macro)
|
name: (identifier) @constant.macro)
|
||||||
|
|
||||||
;; #F29CC3 #000000 0 0 0 0 2
|
;; #F29CC3 #000000 0 0 0 0 3
|
||||||
(preproc_defined
|
(preproc_defined
|
||||||
(identifier) @constant.macro)
|
(identifier) @constant.macro)
|
||||||
|
|
||||||
;; #F29CC3 #000000 0 0 0 0 2
|
;; #F29CC3 #000000 0 0 0 0 3
|
||||||
(preproc_defined) @function.macro
|
(preproc_defined) @function.macro
|
||||||
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ struct CompletionSession {
|
|||||||
std::optional<char> trigger_char;
|
std::optional<char> trigger_char;
|
||||||
uint8_t trigger = 0;
|
uint8_t trigger = 0;
|
||||||
CompletionBox box;
|
CompletionBox box;
|
||||||
|
HoverBox hover;
|
||||||
|
uint32_t doc = UINT32_MAX;
|
||||||
|
|
||||||
CompletionSession() : box(this) {}
|
CompletionSession() : box(this) {}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
|
||||||
struct TextEdit {
|
struct TextEdit {
|
||||||
// NOTE: start.col is in utf16 index and not clusters or utf8
|
|
||||||
Coord start;
|
Coord start;
|
||||||
// NOTE: end.col is in utf16 index and not clusters or utf8
|
|
||||||
Coord end;
|
Coord end;
|
||||||
std::string text;
|
std::string text;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
#define EXTRA_META 4
|
#define EXTRA_META 4
|
||||||
#define INDENT_WIDTH 2
|
#define INDENT_WIDTH 2
|
||||||
|
|
||||||
|
// autocomplete lua// Bracket closing / tab on enter
|
||||||
|
|
||||||
struct Editor {
|
struct Editor {
|
||||||
std::string filename;
|
std::string filename;
|
||||||
std::string uri;
|
std::string uri;
|
||||||
@@ -95,7 +97,7 @@ 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 apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits, bool move);
|
||||||
void completion_resolve_doc(Editor *editor);
|
void completion_resolve_doc(Editor *editor);
|
||||||
void complete_accept(Editor *editor);
|
void complete_accept(Editor *editor);
|
||||||
void complete_next(Editor *editor);
|
void complete_next(Editor *editor);
|
||||||
@@ -116,4 +118,56 @@ inline void apply_hook_deletion(Editor *editor, uint32_t removal_start,
|
|||||||
hook -= removal_end - removal_start + 1;
|
hook -= removal_end - removal_start + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline static void utf8_normalize_edit(Editor *editor, TextEdit *edit) {
|
||||||
|
std::shared_lock lock(editor->knot_mtx);
|
||||||
|
if (edit->start.row > editor->root->line_count) {
|
||||||
|
edit->start.row = editor->root->line_count;
|
||||||
|
edit->start.col = UINT32_MAX;
|
||||||
|
}
|
||||||
|
if (edit->end.row > editor->root->line_count) {
|
||||||
|
edit->end.row = editor->root->line_count;
|
||||||
|
edit->end.col = UINT32_MAX;
|
||||||
|
}
|
||||||
|
LineIterator *it = begin_l_iter(editor->root, edit->start.row);
|
||||||
|
if (!it)
|
||||||
|
return;
|
||||||
|
uint32_t len;
|
||||||
|
char *line = next_line(it, &len);
|
||||||
|
if (!line) {
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (edit->start.col < len)
|
||||||
|
edit->start.col = utf16_offset_to_utf8(line, edit->start.col);
|
||||||
|
else
|
||||||
|
edit->start.col = len;
|
||||||
|
if (edit->end.row == edit->start.row) {
|
||||||
|
if (edit->end.col < len)
|
||||||
|
edit->end.col = utf16_offset_to_utf8(line, edit->end.col);
|
||||||
|
else
|
||||||
|
edit->end.col = len;
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
it = begin_l_iter(editor->root, edit->end.row);
|
||||||
|
if (!it)
|
||||||
|
return;
|
||||||
|
line = next_line(it, &len);
|
||||||
|
if (!line) {
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (edit->end.col < len)
|
||||||
|
edit->end.col = utf16_offset_to_utf8(line, edit->end.col);
|
||||||
|
else
|
||||||
|
edit->end.col = len;
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ struct LSPInstance {
|
|||||||
bool allow_hover = false;
|
bool allow_hover = false;
|
||||||
bool allow_completion = false;
|
bool allow_completion = false;
|
||||||
bool allow_resolve = false;
|
bool allow_resolve = false;
|
||||||
|
bool allow_formatting = false;
|
||||||
|
bool allow_formatting_on_type = false;
|
||||||
|
std::vector<char> format_chars;
|
||||||
std::vector<char> trigger_chars;
|
std::vector<char> trigger_chars;
|
||||||
std::vector<char> end_chars;
|
std::vector<char> end_chars;
|
||||||
uint32_t last_id = 0;
|
uint32_t last_id = 0;
|
||||||
@@ -53,6 +56,8 @@ static json client_capabilities = {
|
|||||||
{"textDocument",
|
{"textDocument",
|
||||||
{{"publishDiagnostics", {{"relatedInformation", true}}},
|
{{"publishDiagnostics", {{"relatedInformation", true}}},
|
||||||
{"hover", {{"contentFormat", {"markdown", "plaintext"}}}},
|
{"hover", {{"contentFormat", {"markdown", "plaintext"}}}},
|
||||||
|
{"formatting", {{"dynamicRegistration", false}}},
|
||||||
|
{"onTypeFormatting", {{"dynamicRegistration", false}}},
|
||||||
{"completion",
|
{"completion",
|
||||||
{{"completionItem",
|
{{"completionItem",
|
||||||
{{"commitCharactersSupport", true},
|
{{"commitCharactersSupport", true},
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ extern std::unordered_map<std::string, pcre2_code *> regex_cache;
|
|||||||
|
|
||||||
TSQuery *load_query(const char *query_path, TSSetBase *set);
|
TSQuery *load_query(const char *query_path, TSSetBase *set);
|
||||||
void ts_collect_spans(Editor *editor);
|
void ts_collect_spans(Editor *editor);
|
||||||
bool ts_predicate(TSQuery *query, const TSQueryMatch &match,
|
bool ts_predicate(
|
||||||
std::function<std::string(const TSNode *)> subject_fn);
|
TSQuery *query, const TSQueryMatch &match,
|
||||||
|
std::function<char *(const TSNode *, uint32_t *len)> subject_fn);
|
||||||
void clear_regex_cache();
|
void clear_regex_cache();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env fish
|
#!/usr/bin/env fish
|
||||||
# Fish highlighting torture test 🐟
|
# Fish highlighting torture test
|
||||||
|
|
||||||
# === Variables ===
|
# === Variables ===
|
||||||
set normal_var "hello"
|
set normal_var hello
|
||||||
set -l local_var 123
|
set -l local_var 123
|
||||||
set -gx GLOBAL_VAR "world"
|
set -gx GLOBAL_VAR world
|
||||||
set PATH $PATH /usr/local/bin
|
set PATH $PATH /usr/local/bin
|
||||||
set --erase OLD_VAR
|
set --erase OLD_VAR
|
||||||
|
|
||||||
@@ -17,15 +17,15 @@ set double "double quoted $normal_var"
|
|||||||
set escaped "newline\n tab\t dollar\$"
|
set escaped "newline\n tab\t dollar\$"
|
||||||
|
|
||||||
# === Conditionals ===
|
# === Conditionals ===
|
||||||
if test $normal_var = "hello"
|
if test $normal_var = hello
|
||||||
echo "equal"
|
echo equal
|
||||||
else if test $normal_var != "world"
|
else if test $normal_var != world
|
||||||
echo "not equal"
|
echo "not equal"
|
||||||
end
|
end
|
||||||
|
|
||||||
# === Logical operators ===
|
# === Logical operators ===
|
||||||
true and echo "yes"
|
true and echo yes
|
||||||
false or echo "fallback"
|
false or echo fallback
|
||||||
not false
|
not false
|
||||||
|
|
||||||
# === Arithmetic ===
|
# === Arithmetic ===
|
||||||
@@ -50,14 +50,14 @@ function greet --argument name
|
|||||||
echo "Hello $name"
|
echo "Hello $name"
|
||||||
end
|
end
|
||||||
|
|
||||||
greet "world"
|
greet world
|
||||||
|
|
||||||
# === Command substitution ===
|
# === Command substitution ===
|
||||||
set files (ls | grep ".fish")
|
set files (ls | grep ".fish")
|
||||||
|
|
||||||
# === Redirections ===
|
# === Redirections ===
|
||||||
echo "output" > /tmp/fish_test.txt
|
echo output >/tmp/fish_test.txt
|
||||||
cat < /tmp/fish_test.txt >> /tmp/fish_log.txt
|
cat </tmp/fish_test.txt >>/tmp/fish_log.txt
|
||||||
|
|
||||||
# === Process substitution ===
|
# === Process substitution ===
|
||||||
diff (ls /bin) (ls /usr/bin)
|
diff (ls /bin) (ls /usr/bin)
|
||||||
@@ -65,11 +65,11 @@ diff (ls /bin) (ls /usr/bin)
|
|||||||
# === Case statement ===
|
# === Case statement ===
|
||||||
switch $argv[1]
|
switch $argv[1]
|
||||||
case start
|
case start
|
||||||
echo "Starting"
|
echo Starting
|
||||||
case stop
|
case stop
|
||||||
echo "Stopping"
|
echo Stopping
|
||||||
case '*'
|
case '*'
|
||||||
echo "Unknown"
|
echo Unknown
|
||||||
end
|
end
|
||||||
|
|
||||||
# === Subshell ===
|
# === Subshell ===
|
||||||
@@ -79,10 +79,10 @@ end
|
|||||||
|
|
||||||
# === Comments & operators ===
|
# === Comments & operators ===
|
||||||
# && || | & ! should all highlight
|
# && || | & ! should all highlight
|
||||||
true && echo "ok" || echo "fail"
|
true && echo ok || echo fail
|
||||||
|
|
||||||
# === Regex ===
|
# === Regex ===
|
||||||
string match -r '^[a-z]+$' "hello"
|
string match -r '^[a-z]+$' hello
|
||||||
|
|
||||||
# === Test builtin ===
|
# === Test builtin ===
|
||||||
test -f /etc/passwd
|
test -f /etc/passwd
|
||||||
@@ -90,3 +90,4 @@ test ! -d /does/not/exist
|
|||||||
|
|
||||||
# === Exit ===
|
# === Exit ===
|
||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
|
|||||||
156
samples/php.php
156
samples/php.php
@@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>PHP Syntax Stress Test</title>
|
<title>PHP Syntax Stress Test</title>
|
||||||
@@ -33,98 +34,103 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Basic variables
|
// Basic variables
|
||||||
$number = 42;
|
$number = 42;
|
||||||
$text = "Hello PHP";
|
$text = "Hello PHP";
|
||||||
$truth = true;
|
$truth = true;
|
||||||
$nothing = null;
|
$nothing = null;
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
define("APP_NAME", "SyntaxTester");
|
define("APP_NAME", "SyntaxTester");
|
||||||
|
|
||||||
// Arrays
|
// Arrays
|
||||||
$list = [1, 2, 3];
|
$list = [1, 2, 3];
|
||||||
$assoc = [
|
$assoc = [
|
||||||
"one" => 1,
|
"one" => 1,
|
||||||
"two" => 2
|
"two" => 2
|
||||||
];
|
];
|
||||||
|
|
||||||
// Function
|
// Function
|
||||||
function add(int $a, int $b): int {
|
function add(int $a, int $b): int
|
||||||
return $a + $b;
|
{
|
||||||
}
|
return $a + $b;
|
||||||
|
|
||||||
// Class + methods
|
|
||||||
class User {
|
|
||||||
private string $name;
|
|
||||||
public static int $count = 0;
|
|
||||||
|
|
||||||
public function __construct(string $name) {
|
|
||||||
$this->name = $name;
|
|
||||||
self::$count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function greet(): string {
|
// Class + methods
|
||||||
return "Hello {$this->name}";
|
class User
|
||||||
|
{
|
||||||
|
private string $name;
|
||||||
|
public static int $count = 0;
|
||||||
|
|
||||||
|
public function __construct(string $name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
self::$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function greet(): string
|
||||||
|
{
|
||||||
|
return "Hello {$this->name}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Object usage
|
// Object usage
|
||||||
$user = new User("Alice");
|
$user = new User("Alice");
|
||||||
echo $user->greet();
|
echo $user->greet();
|
||||||
|
|
||||||
// Control flow
|
// Control flow
|
||||||
if ($number > 10) {
|
if ($number > 10) {
|
||||||
echo "Big number";
|
echo "Big number";
|
||||||
} elseif ($number === 10) {
|
} elseif ($number === 10) {
|
||||||
echo "Exactly ten";
|
echo "Exactly ten";
|
||||||
} else {
|
} else {
|
||||||
echo "Small number";
|
echo "Small number";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop
|
// Loop
|
||||||
foreach ($list as $item) {
|
foreach ($list as $item) {
|
||||||
echo $item;
|
echo $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match expression
|
// Match expression
|
||||||
$result = match ($number) {
|
$result = match ($number) {
|
||||||
1 => "one",
|
1 => "one",
|
||||||
2 => "two",
|
2 => "two",
|
||||||
default => "many"
|
default => "many"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try / catch
|
// Try / catch
|
||||||
try {
|
try {
|
||||||
throw new Exception("Test exception");
|
throw new Exception("Test exception");
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
echo $e->getMessage();
|
echo $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anonymous function
|
// Anonymous function
|
||||||
$double = fn($x) => $x * 2;
|
$double = fn($x) => $x * 2;
|
||||||
|
|
||||||
// Nullsafe operator
|
// Nullsafe operator
|
||||||
$len = $user?->name ? strlen($user->name) : 0;
|
$len = $user?->name ? strlen($user->name) : 0;
|
||||||
|
|
||||||
// Ternary
|
// Ternary
|
||||||
$status = $truth ? "yes" : "no";
|
$status = $truth ? "yes" : "no";
|
||||||
|
|
||||||
// Include / require
|
// Include / require
|
||||||
require_once "config.php";
|
require_once "config.php";
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
echo "<div class='box'>";
|
echo "<div class='box'>";
|
||||||
echo htmlspecialchars($text);
|
echo htmlspecialchars($text);
|
||||||
echo "</div>";
|
echo "</div>";
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// JS interacting with PHP output
|
// JS interacting with PHP output
|
||||||
const phpValue = <?= json_encode($number) ?>;
|
const phpValue = <?= json_encode($number) ?>;
|
||||||
console.log("Value from PHP:", phpValue);
|
console.log("Value from PHP:", phpValue);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Test file for Python Tree-sitter highlighting."""
|
"""Test file for Python Tree-sitter highlighting."""
|
||||||
@@ -17,7 +18,6 @@ __name__ # builtin constant
|
|||||||
import os
|
import os
|
||||||
import sys as system
|
import sys as system
|
||||||
from re import compile as re_compile
|
from re import compile as re_compile
|
||||||
from __future__ import annotations
|
|
||||||
from math import *
|
from math import *
|
||||||
|
|
||||||
# ==============================
|
# ==============================
|
||||||
@@ -94,7 +94,7 @@ while x > 0:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
1 / 0
|
1 / 0
|
||||||
except ZeroDivisionError as e:
|
except ZeroDivisionError as err:
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
pass
|
pass
|
||||||
@@ -105,7 +105,7 @@ finally:
|
|||||||
a, b = 5, 10
|
a, b = 5, 10
|
||||||
c = a + b * 2 // 3 % 4 ** 2
|
c = a + b * 2 // 3 % 4 ** 2
|
||||||
d = (a << 2) & b | c ^ ~a
|
d = (a << 2) & b | c ^ ~a
|
||||||
e = not a or b and c
|
ef = not a or b and c
|
||||||
|
|
||||||
# ==============================
|
# ==============================
|
||||||
# f-strings / interpolation
|
# f-strings / interpolation
|
||||||
@@ -131,7 +131,7 @@ def static_func():
|
|||||||
def cls_func(cls):
|
def cls_func(cls):
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
@custom_decorator
|
# @custom_decorator
|
||||||
def decorated_func():
|
def decorated_func():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
#include "editor/decl.h"
|
||||||
#include "editor/editor.h"
|
#include "editor/editor.h"
|
||||||
#include "io/knot.h"
|
#include "io/knot.h"
|
||||||
#include "io/sysio.h"
|
#include "io/sysio.h"
|
||||||
#include "lsp/lsp.h"
|
#include "lsp/lsp.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
inline static std::string completion_prefix(Editor *editor) {
|
inline static std::string completion_prefix(Editor *editor) {
|
||||||
Coord hook = editor->completion.hook;
|
Coord hook = editor->completion.hook;
|
||||||
@@ -50,23 +52,15 @@ void completion_filter(Editor *editor) {
|
|||||||
void completion_request(Editor *editor) {
|
void completion_request(Editor *editor) {
|
||||||
Coord hook = editor->cursor;
|
Coord hook = editor->cursor;
|
||||||
word_boundaries(editor, editor->cursor, &hook.col, nullptr, nullptr, nullptr);
|
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;
|
|
||||||
}
|
|
||||||
editor->completion.active = true;
|
editor->completion.active = true;
|
||||||
editor->completion.items.clear();
|
editor->completion.items.clear();
|
||||||
editor->completion.visible.clear();
|
editor->completion.visible.clear();
|
||||||
editor->completion.select = 0;
|
editor->completion.select = 0;
|
||||||
hook.col = utf8_byte_offset_to_utf16(line, hook.col);
|
|
||||||
editor->completion.hook = hook;
|
editor->completion.hook = hook;
|
||||||
LSPPending *pending = new LSPPending();
|
LSPPending *pending = new LSPPending();
|
||||||
pending->editor = editor;
|
pending->editor = editor;
|
||||||
pending->method = "textDocument/completion";
|
pending->method = "textDocument/completion";
|
||||||
pending->callback = [line, it](Editor *editor, std::string, json message) {
|
pending->callback = [](Editor *editor, std::string, json message) {
|
||||||
auto &session = editor->completion;
|
auto &session = editor->completion;
|
||||||
std::unique_lock lock(session.mtx);
|
std::unique_lock lock(session.mtx);
|
||||||
std::vector<json> items_json;
|
std::vector<json> items_json;
|
||||||
@@ -115,8 +109,15 @@ void completion_request(Editor *editor) {
|
|||||||
item.is_markup =
|
item.is_markup =
|
||||||
item_json["documentation"]["kind"].get<std::string>() ==
|
item_json["documentation"]["kind"].get<std::string>() ==
|
||||||
"markdown";
|
"markdown";
|
||||||
item.documentation =
|
std::string documentation =
|
||||||
item_json["documentation"]["value"].get<std::string>();
|
item_json["documentation"]["value"].get<std::string>();
|
||||||
|
if (item.is_markup) {
|
||||||
|
static const std::regex fence_no_lang("```(\\s*\\n)");
|
||||||
|
item.documentation = std::regex_replace(
|
||||||
|
documentation, fence_no_lang, "```" + editor->lang.name + "$1");
|
||||||
|
} else {
|
||||||
|
item.documentation = documentation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item_json.contains("deprecated") &&
|
if (item_json.contains("deprecated") &&
|
||||||
@@ -147,26 +148,27 @@ void completion_request(Editor *editor) {
|
|||||||
edit.start.col = te["insert"]["start"]["character"];
|
edit.start.col = te["insert"]["start"]["character"];
|
||||||
edit.end.row = te["insert"]["end"]["line"];
|
edit.end.row = te["insert"]["end"]["line"];
|
||||||
edit.end.col = te["insert"]["end"]["character"];
|
edit.end.col = te["insert"]["end"]["character"];
|
||||||
|
} else if (te.contains("range")) {
|
||||||
|
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 {
|
||||||
|
edit.start = session.hook;
|
||||||
|
edit.end = editor->cursor;
|
||||||
}
|
}
|
||||||
} 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") &&
|
} else if (item_json.contains("insertText") &&
|
||||||
item_json["insertText"].is_string()) {
|
item_json["insertText"].is_string()) {
|
||||||
edit.text = item_json["insertText"].get<std::string>();
|
edit.text = item_json["insertText"].get<std::string>();
|
||||||
edit.start = session.hook;
|
edit.start = session.hook;
|
||||||
uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col);
|
edit.end = editor->cursor;
|
||||||
edit.end = {editor->cursor.row, col};
|
|
||||||
} else {
|
} else {
|
||||||
edit.text = item.label;
|
edit.text = item.label;
|
||||||
edit.start = session.hook;
|
edit.start = session.hook;
|
||||||
uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col);
|
edit.end = editor->cursor;
|
||||||
edit.end = {editor->cursor.row, col};
|
|
||||||
}
|
}
|
||||||
|
utf8_normalize_edit(editor, &edit);
|
||||||
item.edits.push_back(edit);
|
item.edits.push_back(edit);
|
||||||
if (item_json.contains("additionalTextEdits")) {
|
if (item_json.contains("additionalTextEdits")) {
|
||||||
for (auto &te : item_json["additionalTextEdits"]) {
|
for (auto &te : item_json["additionalTextEdits"]) {
|
||||||
@@ -176,6 +178,7 @@ void completion_request(Editor *editor) {
|
|||||||
edit.start.col = te["range"]["start"]["character"];
|
edit.start.col = te["range"]["start"]["character"];
|
||||||
edit.end.row = te["range"]["end"]["line"];
|
edit.end.row = te["range"]["end"]["line"];
|
||||||
edit.end.col = te["range"]["end"]["character"];
|
edit.end.col = te["range"]["end"]["character"];
|
||||||
|
utf8_normalize_edit(editor, &edit);
|
||||||
item.edits.push_back(edit);
|
item.edits.push_back(edit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,15 +190,23 @@ void completion_request(Editor *editor) {
|
|||||||
if (c.is_string() && c.get<std::string>().size() == 1)
|
if (c.is_string() && c.get<std::string>().size() == 1)
|
||||||
item.end_chars.push_back(c.get<std::string>()[0]);
|
item.end_chars.push_back(c.get<std::string>()[0]);
|
||||||
session.items.push_back(std::move(item));
|
session.items.push_back(std::move(item));
|
||||||
session.visible.push_back(session.items.size() - 1);
|
|
||||||
}
|
}
|
||||||
completion_filter(editor);
|
completion_filter(editor);
|
||||||
session.box.hidden = false;
|
session.box.hidden = false;
|
||||||
session.box.render_update();
|
session.box.render_update();
|
||||||
|
};
|
||||||
|
std::shared_lock lock(editor->knot_mtx);
|
||||||
|
LineIterator *it = begin_l_iter(editor->root, hook.row);
|
||||||
|
char *line = next_line(it, nullptr);
|
||||||
|
if (!line) {
|
||||||
free(it->buffer);
|
free(it->buffer);
|
||||||
free(it);
|
free(it);
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col);
|
uint32_t col = utf8_byte_offset_to_utf16(line, editor->cursor.col);
|
||||||
|
free(it->buffer);
|
||||||
|
free(it);
|
||||||
|
lock.unlock();
|
||||||
json message = {
|
json message = {
|
||||||
{"jsonrpc", "2.0"},
|
{"jsonrpc", "2.0"},
|
||||||
{"method", "textDocument/completion"},
|
{"method", "textDocument/completion"},
|
||||||
@@ -315,10 +326,26 @@ void completion_resolve_doc(Editor *editor) {
|
|||||||
pending->callback = [](Editor *editor, std::string, json message) {
|
pending->callback = [](Editor *editor, std::string, json message) {
|
||||||
std::unique_lock lock(editor->completion.mtx);
|
std::unique_lock lock(editor->completion.mtx);
|
||||||
auto &item = editor->completion.items[editor->completion.select];
|
auto &item = editor->completion.items[editor->completion.select];
|
||||||
if (message.contains("documentation"))
|
if (message["result"].contains("documentation")) {
|
||||||
item.documentation = message["documentation"].get<std::string>();
|
if (message["result"]["documentation"].is_string()) {
|
||||||
else
|
item.documentation =
|
||||||
item.documentation = "";
|
message["result"]["documentation"].get<std::string>();
|
||||||
|
} else if (message["result"]["documentation"].contains("value") &&
|
||||||
|
message["result"]["documentation"]["value"].is_string()) {
|
||||||
|
item.is_markup =
|
||||||
|
message["result"]["documentation"]["kind"].get<std::string>() ==
|
||||||
|
"markdown";
|
||||||
|
std::string documentation =
|
||||||
|
message["result"]["documentation"]["value"].get<std::string>();
|
||||||
|
if (item.is_markup) {
|
||||||
|
static const std::regex fence_no_lang("```(\\s*\\n)");
|
||||||
|
item.documentation = std::regex_replace(
|
||||||
|
documentation, fence_no_lang, "```" + editor->lang.name + "$1");
|
||||||
|
} else {
|
||||||
|
item.documentation = documentation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
editor->completion.box.render_update();
|
editor->completion.box.render_update();
|
||||||
};
|
};
|
||||||
json message = {{"jsonrpc", "2.0"},
|
json message = {{"jsonrpc", "2.0"},
|
||||||
@@ -332,7 +359,7 @@ void complete_accept(Editor *editor) {
|
|||||||
return;
|
return;
|
||||||
auto &item = editor->completion.items[editor->completion.select];
|
auto &item = editor->completion.items[editor->completion.select];
|
||||||
// TODO: support snippets here
|
// TODO: support snippets here
|
||||||
apply_lsp_edits(editor, item.edits);
|
apply_lsp_edits(editor, item.edits, true);
|
||||||
editor->completion.active = false;
|
editor->completion.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -283,21 +283,15 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
|
|||||||
void edit_replace(Editor *editor, Coord start, Coord end, const char *text,
|
void edit_replace(Editor *editor, Coord start, Coord end, const char *text,
|
||||||
uint32_t len) {
|
uint32_t len) {
|
||||||
std::shared_lock lock(editor->knot_mtx);
|
std::shared_lock lock(editor->knot_mtx);
|
||||||
uint32_t start_line_byte = line_to_byte(editor->root, start.row, nullptr);
|
uint32_t start_byte =
|
||||||
uint32_t end_len;
|
line_to_byte(editor->root, start.row, nullptr) + start.col;
|
||||||
uint32_t end_line_byte_start = line_to_byte(editor->root, end.row, &end_len);
|
uint32_t end_byte = line_to_byte(editor->root, end.row, nullptr) + end.col;
|
||||||
uint32_t end_line_byte = end_line_byte_start + len;
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
char *buf =
|
char *buf = read(editor->root, start_byte, end_byte - start_byte);
|
||||||
read(editor->root, start_line_byte, end_line_byte - start_line_byte);
|
|
||||||
if (!buf)
|
if (!buf)
|
||||||
return;
|
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 =
|
uint32_t erase_len =
|
||||||
count_clusters(buf, end_line_byte - start_line_byte, start_col,
|
count_clusters(buf, end_byte - start_byte, 0, end_byte - start_byte);
|
||||||
(end_line_byte_start - start_line_byte) + end_col);
|
|
||||||
free(buf);
|
free(buf);
|
||||||
if (erase_len != 0)
|
if (erase_len != 0)
|
||||||
edit_erase(editor, start, erase_len);
|
edit_erase(editor, start, erase_len);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "editor/editor.h"
|
#include "editor/editor.h"
|
||||||
|
#include "editor/decl.h"
|
||||||
#include "lsp/lsp.h"
|
#include "lsp/lsp.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
#include <shared_mutex>
|
||||||
|
|
||||||
Editor *new_editor(const char *filename_arg, Coord position, Coord size) {
|
Editor *new_editor(const char *filename_arg, Coord position, Coord size) {
|
||||||
Editor *editor = new Editor();
|
Editor *editor = new Editor();
|
||||||
@@ -66,15 +68,62 @@ void free_editor(Editor *editor) {
|
|||||||
void save_file(Editor *editor) {
|
void save_file(Editor *editor) {
|
||||||
if (!editor || !editor->root)
|
if (!editor || !editor->root)
|
||||||
return;
|
return;
|
||||||
|
std::shared_lock lock(editor->knot_mtx);
|
||||||
char *str = read(editor->root, 0, editor->root->char_count);
|
char *str = read(editor->root, 0, editor->root->char_count);
|
||||||
if (!str)
|
if (!str)
|
||||||
return;
|
return;
|
||||||
std::ofstream out(editor->filename);
|
std::ofstream out(editor->filename);
|
||||||
out.write(str, editor->root->char_count);
|
out.write(str, editor->root->char_count);
|
||||||
|
out.close();
|
||||||
free(str);
|
free(str);
|
||||||
json msg = {{"jsonrpc", "2.0"},
|
lock.unlock();
|
||||||
{"method", "textDocument/didSave"},
|
if (editor->lsp) {
|
||||||
{"params", {{"textDocument", {{"uri", editor->uri}}}}}};
|
json save_msg = {{"jsonrpc", "2.0"},
|
||||||
if (editor->lsp)
|
{"method", "textDocument/didSave"},
|
||||||
lsp_send(editor->lsp, msg, nullptr);
|
{"params", {{"textDocument", {{"uri", editor->uri}}}}}};
|
||||||
|
lsp_send(editor->lsp, save_msg, nullptr);
|
||||||
|
if (editor->lsp->allow_formatting) {
|
||||||
|
json msg = {{"jsonrpc", "2.0"},
|
||||||
|
{"method", "textDocument/formatting"},
|
||||||
|
{"params",
|
||||||
|
{{"textDocument", {{"uri", editor->uri}}},
|
||||||
|
{"options",
|
||||||
|
{{"tabSize", 2},
|
||||||
|
{"insertSpaces", true},
|
||||||
|
{"trimTrailingWhitespace", true},
|
||||||
|
{"trimFinalNewlines", true}}}}}};
|
||||||
|
LSPPending *pending = new LSPPending();
|
||||||
|
pending->editor = editor;
|
||||||
|
pending->method = "textDocument/formatting";
|
||||||
|
pending->callback = [save_msg](Editor *editor, std::string,
|
||||||
|
json message) {
|
||||||
|
auto &edits = message["result"];
|
||||||
|
if (edits.is_array()) {
|
||||||
|
std::vector<TextEdit> t_edits;
|
||||||
|
t_edits.reserve(edits.size());
|
||||||
|
for (auto &edit : edits) {
|
||||||
|
TextEdit t_edit;
|
||||||
|
t_edit.text = edit.value("newText", "");
|
||||||
|
t_edit.start.row = edit["range"]["start"]["line"];
|
||||||
|
t_edit.start.col = edit["range"]["start"]["character"];
|
||||||
|
t_edit.end.row = edit["range"]["end"]["line"];
|
||||||
|
t_edit.end.col = edit["range"]["end"]["character"];
|
||||||
|
utf8_normalize_edit(editor, &t_edit);
|
||||||
|
t_edits.push_back(t_edit);
|
||||||
|
}
|
||||||
|
apply_lsp_edits(editor, t_edits, false);
|
||||||
|
ensure_scroll(editor);
|
||||||
|
std::unique_lock lock(editor->knot_mtx);
|
||||||
|
char *str = read(editor->root, 0, editor->root->char_count);
|
||||||
|
if (!str)
|
||||||
|
return;
|
||||||
|
std::ofstream out(editor->filename);
|
||||||
|
out.write(str, editor->root->char_count);
|
||||||
|
free(str);
|
||||||
|
lsp_send(editor->lsp, save_msg, nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lsp_send(editor->lsp, msg, pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -612,9 +612,9 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
|||||||
mode = NORMAL;
|
mode = NORMAL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ensure_scroll(editor);
|
|
||||||
if (old_mode == mode || mode != INSERT)
|
if (old_mode == mode || mode != INSERT)
|
||||||
handle_completion(editor, event);
|
handle_completion(editor, event);
|
||||||
|
ensure_scroll(editor);
|
||||||
if ((event.key_type == KEY_CHAR || event.key_type == KEY_PASTE) && event.c)
|
if ((event.key_type == KEY_CHAR || event.key_type == KEY_PASTE) && event.c)
|
||||||
free(event.c);
|
free(event.c);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,37 @@
|
|||||||
|
#include "editor/decl.h"
|
||||||
#include "editor/editor.h"
|
#include "editor/editor.h"
|
||||||
|
|
||||||
void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits) {
|
void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits, bool move) {
|
||||||
|
if (!edits.size())
|
||||||
|
return;
|
||||||
|
TextEdit first = edits[0];
|
||||||
|
Coord cursor = editor->cursor;
|
||||||
std::sort(
|
std::sort(
|
||||||
edits.begin(), edits.end(),
|
edits.begin(), edits.end(),
|
||||||
[](const TextEdit &a, const TextEdit &b) { return a.start > b.start; });
|
[](const TextEdit &a, const TextEdit &b) { return a.start > b.start; });
|
||||||
for (const auto &edit : edits)
|
for (const auto &edit : edits)
|
||||||
edit_replace(editor, edit.start, edit.end, edit.text.c_str(),
|
edit_replace(editor, edit.start, edit.end, edit.text.c_str(),
|
||||||
edit.text.size());
|
edit.text.size());
|
||||||
editor->cursor = edits[0].start;
|
if (move) {
|
||||||
editor->cursor = move_right_pure(editor, editor->cursor,
|
std::shared_lock lock(editor->knot_mtx);
|
||||||
count_clusters(edits[0].text.c_str(),
|
editor->cursor = first.start;
|
||||||
edits[0].text.size(), 0,
|
editor->cursor =
|
||||||
edits[0].text.size()));
|
move_right_pure(editor, editor->cursor,
|
||||||
|
count_clusters(first.text.c_str(), first.text.size(), 0,
|
||||||
|
first.text.size()));
|
||||||
|
} else {
|
||||||
|
if (cursor.row >= editor->root->line_count) {
|
||||||
|
editor->cursor.row = editor->root->line_count - 1;
|
||||||
|
editor->cursor.col = 0;
|
||||||
|
} else {
|
||||||
|
std::shared_lock lock(editor->knot_mtx);
|
||||||
|
uint32_t len;
|
||||||
|
line_to_byte(editor->root, cursor.row, &len);
|
||||||
|
len--;
|
||||||
|
editor->cursor.row = cursor.row;
|
||||||
|
editor->cursor.col = cursor.col < len ? cursor.col : len;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void editor_lsp_handle(Editor *editor, json msg) {
|
void editor_lsp_handle(Editor *editor, json msg) {
|
||||||
|
|||||||
@@ -72,10 +72,35 @@ std::shared_ptr<LSPInstance> get_or_init_lsp(uint8_t lsp_id) {
|
|||||||
lsp->incremental_sync = (change_type == 2);
|
lsp->incremental_sync = (change_type == 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (caps.contains("hoverProvider"))
|
lsp->allow_formatting = caps.value("documentFormattingProvider", false);
|
||||||
lsp->allow_hover = caps["hoverProvider"].get<bool>();
|
if (caps.contains("documentOnTypeFormattingProvider")) {
|
||||||
else
|
auto &fmt = caps["documentOnTypeFormattingProvider"];
|
||||||
|
if (fmt.is_object()) {
|
||||||
|
if (fmt.contains("firstTriggerCharacter")) {
|
||||||
|
std::string s = fmt["firstTriggerCharacter"].get<std::string>();
|
||||||
|
if (s.size() == 1)
|
||||||
|
lsp->format_chars.push_back(s[0]);
|
||||||
|
}
|
||||||
|
if (fmt.contains("moreTriggerCharacter")) {
|
||||||
|
for (auto &c : fmt["moreTriggerCharacter"]) {
|
||||||
|
std::string s = c.get<std::string>();
|
||||||
|
if (s.size() == 1)
|
||||||
|
lsp->format_chars.push_back(s[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lsp->allow_formatting_on_type = true;
|
||||||
|
} else if (fmt.is_boolean()) {
|
||||||
|
lsp->allow_formatting_on_type = fmt.get<bool>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caps.contains("hoverProvider")) {
|
||||||
|
auto &hover = caps["hoverProvider"];
|
||||||
|
lsp->allow_hover =
|
||||||
|
hover.is_boolean() ? hover.get<bool>() : hover.is_object();
|
||||||
|
} 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"))
|
if (caps["completionProvider"].contains("resolveProvider"))
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ void background_lsp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void input_listener() {
|
void input_listener() {
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
KeyEvent event = throttle(1ms, read_key);
|
KeyEvent event = throttle(1ms, read_key);
|
||||||
if (event.key_type == KEY_NONE)
|
if (event.key_type == KEY_NONE)
|
||||||
@@ -130,3 +131,4 @@ int main(int argc, char *argv[]) {
|
|||||||
clear_regex_cache();
|
clear_regex_cache();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
src/ts/ts.cc
18
src/ts/ts.cc
@@ -90,15 +90,17 @@ void ts_collect_spans(Editor *editor) {
|
|||||||
ts_query_cursor_exec(cursor, q, ts_tree_root_node(item.tsset->tree));
|
ts_query_cursor_exec(cursor, q, ts_tree_root_node(item.tsset->tree));
|
||||||
std::unordered_map<std::string, PendingRanges> pending_injections;
|
std::unordered_map<std::string, PendingRanges> pending_injections;
|
||||||
TSQueryMatch match;
|
TSQueryMatch match;
|
||||||
|
auto subject_fn = [&](const TSNode *node, uint32_t *len) -> char * {
|
||||||
|
uint32_t start = ts_node_start_byte(*node);
|
||||||
|
uint32_t end = ts_node_end_byte(*node);
|
||||||
|
if (start == end || end > editor->root->char_count)
|
||||||
|
return nullptr;
|
||||||
|
std::shared_lock lock(editor->knot_mtx);
|
||||||
|
char *text = read(editor->root, start, end - start);
|
||||||
|
*len = end - start;
|
||||||
|
return text;
|
||||||
|
};
|
||||||
while (ts_query_cursor_next_match(cursor, &match)) {
|
while (ts_query_cursor_next_match(cursor, &match)) {
|
||||||
auto subject_fn = [&](const TSNode *node) -> std::string {
|
|
||||||
uint32_t start = ts_node_start_byte(*node);
|
|
||||||
uint32_t end = ts_node_end_byte(*node);
|
|
||||||
char *text = read(editor->root, start, end - start);
|
|
||||||
std::string final = std::string(text, end - start);
|
|
||||||
free(text);
|
|
||||||
return final;
|
|
||||||
};
|
|
||||||
if (!ts_predicate(q, match, subject_fn))
|
if (!ts_predicate(q, match, subject_fn))
|
||||||
continue;
|
continue;
|
||||||
for (uint32_t i = 0; i < match.capture_count; i++) {
|
for (uint32_t i = 0; i < match.capture_count; i++) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "io/sysio.h"
|
#include "io/sysio.h"
|
||||||
#include "ts/ts.h"
|
#include "ts/ts.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
std::unordered_map<std::string, pcre2_code *> regex_cache;
|
std::unordered_map<std::string, pcre2_code *> regex_cache;
|
||||||
|
|
||||||
@@ -114,8 +115,9 @@ TSQuery *load_query(const char *query_path, TSSetBase *set) {
|
|||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ts_predicate(TSQuery *query, const TSQueryMatch &match,
|
bool ts_predicate(
|
||||||
std::function<std::string(const TSNode *)> subject_fn) {
|
TSQuery *query, const TSQueryMatch &match,
|
||||||
|
std::function<char *(const TSNode *, uint32_t *len)> subject_fn) {
|
||||||
uint32_t step_count;
|
uint32_t step_count;
|
||||||
const TSQueryPredicateStep *steps =
|
const TSQueryPredicateStep *steps =
|
||||||
ts_query_predicates_for_pattern(query, match.pattern_index, &step_count);
|
ts_query_predicates_for_pattern(query, match.pattern_index, &step_count);
|
||||||
@@ -149,11 +151,14 @@ bool ts_predicate(TSQuery *query, const TSQueryMatch &match,
|
|||||||
}
|
}
|
||||||
const TSNode *node = find_capture_node(match, subject_id);
|
const TSNode *node = find_capture_node(match, subject_id);
|
||||||
pcre2_code *re = get_re(regex_txt);
|
pcre2_code *re = get_re(regex_txt);
|
||||||
std::string subject = subject_fn(node);
|
uint32_t len;
|
||||||
|
char *subject = subject_fn(node, &len);
|
||||||
|
if (!subject)
|
||||||
|
return false;
|
||||||
pcre2_match_data *md = pcre2_match_data_create_from_pattern(re, nullptr);
|
pcre2_match_data *md = pcre2_match_data_create_from_pattern(re, nullptr);
|
||||||
int rc = pcre2_match(re, (PCRE2_SPTR)subject.c_str(), subject.size(), 0, 0,
|
int rc = pcre2_match(re, (PCRE2_SPTR)subject, len, 0, 0, md, nullptr);
|
||||||
md, nullptr);
|
|
||||||
pcre2_match_data_free(md);
|
pcre2_match_data_free(md);
|
||||||
bool ok = (rc >= 0);
|
bool ok = (rc >= 0);
|
||||||
|
free(subject);
|
||||||
return (command == "match?" ? ok : !ok);
|
return (command == "match?" ? ok : !ok);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#include "ui/completionbox.h"
|
#include "ui/completionbox.h"
|
||||||
#include "editor/completions.h"
|
#include "editor/completions.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
std::string item_kind_name(uint8_t kind) {
|
std::string item_kind_name(uint8_t kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
@@ -76,11 +74,11 @@ void CompletionBox::render_update() {
|
|||||||
if (i >= session->items.size())
|
if (i >= session->items.size())
|
||||||
continue;
|
continue;
|
||||||
auto &item = session->items[i];
|
auto &item = session->items[i];
|
||||||
max_label_len = std::max(max_label_len, (uint32_t)item.label.size());
|
max_label_len = MAX(max_label_len, (uint32_t)item.label.size());
|
||||||
if (item.detail)
|
if (item.detail)
|
||||||
max_detail_len = std::max(max_detail_len, (uint32_t)item.detail->size());
|
max_detail_len = MAX(max_detail_len, (uint32_t)item.detail->size());
|
||||||
max_kind_len =
|
max_kind_len =
|
||||||
std::max(max_kind_len, (uint32_t)item_kind_name(item.kind).size());
|
MAX(max_kind_len, (uint32_t)item_kind_name(item.kind).size());
|
||||||
}
|
}
|
||||||
size.row = session->visible.size() + 2;
|
size.row = session->visible.size() + 2;
|
||||||
size.col = 2 + 2 + max_label_len + 1 + max_detail_len + 2 + max_kind_len + 1;
|
size.col = 2 + 2 + max_label_len + 1 + max_detail_len + 2 + max_kind_len + 1;
|
||||||
@@ -91,7 +89,7 @@ void CompletionBox::render_update() {
|
|||||||
cells[r * size.col + c] = {std::string(text), 0, fg, bg, flags, 0};
|
cells[r * size.col + c] = {std::string(text), 0, fg, bg, flags, 0};
|
||||||
};
|
};
|
||||||
uint32_t border_fg = 0x82AAFF;
|
uint32_t border_fg = 0x82AAFF;
|
||||||
uint32_t sel_bg = 0xFFFF00;
|
uint32_t sel_bg = 0x174225;
|
||||||
set(0, 0, "┌", border_fg, 0, 0);
|
set(0, 0, "┌", border_fg, 0, 0);
|
||||||
for (uint32_t c = 1; c < size.col - 1; c++)
|
for (uint32_t c = 1; c < size.col - 1; c++)
|
||||||
set(0, c, "─", border_fg, 0, 0);
|
set(0, c, "─", border_fg, 0, 0);
|
||||||
@@ -99,7 +97,7 @@ void CompletionBox::render_update() {
|
|||||||
for (uint32_t row_idx = 0; row_idx < session->visible.size(); row_idx++) {
|
for (uint32_t row_idx = 0; row_idx < session->visible.size(); row_idx++) {
|
||||||
uint32_t r = row_idx + 1;
|
uint32_t r = row_idx + 1;
|
||||||
auto &item = session->items[session->visible[row_idx]];
|
auto &item = session->items[session->visible[row_idx]];
|
||||||
uint32_t bg = (session->visible[row_idx] == session->select) ? sel_bg : 0;
|
uint32_t bg = (session->visible[row_idx] == session->select) ? sel_bg : 1;
|
||||||
uint32_t fg = 0xFFFFFF;
|
uint32_t fg = 0xFFFFFF;
|
||||||
set(r, 0, "│", border_fg, 0, 0);
|
set(r, 0, "│", border_fg, 0, 0);
|
||||||
uint32_t c = 1;
|
uint32_t c = 1;
|
||||||
@@ -134,7 +132,7 @@ void CompletionBox::render_update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CompletionBox::render(Coord pos) {
|
void CompletionBox::render(Coord pos) {
|
||||||
if (hidden)
|
if (hidden || session->visible.empty())
|
||||||
return;
|
return;
|
||||||
std::shared_lock lock(mtx);
|
std::shared_lock lock(mtx);
|
||||||
int32_t start_row = (int32_t)pos.row - (int32_t)size.row;
|
int32_t start_row = (int32_t)pos.row - (int32_t)size.row;
|
||||||
@@ -152,4 +150,23 @@ void CompletionBox::render(Coord pos) {
|
|||||||
update(start_row + r, start_col + c, cells[r * size.col + c].utf8,
|
update(start_row + r, start_col + c, cells[r * size.col + c].utf8,
|
||||||
cells[r * size.col + c].fg, cells[r * size.col + c].bg,
|
cells[r * size.col + c].fg, cells[r * size.col + c].bg,
|
||||||
cells[r * size.col + c].flags);
|
cells[r * size.col + c].flags);
|
||||||
|
if (session->items.size() > session->select &&
|
||||||
|
session->items[session->select].documentation &&
|
||||||
|
*session->items[session->select].documentation != "") {
|
||||||
|
if (session->doc != session->select) {
|
||||||
|
session->doc = session->select;
|
||||||
|
session->hover.clear();
|
||||||
|
session->hover.text = *session->items[session->select].documentation;
|
||||||
|
session->hover.is_markup = true;
|
||||||
|
session->hover.render_first();
|
||||||
|
} else {
|
||||||
|
if ((int32_t)position.col - (int32_t)session->hover.size.col > 0) {
|
||||||
|
session->hover.render({position.row + session->hover.size.row,
|
||||||
|
position.col - session->hover.size.col});
|
||||||
|
} else {
|
||||||
|
session->hover.render(
|
||||||
|
{position.row + session->hover.size.row, position.col + size.col});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,13 @@ void HoverBox::render_first(bool scroll) {
|
|||||||
TSQueryCursor *cursor = ts_query_cursor_new();
|
TSQueryCursor *cursor = ts_query_cursor_new();
|
||||||
ts_query_cursor_exec(cursor, ts.query, ts_tree_root_node(ts.tree));
|
ts_query_cursor_exec(cursor, ts.query, ts_tree_root_node(ts.tree));
|
||||||
TSQueryMatch match;
|
TSQueryMatch match;
|
||||||
|
auto subject_fn = [&](const TSNode *node, uint32_t *len) -> char * {
|
||||||
|
uint32_t start = ts_node_start_byte(*node);
|
||||||
|
uint32_t end = ts_node_end_byte(*node);
|
||||||
|
*len = end - start;
|
||||||
|
return text.data() + start;
|
||||||
|
};
|
||||||
while (ts_query_cursor_next_match(cursor, &match)) {
|
while (ts_query_cursor_next_match(cursor, &match)) {
|
||||||
auto subject_fn = [&](const TSNode *node) -> std::string {
|
|
||||||
uint32_t start = ts_node_start_byte(*node);
|
|
||||||
uint32_t end = ts_node_end_byte(*node);
|
|
||||||
return text.substr(start, end - start);
|
|
||||||
};
|
|
||||||
if (!ts_predicate(ts.query, match, subject_fn))
|
if (!ts_predicate(ts.query, match, subject_fn))
|
||||||
continue;
|
continue;
|
||||||
for (uint32_t i = 0; i < match.capture_count; i++) {
|
for (uint32_t i = 0; i < match.capture_count; i++) {
|
||||||
@@ -75,11 +76,6 @@ void HoverBox::render_first(bool scroll) {
|
|||||||
ts_tree_root_node(inj_ts.tree));
|
ts_tree_root_node(inj_ts.tree));
|
||||||
TSQueryMatch inj_match;
|
TSQueryMatch inj_match;
|
||||||
while (ts_query_cursor_next_match(inj_cursor, &inj_match)) {
|
while (ts_query_cursor_next_match(inj_cursor, &inj_match)) {
|
||||||
auto subject_fn = [&](const TSNode *node) -> std::string {
|
|
||||||
uint32_t start = ts_node_start_byte(*node);
|
|
||||||
uint32_t end = ts_node_end_byte(*node);
|
|
||||||
return text.substr(start, end - start);
|
|
||||||
};
|
|
||||||
if (!ts_predicate(inj_ts.query, inj_match, subject_fn))
|
if (!ts_predicate(inj_ts.query, inj_match, subject_fn))
|
||||||
continue;
|
continue;
|
||||||
for (uint32_t i = 0; i < inj_match.capture_count; i++) {
|
for (uint32_t i = 0; i < inj_match.capture_count; i++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user