UNPKG

@krassowski/jupyterlab_go_to_definition

Version:

Jump to definition of a variable or function in JupyterLab

105 lines (88 loc) 2.64 kB
import { CodeEditor } from '@jupyterlab/codeeditor'; import { LanguageWithOptionalSemicolons, TokenContext } from './analyzer'; export class RAnalyzer extends LanguageWithOptionalSemicolons { definitionRules = [ this.isStandaloneAssignment, this.isImport, this.isForLoop ]; isAssignment(token: CodeEditor.IToken): boolean { return ( (token.type === 'operator' && token.value === '=') || (token.type === 'operator arrow' && (token.value === '<-' || token.value === '<<-')) ); } // Matching standalone variable assignment: isStandaloneAssignment(context: TokenContext) { let { previous, next } = context; return ( // standard, leftwards assignments: (next.exists && this.isAssignment(next)) || // rightwards assignments: (previous.exists && previous.type === 'operator arrow' && (previous.value === '->' || previous.value === '->>')) ); } // Matching imports: isImport(context: TokenContext) { let { previous } = context; if ( previous.exists && previous.type === 'variable' && (previous.value === 'library' || previous.value === 'require') ) { return true; } previous = this.traverse_left(previous, ', '); if ( previous.exists && previous.value === 'here' && previous.type === 'variable' && previous.previous.value === '::' && previous.previous.previous.value === 'import' ) { return true; } return false; } // Matching `for` loop and comprehensions: isForLoop(context: TokenContext) { let { previous, next } = context; return ( previous.exists && previous.type === 'keyword' && previous.value === 'for' && next.exists && next.type === 'keyword' && next.value === 'in' ); } guessReferencePath(context: TokenContext) { let { next } = context; if (context.value === 'source') { return [next.next.value.slice(0, -1)]; } // TODO for now only works when alt-clicking on ".from" // ideally clicking on the file name would be preferred return [next.next.next.value.slice(0, -1)]; } isCrossFileReference(context: TokenContext): boolean { // case: "source('test.R')" - clicking on source will open test.R let { next } = context; if ( context.type === 'variable' && context.value === 'source' && next.exists && (next.value === "'" || next.value === '"') && next.next.exists ) { return true; } if (this.isImport(context.previous)) { return true; } return false; } }