aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lsp/server.zc28
-rw-r--r--tests/drivers/completion_test.js13
2 files changed, 36 insertions, 5 deletions
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index bc2c967..65137a6 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -3180,6 +3180,16 @@ int gen_completion() {
}
ws("]}}"); return 0;
}
+// a group such as File in std.File.write, offered once as a namespace unless its own symbol (a struct) is listed.
+int cp_group(char* nm, int from, int to) {
+ int k = 0; char* g = cp_copy(nm);
+ g[to] = 0;
+ if (run.lookup(g) >= 0) return 0;
+ while (k < cp_n) { if (rt.eq_w(cp_name[k], g + from)) return 0; k++; }
+ hn = 0; hov_text("namespace "); hov_text(g);
+ cp_add(g + from, 9, 0, 2);
+ return 0;
+}
int do_completion(int i) {
int s, k, j, old, n, head, c; word member, fields; char* nm; char* owner = 0;
cp_i = i; cp_n = 0;
@@ -3190,20 +3200,30 @@ int do_completion(int i) {
n = cp_cursor - cp_start; cp_prefix = lsp_grow(0, 0, n + 1);
k = 0; while (k < n) { cp_prefix[k] = dc(doff[i], cp_start + k); k++; }
if (cp_start > 0 && dc(doff[i], cp_start - 1) == '.') {
+ // the owner is the whole dotted path before the last dot (std or std.File); its first part must be a module.
head = cp_start - 1;
- while (head > 0 && hid(dc(doff[i], head - 1))) head--;
+ while (head > 0 && (hid(dc(doff[i], head - 1)) || (dc(doff[i], head - 1) == '.' && head > 1 && hid(dc(doff[i], head - 2))))) head--;
n = cp_start - 1 - head; owner = m*lsp_grow(0, 0, n + 1);
k = 0; while (k < n) { owner[k] = dc(doff[i], head + k); k++; } owner[n] = 0;
- if (!run.module_known(owner)) owner = 0;
+ k = 0; while (owner[k] && owner[k] != '.') k++;
+ c = owner[k]; owner[k] = 0;
+ if (!run.module_known(owner)) owner = 0; else owner[k] = c;
}
// the full check supplies functions declared later; the probe supplies locals and their current lifetimes.
ensure_checked(i);
if (owner) {
- if (run.module_imported(owner)) {
+ // one level at a time: owner.name is listed, owner.group.name only as its group.
+ k = 0; while (owner[k] && owner[k] != '.') k++;
+ c = owner[k]; owner[k] = 0; j = run.module_imported(owner); owner[k] = c;
+ if (j) {
n = rt.len_w(owner); s = 0;
while (s < run.nsym) {
nm = run.sname[s]; k = 0; while (k < n && nm[k] == owner[k]) k++;
- if (k == n && nm[k] == '.' && run.skind[s] != run.K_DOC && run.lookup(nm) == s) cp_symbol(s, nm + n + 1);
+ if (k == n && nm[k] == '.' && nm[k + 1] && run.skind[s] != run.K_DOC && run.lookup(nm) == s) {
+ j = k + 1; while (nm[j] && nm[j] != '.') j++;
+ if (!nm[j]) cp_symbol(s, nm + n + 1);
+ else cp_group(nm, n + 1, j);
+ }
s++;
}
}
diff --git a/tests/drivers/completion_test.js b/tests/drivers/completion_test.js
index f1ee5e6..4b767a1 100644
--- a/tests/drivers/completion_test.js
+++ b/tests/drivers/completion_test.js
@@ -104,4 +104,15 @@ assert.match(get(r.items, 'map').documentation.value, /transform.*each element/)
assert.ok(!r.items.some(c => ['push', 'pop', 'resize'].includes(c.label)), 'library algorithms do not make a fixed array growable');
r = complete('include rt; fn main() { int[?] items = {}; items.| }');
assert.ok(!r.items.some(c => ['map', 'filter'].includes(c.label)), 'library methods require an explicit import');
-console.log('ok completion: signatures, docs, fuzzy ranking, scope, array members, shadowing and consumed lifetimes');
+r = complete('include std; fn main() { std.| }');
+for (const name of ['File', 'Io', 'Text', 'Host', 'argv', 'bnew']) get(r.items, name);
+assert.ok(!r.items.some(c => c.label.includes('.')), 'module completion lists one level: ' + r.items.filter(c => c.label.includes('.')).map(c => c.label));
+r = complete('include std; fn main() { std.File.| }');
+for (const name of ['read', 'write']) get(r.items, name);
+assert.ok(!r.items.some(c => ['Io', 'File', 'main', 'int'].includes(c.label)), 'group completion lists only its members');
+r = complete('include std; fn main() { std.Io.| }');
+for (const name of ['print', 'println', 'put_n', 'put_f', 'getline']) get(r.items, name);
+assert.ok(!r.items.some(c => ['write', 'itoa', 'main', 'int'].includes(c.label)), 'std.Io. lists only Io members');
+r = complete('include std; fn main() { std.Io.pr| }');
+assert.deepStrictEqual(r.items.map(c => c.label).sort(), ['print', 'println']);
+console.log('ok completion: signatures, docs, fuzzy ranking, scope, array members, shadowing, consumed lifetimes and one level of a module path');