-
Notifications
You must be signed in to change notification settings - Fork 44
Fix autocomplete suggesting erratically & add more suggestions #866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
krishnangovindraj
wants to merge
36
commits into
typedb:development
Choose a base branch
from
krishnangovindraj:update-typeql-autocomplete
base: development
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,714
−1,092
Open
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
057c08a
Merge branch 'development'
alexjpwalker 2458fb1
Merge branch 'development'
alexjpwalker ea03618
Merge branch 'development'
alexjpwalker 845388a
Merge branch 'development'
alexjpwalker 4c83b95
Merge branch 'development'
alexjpwalker 3618375
Merge branch 'development'
alexjpwalker 0165af9
Release 3.3.0 (#864)
alexjpwalker 8cfe668
Merge branch 'development'
alexjpwalker da83ee8
Merge branch 'development'
alexjpwalker 5ad6ef6
Merge branch 'development'
alexjpwalker 7d600c5
Introduce schema tree view on Query page
alexjpwalker a880ed6
Merge branch 'development' into schema-tree
alexjpwalker d75f7c4
Enrich schema tree view with owns, plays and relates links
alexjpwalker 0a2ea88
Represent schema as a map of labels to concepts. Standardise terminology
alexjpwalker 9cd9551
Clear schema tool window on unloading schema
alexjpwalker 1db29e8
Clean up comments
alexjpwalker 7ff002a
Typo squatting
alexjpwalker 55b4533
Schema tool window scrolling behaviour
alexjpwalker 6947f73
Schema tool window: add top border to scrollable area when scrolled
alexjpwalker 9b9b565
Update autocomplete
krishnangovindraj 3f120a7
Cleanup some comments
krishnangovindraj 6b802e4
Eeps. Why can't i select autocomplete stuff
krishnangovindraj 9e735df
OOf, works somewhat ok
krishnangovindraj 16edf3d
Rearrange
krishnangovindraj 1255bd3
More clenaups, esp with navigation helpers
krishnangovindraj ba9299f
Suggestions for has and links constraints are nicely scoped
krishnangovindraj 3ce4391
Fix boost in variables not being used
krishnangovindraj 4269c36
Minor refactors
krishnangovindraj 9107094
Add TODO to remove window.OC_lastQueryAnswers
krishnangovindraj e456b05
Retry CI
krishnangovindraj 4477aba
Post rebase working in
krishnangovindraj fa6b1d7
Update deriving partial schema from text editor state
krishnangovindraj e292510
Plug in schema from schema state to autocomplete
krishnangovindraj 463cefe
Small cleanup of todos and unused imports
krishnangovindraj c9990bb
Move lezer to the appropriate line in devDependencies
krishnangovindraj 7982e8c
Update names for rebase
krishnangovindraj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
|
||
import { CompletionContext, Completion, CompletionResult } from "@codemirror/autocomplete"; | ||
import { syntaxTree } from "@codemirror/language" | ||
import { SyntaxNode, NodeType, Tree } from "@lezer/common" | ||
|
||
type TokenID = number; | ||
export interface SuggestionMap<STATE> { | ||
[key: TokenID]: SuffixOfPrefixSuggestion<STATE>[] | ||
} | ||
|
||
|
||
export type SuffixCandidate = TokenID[]; // A SuffixCandidate 's' "matches" a prefix if prefix[-s.length:] == s | ||
export interface SuffixOfPrefixSuggestion<STATE> { | ||
suffixes: SuffixCandidate[], // If any of the suffix candidates match, the suggestions will be used. | ||
suggestions: SuggestionFunction<STATE>[] | ||
} | ||
|
||
export type SuggestionFunction<STATE> = (context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode, prefix: NodeType[], state: STATE) => Completion[] | null; | ||
|
||
export function suggest(type: string, label: string, boost: number = 0): Completion { | ||
// type (docs): used to pick an icon to show for the completion. Icons are styled with a CSS class created by appending the type name to "cm-completionIcon-". | ||
return { | ||
label: label, | ||
type: type, | ||
apply: label, | ||
info: type, | ||
boost: boost, | ||
}; | ||
} | ||
|
||
interface NodePrefixAutoCompleteState { | ||
mayUpdateFromEditorState(context: CompletionContext, tree: Tree): void; | ||
} | ||
|
||
// See: https://codemirror.net/examples/autocompletion/ and maybe the SQL / HTML Example there. | ||
export class NodePrefixAutoComplete<STATE extends NodePrefixAutoCompleteState> { | ||
suggestionMap: SuggestionMap<STATE>; | ||
suggestorState: STATE; | ||
|
||
constructor(suggestionMap: SuggestionMap<STATE>, suggestorState: STATE) { | ||
// This is where we would set up the autocompletion, but we do it in the index.ts file. | ||
// See: https://codemirror.net/docs/ref/#autocomplete.autocompletion | ||
this.suggestionMap = suggestionMap; | ||
this.suggestorState = suggestorState; | ||
} | ||
|
||
getState(): STATE { | ||
return this.suggestorState; | ||
} | ||
|
||
autocomplete(context: CompletionContext): CompletionResult | null { | ||
let tree: Tree = syntaxTree(context.state); | ||
this.suggestorState.mayUpdateFromEditorState(context, tree); | ||
let currentNode: SyntaxNode = tree.resolveInner(context.pos, -1); // https://lezer.codemirror.net/docs/ref/#common.SyntaxNode | ||
let options = this.getSuggestions(context, tree, currentNode); | ||
if (options != null) { | ||
// And once we figure out, we have to create a list of completion objects | ||
// It may be worth changing the grammar to be able to do this more easily, rather than replicate the original TypeQL grammar. | ||
// https://codemirror.net/docs/ref/#autocomplete.Completion | ||
let from = findStartOfCompletion(context) + 1; | ||
return { | ||
from: from, | ||
options: options, | ||
// Docs: "regular expression that tells the extension that, as long as the updated input (the range between the result's from property and the completion point) matches that value, it can continue to use the list of completions." | ||
validFor: /^([\w\$]+)?$/ | ||
} | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
getSuggestions(context: CompletionContext, tree: Tree, parseAt: SyntaxNode): Completion[] | null { | ||
return this.climbTillWeRecogniseSomething(context, tree, parseAt, parseAt, collectPrecedingChildrenOf(context, parseAt)); | ||
} | ||
|
||
|
||
climbTillWeRecogniseSomething(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode | null, prefix: NodeType[]): Completion[] | null { | ||
if (climbedTo == null) { | ||
// Uncomment this if you don't see suggestions | ||
// this.logInterestingStuff(context, tree, parseAt, climbedTo, prefix); | ||
return null; | ||
} | ||
let suggestionEither = this.suggestionMap[climbedTo.type.id]; | ||
if (suggestionEither != null) { | ||
for (let sops of (suggestionEither as SuffixOfPrefixSuggestion<STATE>[])) { | ||
if (prefixHasAnyOfSuffixes(prefix, sops.suffixes)) { | ||
return this.combineSuggestions(context, tree, parseAt, climbedTo, prefix, sops.suggestions); | ||
} | ||
} | ||
// None match? Fall through. | ||
// console.log("Fell through!!!: ", climbedTo.type.name, "with prefix", prefix); | ||
} | ||
let newPrefix = collectSiblingsOf(climbedTo).concat(prefix); | ||
return this.climbTillWeRecogniseSomething(context, tree, parseAt, climbedTo.parent, newPrefix); | ||
} | ||
|
||
|
||
combineSuggestions(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode, prefix: NodeType[], suggestionFunctions: SuggestionFunction<STATE>[]): Completion[] { | ||
let suggestions = suggestionFunctions.map((f) => { | ||
return f(context, tree, parseAt, climbedTo, prefix, this.suggestorState); | ||
}).reduce((acc, curr) => { | ||
return (curr == null) ? acc : acc!.concat(curr); | ||
}, []); | ||
// console.log("Matched:", climbedTo.type.name, "with prefix", prefix, ". Suggestions:", suggestions); | ||
return suggestions!; | ||
} | ||
|
||
logInterestingStuff(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode | null, prefix: NodeType[]) { | ||
console.log("Current Node:", parseAt.name); | ||
console.log("ClimbedTo Node:", climbedTo?.name); | ||
|
||
let at: SyntaxNode | null = parseAt; | ||
let climbThrough = []; | ||
while (at != null && at.name != climbedTo?.name) { | ||
climbThrough.push(at.name); | ||
at = at.parent; | ||
} | ||
climbThrough.push(at?.name); | ||
console.log("Climbed through", climbThrough); | ||
console.log("Prefix:", prefix); | ||
} | ||
} | ||
|
||
function isPartOfWord(s: string): boolean { | ||
let matches = s.match(/^[A-Za-z0-9_\-\$]+/); | ||
return matches != null && matches.length > 0; | ||
} | ||
|
||
function findStartOfCompletion(context: CompletionContext): number { | ||
let str = context.state.doc.sliceString(0, context.pos); | ||
let at = context.pos - 1; | ||
while (at >= 0 && isPartOfWord(str.charAt(at))) { | ||
at -= 1; | ||
} | ||
return at; | ||
} | ||
|
||
function collectSiblingsOf(node: SyntaxNode): NodeType[] { | ||
let siblings = []; | ||
let prev: SyntaxNode | null = node; | ||
while (null != (prev = prev.prevSibling)) { | ||
siblings.push(prev.type); | ||
} | ||
return siblings.reverse(); | ||
} | ||
|
||
function collectPrecedingChildrenOf(context: CompletionContext, node: SyntaxNode): NodeType[] { | ||
let lastChild = node.childBefore(context.pos); | ||
if (lastChild == null) { | ||
return []; | ||
} | ||
let precedingChildren = collectSiblingsOf(lastChild); | ||
precedingChildren.push(lastChild.type); | ||
return precedingChildren; | ||
} | ||
|
||
function prefixHasAnyOfSuffixes(prefix: NodeType[], suffixes: SuffixCandidate[]): boolean { | ||
for (let i = 0; i < suffixes.length; i++) { | ||
if (prefixHasSuffix(prefix, suffixes[i])) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
function prefixHasSuffix(prefix: NodeType[], suffix: TokenID[]): boolean { | ||
if (prefix.length < suffix.length) { | ||
return false; | ||
} | ||
for (let i = 0; i < suffix.length; i++) { | ||
if (prefix[prefix.length - suffix.length + i].id != suffix[i]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to run this step every time we update the
typeql.grammar
file. I should probably add a comment in that file.