UNPKG

@krassowski/jupyterlab_go_to_definition

Version:

Jump to definition of a variable or function in JupyterLab

209 lines 7.51 kB
export class TokenContext { constructor(token, tokens, index) { this.token = token; this.index = index; this.tokens = tokens; } get simple_next() { return this.index + 1 < this.tokens.length ? this.tokens[this.index + 1].value : null; } get simple_previous() { return this.index - 1 >= 0 ? this.tokens[this.index - 1].value : null; } get previous() { if (!this._previous) { let { token, index } = _closestMeaningfulTokenWithIndex(this.index, this.tokens, -1); // TODO: null!! this._previous = new TokenContext(token, this.tokens, index); } return this._previous; } get next() { if (!this._next) { let { token, index } = _closestMeaningfulTokenWithIndex(this.index, this.tokens, +1); this._next = new TokenContext(token, this.tokens, index); } return this._next; } get value() { return this.token ? this.token.value : undefined; } get type() { return this.token.type; } get offset() { return this.token.offset; } get exists() { return !!this.token; } } export class LanguageAnalyzer { /** * Name of a variable for which a definition is sought */ constructor(tokensProvider) { this.supportsKernel = false; this.tokensProvider = tokensProvider; } isCrossFileReference(context) { // identify Python import, and R source(), library(), require() return false; } guessReferencePath(context) { // more than one guess allowed return ['']; } referencePathQuery(context) { return ''; } definitionLocationQuery(context) { return ''; } _maybe_setup_tokens() { if (!this.tokens) { this.tokens = this.tokensProvider.getTokens(); } } _get_token_index(token) { this._maybe_setup_tokens(); return this.tokens.findIndex(t => t.value === token.value && t.offset === token.offset && t.type === token.type); } isDefinition(token, i) { if (token.type === 'variable') { let token_context = new TokenContext(token, this.tokens, i); let isVariableDefinition; for (isVariableDefinition of this.definitionRules) { if (isVariableDefinition.bind(this)(token_context)) { return true; } } // nothing matched return false; } else if (token.type === 'def') { // Matching function definition. // We could double-check that an opening parenthesis follows, // but we can assume that it is the responsibility of CodeMirror. return true; } else { // nothing matched return false; } } nameMatches(name, token) { return token.value === name; } getDefinitions(variable) { this.tokens = this.tokensProvider.getTokens(); return Array.from(this.tokens).filter((token, i) => this.nameMatches(variable, token) && this.isDefinition(token, i)); } isAssignment(token) { return token.type === 'operator' && token.value === '='; } /** * Check whether testedToken belongs to same assignment expression as originToken * * #### Notes * To verify if token belongs to same assignment expression, the tokens * between testedToken and originToken as tested. The token is in same * assignment expression if there is an assignment token in between and * there are no expression-terminating tokens after such an assignment. * * We only need to look at the first assignment token, see this example: * a = 1; b = a */ isTokenInSameAssignmentExpression(testedToken, originToken) { // Find tokens between token.offset and otherToken.offset. let tokensSet = new Set(); for (let offset = testedToken.offset + 1; offset < originToken.offset + 1; offset++) { let token = this.tokensProvider.getTokenAt(offset); if (token.offset === testedToken.offset) { continue; } tokensSet.add(token); } let tokensBetween = Array.from(tokensSet); // If there is no assignment token, we don't need to worry let assignments = tokensBetween.filter(this.isAssignment); if (!assignments.length) { return false; } let firstAssignment = assignments.sort((a, b) => a.offset > b.offset ? 1 : -1)[0]; // Select terminating tokens: let terminatingTokens = this._selectTerminatingTokens(tokensBetween); let terminatorsAfterAssignment = terminatingTokens.filter(token => token.offset > firstAssignment.offset); if (!terminatorsAfterAssignment.length) { return true; } return false; } } export class LanguageWithOptionalSemicolons extends LanguageAnalyzer { _selectTerminatingTokens(tokens) { // terminating tokens are: // semicolons and new lines (which are locally outside of brackets) let terminatingTokens = []; let openedBrackets = 0; const openingBrackets = '([{'; const closingBrackets = ')]}'; for (let token of tokens) { // let's assume that the code is properly formatted, // and do not check the order of brackets if (token.value !== '') { if (openingBrackets.includes(token.value)) { openedBrackets += 1; continue; } else if (closingBrackets.includes(token.value)) { openedBrackets -= 1; continue; } } // empty string is used as a token representing new-line let terminator = token.value === ';' || token.value === ''; // if there is a closing bracket, completing a previously opened one, // which proceeds the token of origin, that is fine too (hence <= 0) if (openedBrackets <= 0 && terminator) { terminatingTokens.push(token); } } return terminatingTokens; } traverse_left(previous, step_on) { let is_sep = step_on.includes(previous.simple_next); while (is_sep && previous.exists) { previous = previous.previous; is_sep = step_on.includes(previous.simple_next); } return previous; } traverse_right(next, step_on) { let is_sep = step_on.includes(next.simple_previous); while (is_sep && next.exists) { next = next.next; is_sep = step_on.includes(next.simple_previous); } return next; } } export function _closestMeaningfulTokenWithIndex(tokenIndex, tokens, direction) { let nextMeaningfulToken = null; while (nextMeaningfulToken == null) { tokenIndex += direction; if (tokenIndex < 0 || tokenIndex >= tokens.length) { return { index: null, token: null }; } let nextToken = tokens[tokenIndex]; if (nextToken.type !== '') { nextMeaningfulToken = nextToken; } } return { index: tokenIndex, token: nextMeaningfulToken }; } //# sourceMappingURL=analyzer.js.map