import * as monaco from "monaco-editor" ; import { Observable , Observer } from "rxjs" ; import { first , map } from "rxjs/operators" ; import { childOf , JupyterMessage , ofMessageType , Channels , } from "@nteract/messaging" ; import { CompletionResults , CompletionMatch , completionRequest , js_idx_to_char_idx , } from "../editor-base" ; /**
 * Jupyter to Monaco completion item kinds.
 */ const unknownJupyterKind = "<unknown>" ; const jupyterToMonacoCompletionItemKind = { [ unknownJupyterKind ] : monaco . languages . CompletionItemKind . Field , class : monaco . languages . CompletionItemKind . Class , function : monaco . languages . CompletionItemKind . Function , keyword : monaco . languages . CompletionItemKind . Keyword , instance : monaco . languages . CompletionItemKind . Variable , statement : monaco . languages . CompletionItemKind . Variable , } ; /**
 * Completion item provider.
 */ class CompletionItemProvider implements monaco . languages . CompletionItemProvider { private channels : Channels | undefined ; param channels setChannels ( channels ) { this . channels = channels ; } /**
     * Whether provider is connected to Jupyter kernel.
     */ get isConnectedToKernel ( ) { return ! ! this . channels ; } /**
     * Additional characters to trigger completion other than Ctrl+Space.
     */ get triggerCharacters ( ) { return [ " " , "<" , "/" , "." , "=" ] ; } param model param position async provideCompletionItems ( model , position ) { let cursorPos = model . getOffsetAt ( position ) ; const code = model . getValue ( ) ; cursorPos = js_idx_to_char_idx ( cursorPos , code ) ; let items = [ ] ; if ( this . channels ) { try { const message = completionRequest ( code , cursorPos ) ; items = await this . codeCompleteObservable ( this . channels , message , model ) . toPromise ( ) ; } catch ( error ) { console . error ( error ) ; } } return Promise . resolve < monaco . languages . CompletionList > ( { suggestions : items , incomplete : false , } ) ; } param channels param message param model private codeCompleteObservable ( channels , message , model ) { const completion$ = channels . pipe ( childOf ( message ) , ofMessageType ( "complete_reply" ) , map ( ( entry ) => entry . content ) , first ( ) , map ( ( results ) => this . adaptToMonacoCompletions ( results , model ) ) ) ; return Observable . create ( ( observer ) => { const subscription = completion$ . subscribe ( observer ) ; channels . next ( message ) ; return subscription ; } ) ; } /**
     * Converts Jupyter completion result to list of Monaco completion items.
     */ private adaptToMonacoCompletions ( results , model ) { let range ; let percentCount = 0 ; let matches = results ? results . matches : [ ] ; if ( results . metadata && results . metadata . _jupyter_types_experimental ) { matches = results . metadata . _jupyter_types_experimental ; } const startPos = model . getPositionAt ( results . cursor_start ) ; const endPos = model . getPositionAt ( results . cursor_end ) ; const context = model . getValueInRange ( { startLineNumber : startPos . lineNumber , startColumn : startPos . column , endLineNumber : endPos . lineNumber , endColumn : endPos . column } ) ; return matches . map ( ( match , index ) => { if ( typeof match === "string" ) { const text = this . sanitizeText ( match , context ) ; const inserted = this . getInsertText ( text , context ) ; const filtered = this . getFilterText ( text , context ) ; return { kind : this . adaptToMonacoCompletionItemKind ( unknownJupyterKind ) , label : text , insertText : inserted , filterText : filtered , sortText : this . getSortText ( index ) , } as monaco . languages . CompletionItem ; } else { if ( ! range ) { const start = model . getPositionAt ( match . start ) ; const end = model . getPositionAt ( match . end ) ; range = { startLineNumber : start . lineNumber , startColumn : start . column , endLineNumber : end . lineNumber , endColumn : end . column , } ; const rangeText = model . getValueInRange ( range ) ; if ( rangeText . startsWith ( "%%" ) ) { percentCount = 2 ; } else if ( rangeText . startsWith ( "%" ) ) { percentCount = 1 ; } } const text = this . sanitizeText ( match . text , context ) ; const filtered = this . getFilterText ( text , context ) ; const inserted = this . getInsertText ( text , context , percentCount ) ; return { kind : this . adaptToMonacoCompletionItemKind ( match . type ) , label : text , insertText : inserted , filterText : filtered , sortText : this . getSortText ( index ) , } as monaco . languages . CompletionItem ; } } ) ; } param kind private adaptToMonacoCompletionItemKind ( kind ) { const result = jupyterToMonacoCompletionItemKind [ kind ] ; return result ? result : jupyterToMonacoCompletionItemKind [ unknownJupyterKind ] ; } param text private sanitizeText ( text , context ) { const isPathCompletion = context . includes ( "/" ) ; if ( isPathCompletion ) { const completionIsPathWithWhitespace = text . startsWith ( '"' ) && text . endsWith ( '"' ) && text . length > 2 ; if ( completionIsPathWithWhitespace && text . substr ( 1 ) . startsWith ( context ) ) { const toRemove = context . substr ( 0 , context . lastIndexOf ( "/" ) + 1 ) ; return `"${ text . substr ( toRemove . length + 1 ) }` ; } if ( text . startsWith ( context ) ) { const toRemove = context . substr ( 0 , context . lastIndexOf ( "/" ) + 1 ) ; return text . substr ( toRemove . length ) ; } } const isMemberCompletion = context . endsWith ( "." ) ; if ( isMemberCompletion && text . startsWith ( context ) ) { const toRemove = context . substr ( 0 , context . lastIndexOf ( "." ) + 1 ) ; return text . substr ( toRemove . length ) ; } const index = text . lastIndexOf ( "." ) ; if ( index > - 1 && index < text . length - 1 ) { return text . substring ( index + 1 ) ; } return text ; } param text private getFilterText ( text , context ) { const isPathCompletion = context . includes ( "/" ) ; if ( isPathCompletion ) { const completionIsPathWithWhitespace = text . startsWith ( '"' ) && text . endsWith ( '"' ) && text . length > 2 ; if ( completionIsPathWithWhitespace && text . substr ( 1 ) . startsWith ( context ) ) { return text . substr ( 1 , text . length - 1 ) ; } } return text . replace ( /%/g , "" ) ; } param text param percentCount private getInsertText ( text , context , percentCount = 0 ) { const isPathCompletion = context . includes ( "/" ) ; const isPathWithPeriodInName = isPathCompletion && text . includes ( "." ) && context . includes ( "." ) ; if ( isPathWithPeriodInName ) { const completionContext = context . substr ( context . lastIndexOf ( "/" ) + 1 ) ; if ( text . startsWith ( completionContext ) ) { return text . substr ( completionContext . lastIndexOf ( "." ) + 1 ) ; } } for ( let i = 0 ; i < percentCount ; i ++ ) { text = text . replace ( "%" , "" ) ; } return text ; } param order returns private getSortText ( order ) { order ++ ; const numCharacters = 26 ; const div = Math . floor ( order / numCharacters ) ; let sortText = "z" ; for ( let i = 0 ; i < div ; i ++ ) { sortText += "z" ; } const remainder = order % numCharacters ; if ( remainder > 0 ) { sortText += String . fromCharCode ( 96 + remainder ) ; } return sortText ; } } const completionProvider = new CompletionItemProvider ( ) ; export { completionProvider } ;