1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
|
#include "completion.h"
#include <stddef.h>
#include "dged/s8.h"
#include "dged/vec.h"
#include "types.h"
#include "dged/buffer.h"
#include "dged/buffer_view.h"
#include "dged/minibuffer.h"
#include "main/completion.h"
#include "main/lsp.h"
struct completion_ctx {
struct lsp_server *server;
struct completion_context comp_ctx;
struct completion_list completions;
struct s8 cached_with;
struct completion *completion_data;
uint64_t last_request;
triggerchar_vec trigger_chars;
};
struct symbol {
struct s8 symbol;
struct region region;
};
static struct symbol current_symbol(struct buffer *buffer, struct location at) {
struct region word = buffer_word_at(buffer, at);
if (!region_has_size(word)) {
return (struct symbol){
.symbol =
(struct s8){
.s = NULL,
.l = 0,
},
.region = word,
};
};
struct text_chunk line = buffer_region(buffer, region_new(word.begin, at));
struct s8 symbol = s8new((const char *)line.text, line.nbytes);
if (line.allocated) {
free(line.text);
}
return (struct symbol){.symbol = symbol, .region = word};
}
struct completion_ctx *create_completion_ctx(struct lsp_server *server,
triggerchar_vec *trigger_chars) {
struct completion_ctx *ctx =
(struct completion_ctx *)calloc(1, sizeof(struct completion_ctx));
ctx->server = server;
ctx->completion_data = NULL;
ctx->completions.incomplete = false;
ctx->cached_with.s = NULL;
ctx->cached_with.l = 0;
VEC_INIT(&ctx->completions.items, 0);
VEC_INIT(&ctx->trigger_chars, VEC_SIZE(trigger_chars));
VEC_FOR_EACH(trigger_chars, struct s8 * s) {
VEC_PUSH(&ctx->trigger_chars, s8dup(*s));
}
return ctx;
}
void destroy_completion_ctx(struct completion_ctx *ctx) {
completion_list_free(&ctx->completions);
if (ctx->completion_data != NULL) {
free(ctx->completion_data);
}
s8delete(ctx->cached_with);
VEC_FOR_EACH(&ctx->trigger_chars, struct s8 * s) { s8delete(*s); }
VEC_DESTROY(&ctx->trigger_chars);
free(ctx);
}
static char *item_kind_to_str(enum completion_item_kind kind) {
switch (kind) {
case CompletionItem_Text:
return "tx";
case CompletionItem_Method:
return "mth";
case CompletionItem_Function:
return "fn";
case CompletionItem_Constructor:
return "cons";
case CompletionItem_Field:
return "field";
case CompletionItem_Variable:
return "var";
case CompletionItem_Class:
return "cls";
case CompletionItem_Interface:
return "iface";
case CompletionItem_Module:
return "mod";
case CompletionItem_Property:
return "prop";
case CompletionItem_Unit:
return "unit";
case CompletionItem_Value:
return "val";
case CompletionItem_Enum:
return "enum";
case CompletionItem_Keyword:
return "kw";
case CompletionItem_Snippet:
return "snp";
case CompletionItem_Color:
return "col";
case CompletionItem_File:
return "file";
case CompletionItem_Reference:
return "ref";
case CompletionItem_Folder:
return "fld";
case CompletionItem_EnumMember:
return "em";
case CompletionItem_Constant:
return "const";
case CompletionItem_Struct:
return "struct";
case CompletionItem_Event:
return "ev";
case CompletionItem_Operator:
return "op";
case CompletionItem_TypeParameter:
return "tp";
default:
return "";
}
}
static struct region lsp_item_render(void *data, struct buffer *buffer) {
struct lsp_completion_item *item = (struct lsp_completion_item *)data;
struct location begin = buffer_end(buffer);
struct s8 kind_str = s8from_fmt("(%s)", item_kind_to_str(item->kind));
struct s8 txt = s8from_fmt("%-8.*s%.*s", kind_str.l, kind_str.s,
item->label.l, item->label.s);
struct location end = buffer_add(buffer, begin, txt.s, txt.l);
s8delete(txt);
s8delete(kind_str);
buffer_newline(buffer, buffer_end(buffer));
return region_new(begin, end);
}
static void lsp_item_selected(void *data, struct buffer_view *view) {
struct lsp_completion_item *item = (struct lsp_completion_item *)data;
struct buffer *buffer = view->buffer;
struct lsp_server *lsp_server = lsp_server_for_buffer(buffer);
abort_completion();
if (lsp_server == NULL) {
return;
}
switch (item->edit_type) {
case TextEdit_None: {
struct symbol symbol = current_symbol(buffer, view->dot);
struct s8 insert = item->insert_text;
// FIXME: why does this happen?
if (symbol.symbol.l >= insert.l) {
s8delete(symbol.symbol);
return;
}
if (symbol.symbol.l > 0) {
insert.s += symbol.symbol.l;
insert.l -= symbol.symbol.l;
}
s8delete(symbol.symbol);
buffer_push_undo_boundary(buffer);
struct location at = buffer_add(buffer, view->dot, insert.s, insert.l);
buffer_view_goto(view, at);
} break;
case TextEdit_TextEdit: {
struct text_edit *ed = &item->edit.text_edit;
struct region reg = lsp_range_to_coordinates(lsp_server, buffer, ed->range);
struct location at = reg.begin;
if (!region_is_inside(reg, view->dot)) {
reg.end = view->dot;
}
if (region_has_size(reg)) {
at = buffer_delete(buffer, reg);
}
buffer_push_undo_boundary(buffer);
at = buffer_add(buffer, at, ed->new_text.s, ed->new_text.l);
buffer_view_goto(view, at);
} break;
case TextEdit_InsertReplaceEdit: {
struct insert_replace_edit *ed = &item->edit.insert_replace_edit;
struct region reg =
lsp_range_to_coordinates(lsp_server, buffer, ed->replace);
if (!region_is_inside(reg, view->dot)) {
reg.end = view->dot;
}
if (region_has_size(reg)) {
buffer_delete(buffer, reg);
}
buffer_push_undo_boundary(buffer);
struct location at =
buffer_add(buffer, ed->insert.begin, ed->new_text.s, ed->new_text.l);
buffer_view_goto(view, at);
} break;
}
if (!VEC_EMPTY(&item->additional_text_edits)) {
apply_edits_buffer(lsp_server, view->buffer, item->additional_text_edits,
&view->dot);
}
}
static void lsp_item_cleanup(void *data) { (void)data; }
static struct s8 get_filter_text(struct lsp_completion_item *item) {
return item->filter_text.l > 0 ? item->filter_text : item->label;
}
static void fill_completions(struct completion_ctx *lsp_ctx, struct s8 needle) {
if (lsp_ctx->completion_data != NULL) {
free(lsp_ctx->completion_data);
lsp_ctx->completion_data = NULL;
}
size_t ncomps = VEC_SIZE(&lsp_ctx->completions.items);
// if there is more than a single item or the user has not typed that
// single item exactly, then add to the list of completions.
lsp_ctx->completion_data = calloc(ncomps, sizeof(struct completion));
ncomps = 0;
VEC_FOR_EACH(&lsp_ctx->completions.items,
struct lsp_completion_item * lsp_item) {
struct s8 filter_text = get_filter_text(lsp_item);
if (needle.l == 0 || s8startswith(filter_text, needle)) {
struct completion *c = &lsp_ctx->completion_data[ncomps];
c->data = lsp_item;
c->render = lsp_item_render;
c->selected = lsp_item_selected;
c->cleanup = lsp_item_cleanup;
++ncomps;
}
}
// if there is only a single item that matches the needle exactly,
// don't add it to the list since the user has already won
if (ncomps == 1 && needle.l > 0 &&
s8eq(get_filter_text(lsp_ctx->completion_data[0].data), needle)) {
return;
}
if (ncomps > 0) {
lsp_ctx->comp_ctx.add_completions(lsp_ctx->completion_data, ncomps);
}
}
static void handle_completion_response(struct lsp_server *server,
struct lsp_response *response,
void *userdata) {
(void)server;
struct completion_ctx *lsp_ctx = (struct completion_ctx *)userdata;
if (response->id != lsp_ctx->last_request) {
// discard any old requests
return;
}
completion_list_free(&lsp_ctx->completions);
lsp_ctx->completions = completion_list_from_json(&response->value.result);
fill_completions(lsp_ctx, lsp_ctx->cached_with);
}
static void complete_with_lsp(struct completion_context ctx, bool deletion,
void *userdata) {
(void)deletion;
struct completion_ctx *lsp_ctx = (struct completion_ctx *)userdata;
lsp_ctx->comp_ctx = ctx;
struct symbol sym = current_symbol(ctx.buffer, ctx.location);
struct s8 symbol = sym.symbol;
// check if the symbol is too short for triggering completion
bool should_activate =
(symbol.l >= 3 || completion_active()) && !s8onlyws(symbol);
// use trigger chars as an alternative activation condition
if (!should_activate) {
struct location begin = buffer_previous_char(ctx.buffer, ctx.location);
struct location end = begin;
end.col += 4;
struct text_chunk txt = buffer_region(ctx.buffer, region_new(begin, end));
struct s8 t = {
.s = txt.text,
.l = txt.nbytes,
};
VEC_FOR_EACH(&lsp_ctx->trigger_chars, struct s8 * tc) {
if (s8startswith(t, *tc)) {
should_activate = true;
goto done;
}
}
done:
if (txt.allocated) {
free(txt.text);
}
}
// if we still should not activate, we give up
if (!should_activate) {
s8delete(symbol);
return;
}
bool has_completions = !VEC_EMPTY(&lsp_ctx->completions.items);
if (completion_active() && has_completions &&
!lsp_ctx->completions.incomplete && !s8empty(lsp_ctx->cached_with) &&
s8startswith(symbol, lsp_ctx->cached_with)) {
fill_completions(lsp_ctx, symbol);
} else {
uint64_t id = new_pending_request(lsp_ctx->server,
handle_completion_response, lsp_ctx);
lsp_ctx->last_request = id;
struct versioned_text_document_identifier doc =
versioned_identifier_from_buffer(ctx.buffer);
struct text_document_position params = {
.uri = doc.uri,
.position = ctx.location,
};
s8delete(lsp_ctx->cached_with);
lsp_ctx->cached_with = s8dup(symbol);
struct s8 json_payload = document_position_to_json(¶ms);
lsp_send(
lsp_backend(lsp_ctx->server),
lsp_create_request(id, s8("textDocument/completion"), json_payload));
versioned_text_document_identifier_free(&doc);
s8delete(json_payload);
}
s8delete(symbol);
}
void enable_completion_for_buffer(struct completion_ctx *ctx,
struct buffer *buffer) {
struct completion_provider prov = {
.name = "lsp",
.complete = complete_with_lsp,
.userdata = ctx,
};
struct completion_provider providers[] = {prov};
add_completion_providers(buffer, providers, 1);
}
|