@adecore/editor-react 0.17.0 → 0.18.0

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.
@@ -4,6 +4,7 @@ import type { CompletionDocs } from './popups.ts';
4
4
  import { type SnippetStop } from './snippet.ts';
5
5
  export declare function isIdentifierCharacter(character: string): boolean;
6
6
  export declare function identifierPrefix(before: string): string;
7
+ export declare function opensCompletion(before: string, text: string): boolean;
7
8
  export interface MatchDetail {
8
9
  readonly score: number;
9
10
  readonly positions: readonly number[];
@@ -21,6 +21,19 @@ export function identifierPrefix(before) {
21
21
  lastPrefix = characters.slice(start).join('');
22
22
  return lastPrefix;
23
23
  }
24
+ const OPENS_VALUE = /(?:[-=!<>&|?+*/%^:,]|(?:^|[^\p{L}\p{N}_$])(?:return|new|case|in|of|instanceof|typeof|keyof|await|yield|throw|extends|implements|as|is|not|and|or|echo|print|use|import|from|clone))$/u;
25
+ const NOT_A_VALUE = /(?:\+\+|--|\/\/|\/\*\*?|^\s*[-+*])$/;
26
+ /*
27
+ * Whether `text`, typed after `before` on its line, puts the caret where a value or a name comes next, so the list
28
+ * opens without a word typed: an opening bracket, or a space after an operator, a comma or a keyword. A comment
29
+ * opener, a list marker and `++` or `--` open nothing.
30
+ */
31
+ export function opensCompletion(before, text) {
32
+ if (text === '(' || text === '()' || text === '[' || text === '[]') {
33
+ return true;
34
+ }
35
+ return text === ' ' && OPENS_VALUE.test(before) && !NOT_A_VALUE.test(before);
36
+ }
24
37
  function run(start, length) {
25
38
  return Array.from({ length }, (_, offset) => start + offset);
26
39
  }
@@ -1,6 +1,6 @@
1
1
  import { StaleResultError } from '@adecore/lsp';
2
2
  import { comparePositions, shiftPosition } from './diagnostics-model.js';
3
- import { completionDocsOf, identifierPrefix, insertionOf, isIdentifierCharacter, itemsOf, matchedCharacters, mirroredInsertions, prefixFor, qualifiersOf, rankCompletions } from './completion-model.js';
3
+ import { completionDocsOf, identifierPrefix, insertionOf, isIdentifierCharacter, itemsOf, matchedCharacters, mirroredInsertions, opensCompletion, prefixFor, qualifiersOf, rankCompletions } from './completion-model.js';
4
4
  import { PARAMETER_HINTS_COMMAND, isCallItem, planCall, withParentheses } from './completion-call.js';
5
5
  import { RecentChoices, recentChoicesOf } from './recent-choices.js';
6
6
  import { shikiLanguageOf } from './language-ids.js';
@@ -281,6 +281,11 @@ export class CompletionFeature {
281
281
  this.close();
282
282
  this.beginRequest({ triggerKind: 2, triggerCharacter: last }, 0);
283
283
  }
284
+ else if (isEmpty(range) &&
285
+ opensCompletion(this.language.editor.textInRange({ start: { line: range.start.line, character: 0 }, end: range.start }), text)) {
286
+ this.close();
287
+ this.beginRequest({ triggerKind: 1 }, START_DELAY_MS);
288
+ }
284
289
  else if (this.isOpen) {
285
290
  if (text === '' || typedIdentifier) {
286
291
  this.refilter();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/editor-react",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "description": "Language-enabled editors, feature coordination and React popups over injected language services.",
6
6
  "license": "FSL-1.1-MIT",
@@ -57,9 +57,9 @@
57
57
  "test": "bun test --conditions=source"
58
58
  },
59
59
  "dependencies": {
60
- "@adecore/editor": "0.17.0",
61
- "@adecore/lsp": "0.17.0",
62
- "@adecore/merge": "0.17.0",
60
+ "@adecore/editor": "0.18.0",
61
+ "@adecore/lsp": "0.18.0",
62
+ "@adecore/merge": "0.18.0",
63
63
  "zustand": "^5.0.15",
64
64
  "lucide-react": "^1.47.0",
65
65
  "react-markdown": "^10.1.0",
@@ -67,7 +67,7 @@
67
67
  "remark-breaks": "^4.0.0"
68
68
  },
69
69
  "peerDependencies": {
70
- "@adecore/ui": "^0.17.0",
70
+ "@adecore/ui": "^0.18.0",
71
71
  "react": "^19.3.0",
72
72
  "react-dom": "^19.3.0",
73
73
  "i18next": "^26.4.2",
@@ -29,6 +29,22 @@ export function identifierPrefix(before: string): string {
29
29
  return lastPrefix;
30
30
  }
31
31
 
32
+ const OPENS_VALUE =
33
+ /(?:[-=!<>&|?+*/%^:,]|(?:^|[^\p{L}\p{N}_$])(?:return|new|case|in|of|instanceof|typeof|keyof|await|yield|throw|extends|implements|as|is|not|and|or|echo|print|use|import|from|clone))$/u;
34
+ const NOT_A_VALUE = /(?:\+\+|--|\/\/|\/\*\*?|^\s*[-+*])$/;
35
+
36
+ /*
37
+ * Whether `text`, typed after `before` on its line, puts the caret where a value or a name comes next, so the list
38
+ * opens without a word typed: an opening bracket, or a space after an operator, a comma or a keyword. A comment
39
+ * opener, a list marker and `++` or `--` open nothing.
40
+ */
41
+ export function opensCompletion(before: string, text: string): boolean {
42
+ if (text === '(' || text === '()' || text === '[' || text === '[]') {
43
+ return true;
44
+ }
45
+ return text === ' ' && OPENS_VALUE.test(before) && !NOT_A_VALUE.test(before);
46
+ }
47
+
32
48
  export interface MatchDetail {
33
49
  /* Lower is better. */
34
50
  readonly score: number;
package/src/completion.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  itemsOf,
11
11
  matchedCharacters,
12
12
  mirroredInsertions,
13
+ opensCompletion,
13
14
  prefixFor,
14
15
  qualifiersOf,
15
16
  rankCompletions,
@@ -312,6 +313,12 @@ export class CompletionFeature {
312
313
  if (text.length > 0 && triggers.includes(last) && text.length === 1) {
313
314
  this.close();
314
315
  this.beginRequest({ triggerKind: 2, triggerCharacter: last }, 0);
316
+ } else if (
317
+ isEmpty(range) &&
318
+ opensCompletion(this.language.editor.textInRange({ start: { line: range.start.line, character: 0 }, end: range.start }), text)
319
+ ) {
320
+ this.close();
321
+ this.beginRequest({ triggerKind: 1 }, START_DELAY_MS);
315
322
  } else if (this.isOpen) {
316
323
  if (text === '' || typedIdentifier) {
317
324
  this.refilter();