diff --git a/packages/graphql-language-service/src/interface/getAutocompleteSuggestions.ts b/packages/graphql-language-service/src/interface/getAutocompleteSuggestions.ts index 5c01896a22d..bbfe010afe6 100644 --- a/packages/graphql-language-service/src/interface/getAutocompleteSuggestions.ts +++ b/packages/graphql-language-service/src/interface/getAutocompleteSuggestions.ts @@ -66,6 +66,9 @@ import { RuleKinds, RuleKind, ContextTokenForCodeMirror, + ParserOptions, + LexRules, + ParseRules, } from '../parser'; import { @@ -968,18 +971,70 @@ export function getTokenAtPosition( let styleAtCursor = null; let stateAtCursor = null; let stringAtCursor = null; - const token = runOnlineParser(queryText, (stream, state, style, index) => { - if ( - index !== cursor.line || - stream.getCurrentPosition() + offset < cursor.character + 1 - ) { - return; + + /** Returns whether a token is before, at or after the cursor */ + function compareTokenPositionToCursor( + line: number, + col: number, + ): 'before' | 'at' | 'after' { + if (line < cursor.line) { + return 'before'; } - styleAtCursor = style; - stateAtCursor = { ...state }; - stringAtCursor = stream.current(); - return 'BREAK'; - }); + if (line > cursor.line) { + return 'after'; + } + + if (col < cursor.character) { + return 'before'; + } + if (col > cursor.character) { + return 'after'; + } + return 'at'; + } + + let lastString: string | null = null; + let lastState: State | null = null; + let lastStyle: string | null = null; + const token = runOnlineParser( + queryText, + (stream, state, style, index) => { + const compare = compareTokenPositionToCursor( + index, + stream.getCurrentPosition() + offset, + ); + + if (compare === 'before') { + // The token is before the cursor, keep track of it + lastState = state; + lastStyle = style; + lastString = stream.current(); + return; + } + + // The token is at or after the cursor, we return the last token before the cursor + if ( + index !== cursor.line || + stream.getCurrentPosition() + offset < cursor.character + 1 + ) { + return; + } + + styleAtCursor = lastStyle; + stateAtCursor = { ...lastState }; + stringAtCursor = lastString; + return 'BREAK'; + }, + { + // Don't swallow whitespace to have the token positions match that of the + // original query text, so we can compare them with the cursor position. + eatWhitespace: () => false, + // TODO should ParserOptions have optional fields instead? + lexRules: LexRules, + parseRules: ParseRules, + editorConfig: {}, + }, + ); // Return the state/style of parsed token in case those at cursor aren't // available. @@ -1009,9 +1064,10 @@ type callbackFnType = ( export function runOnlineParser( queryText: string, callback: callbackFnType, + options?: ParserOptions, ): ContextToken { const lines = queryText.split('\n'); - const parser = onlineParser(); + const parser = onlineParser(options); let state = parser.startState(); let style = ''; diff --git a/yarn.lock b/yarn.lock index 96831d6a942..4fbe8bd8fac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6050,6 +6050,34 @@ loupe "^2.3.6" pretty-format "^27.5.1" +"@vscode/vsce@^2.19.0", "@vscode/vsce@^2.23.0": + version "2.24.0" + resolved "https://registry.yarnpkg.com/@vscode/vsce/-/vsce-2.24.0.tgz#7f835b9fdd5bfedcecd62a6c4d684841a74974d4" + integrity sha512-p6CIXpH5HXDqmUkgFXvIKTjZpZxy/uDx4d/UsfhS9vQUun43KDNUbYeZocyAHgqcJlPEurgArHz9te1PPiqPyA== + dependencies: + azure-devops-node-api "^11.0.1" + chalk "^2.4.2" + cheerio "^1.0.0-rc.9" + commander "^6.2.1" + glob "^7.0.6" + hosted-git-info "^4.0.2" + jsonc-parser "^3.2.0" + leven "^3.1.0" + markdown-it "^12.3.2" + mime "^1.3.4" + minimatch "^3.0.3" + parse-semver "^1.1.1" + read "^1.0.7" + semver "^7.5.2" + tmp "^0.2.1" + typed-rest-client "^1.8.4" + url-join "^4.0.1" + xml2js "^0.5.0" + yauzl "^2.3.1" + yazl "^2.2.2" + optionalDependencies: + keytar "^7.7.0" + "@vscode/vsce@^2.22.1-2": version "2.22.1-2" resolved "https://registry.yarnpkg.com/@vscode/vsce/-/vsce-2.22.1-2.tgz#0f272f97b23986366ea97e29721cb28410b9af68" @@ -10698,6 +10726,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== +follow-redirects@^1.13.2: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + follow-redirects@^1.14.6: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" @@ -15287,15 +15320,28 @@ outdent@^0.5.0: resolved "https://registry.yarnpkg.com/outdent/-/outdent-0.5.0.tgz#9e10982fdc41492bb473ad13840d22f9655be2ff" integrity sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q== -ovsx@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/ovsx/-/ovsx-0.5.1.tgz#3a8c2707ea120d542d1d226a47f24ac47b078710" - integrity sha512-3OWq0l7DuVHi2bd2aQe5+QVQlFIqvrcw3/2vGXL404L6Tr+R4QHtzfnYYghv8CCa85xJHjU0RhcaC7pyXkAUbg== +ovsx@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/ovsx/-/ovsx-0.8.3.tgz#3c67a595e423f3f70a3d62da3735dd07dfbca69f" + integrity sha512-LG7wTzy4eYV/KolFeO4AwWPzQSARvCONzd5oHQlNvYOlji2r/zjbdK8pyObZN84uZlk6rQBWrJrAdJfh/SX0Hg== dependencies: + "@vscode/vsce" "^2.19.0" commander "^6.1.0" follow-redirects "^1.14.6" is-ci "^2.0.0" leven "^3.1.0" + semver "^7.5.2" + tmp "^0.2.1" + +ovsx@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/ovsx/-/ovsx-0.3.0.tgz#2f30c80c90fbe3c8fc406730c35371219187ca0a" + integrity sha512-UjZjzLt6Iq7LS/XFvEuBAWyn0zmsZEe8fuy5DsbcsIb0mW7PbVtB5Dhe4HeK7NJM228nyhYXC9WCeyBoMi4M3A== + dependencies: + commander "^6.1.0" + follow-redirects "^1.13.2" + is-ci "^2.0.0" + leven "^3.1.0" tmp "^0.2.1" vsce "^2.6.3"