@venn-lang/lsp 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venn-lang/lsp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "The Venn language server: diagnostics, hover, completion, definition, references, rename, signature help, semantic tokens, formatting, document highlight and quick fixes for .vn files.",
5
5
  "keywords": [
6
6
  "venn",
@@ -44,12 +44,12 @@
44
44
  "langium": "^4.3.1",
45
45
  "vscode-languageserver": "^10.0.1",
46
46
  "vscode-languageserver-textdocument": "^1.0.13",
47
- "@venn-lang/contracts": "0.1.1",
48
- "@venn-lang/core": "0.1.1",
49
- "@venn-lang/runtime": "0.1.1",
50
- "@venn-lang/sdk": "0.1.1",
51
- "@venn-lang/stdlib": "0.1.1",
52
- "@venn-lang/types": "0.1.1"
47
+ "@venn-lang/contracts": "0.1.2",
48
+ "@venn-lang/core": "0.1.2",
49
+ "@venn-lang/runtime": "0.1.2",
50
+ "@venn-lang/sdk": "0.1.2",
51
+ "@venn-lang/stdlib": "0.1.2",
52
+ "@venn-lang/types": "0.1.2"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^24",
@@ -14,7 +14,6 @@ const ACTION = /(?<![\w.])([A-Za-z_]\w*(?:\.\w+)*)\.(\w*)$/;
14
14
  const ANNOTATION = /@(\w*)$/;
15
15
  const FRAGMENT = /\brun\s+(\w*)$/;
16
16
  const MATCHER = /\bexpect\b\s+\S+\s+(\w*)$/;
17
- const WORD = /(\w*)$/;
18
17
  /**
19
18
  * Where a type goes: after the `:` of a field, a parameter or a binding, after
20
19
  * the `->` of a declared return, or after the `|` of a union.
@@ -30,7 +29,7 @@ const TYPE_POSITION = /(?:^|[^:])(?::|->|\|)\s*([A-Za-z_][\w.]*)?$/;
30
29
  * `http.on api ▮`, `print ▮`. The leading anchor keeps this to the head of a
31
30
  * statement, so `1 + ▮` and the tail of an expression are not swept in.
32
31
  */
33
- const ARGUMENT = /^\s*([A-Za-z_]\w*(?:\.\w+)*)\s+[^{}]*?(\w*)$/;
32
+ const ARGUMENT = /^\s*([A-Za-z_]\w*(?:\.\w+)*)\s/;
34
33
  const FROM_PATH = /\bfrom\s+"([^"]+)"/;
35
34
  /**
36
35
  * A dot whose receiver no path can name: `1234.567.round`, `f(x).len`,
@@ -68,7 +67,7 @@ export function contextAt(text: CursorText): CompletionContext {
68
67
  return (
69
68
  optionContext(text) ??
70
69
  typeContext(text) ??
71
- argumentContext(prefix) ?? { kind: "statement", from: back(prefix, WORD.exec(prefix)?.[1]) }
70
+ argumentContext(prefix) ?? { kind: "statement", from: back(prefix, trailingWord(prefix)) }
72
71
  );
73
72
  }
74
73
 
@@ -86,9 +85,13 @@ function typeContext(text: CursorText): CompletionContext | undefined {
86
85
 
87
86
  /** `http.on ▮`: the head of the line is a verb, and an argument is due. */
88
87
  function argumentContext(prefix: string): CompletionContext | undefined {
88
+ // A brace anywhere means this is a map being written, not an argument. Asked
89
+ // outright rather than folded into the pattern as a trailing `[^{}]*$`: that
90
+ // made the pattern describe the whole line to say something about its head.
91
+ if (prefix.includes("{") || prefix.includes("}")) return undefined;
89
92
  const found = ARGUMENT.exec(prefix);
90
93
  if (!found?.[1]) return undefined;
91
- return { kind: "argument", target: found[1], from: back(prefix, found[2]) };
94
+ return { kind: "argument", target: found[1], from: back(prefix, trailingWord(prefix)) };
92
95
  }
93
96
 
94
97
  /**
@@ -102,7 +105,7 @@ function optionContext(text: CursorText): CompletionContext | undefined {
102
105
  if (open < 0) return undefined;
103
106
  const target = OPTION_OWNER.exec(text.before.slice(0, open))?.[1];
104
107
  if (!target) return undefined;
105
- return { kind: "optionKey", target, from: back(text.prefix, WORD.exec(text.prefix)?.[1]) };
108
+ return { kind: "optionKey", target, from: back(text.prefix, trailingWord(text.prefix)) };
106
109
  }
107
110
 
108
111
  /** The `{` still open at the cursor, scanning backwards. */
@@ -142,3 +145,27 @@ function pathOf(line: string): string | undefined {
142
145
  function back(prefix: string, partial: string | undefined): number {
143
146
  return prefix.length - (partial?.length ?? 0);
144
147
  }
148
+
149
+ /**
150
+ * The word being typed at the end of `text`, empty when the last character is
151
+ * not part of one.
152
+ *
153
+ * Scanned backwards rather than matched with `/(\w*)$/`. That pattern has
154
+ * nothing to anchor to, so the engine tries it at every position, which is
155
+ * quadratic in the length of the line: 27ms at ten thousand characters and
156
+ * 454ms at forty thousand, while a completion is meant to be instant. A line is
157
+ * as long as whoever wrote the file made it.
158
+ */
159
+ function trailingWord(text: string): string {
160
+ let at = text.length;
161
+ while (at > 0 && isWordCharacter(text.charCodeAt(at - 1))) at -= 1;
162
+ return text.slice(at);
163
+ }
164
+
165
+ /** `\w`: a digit, a letter or an underscore. */
166
+ function isWordCharacter(code: number): boolean {
167
+ if (code >= 48 && code <= 57) return true;
168
+ if (code >= 65 && code <= 90) return true;
169
+ if (code >= 97 && code <= 122) return true;
170
+ return code === 95;
171
+ }