import { CancellationToken , MarkupContent , MarkupKind } from 'vscode-languageserver' ; import { convertDocStringToMarkdown , convertDocStringToPlainText } from '../analyzer/docStringConversion' ; import { extractParameterDocumentation } from '../analyzer/docStringUtils' ; import * as ParseTreeUtils from '../analyzer/parseTreeUtils' ; import { getCallNodeAndActiveParameterIndex } from '../analyzer/parseTreeUtils' ; import { SourceMapper } from '../analyzer/sourceMapper' ; import { CallSignature , TypeEvaluator } from '../analyzer/typeEvaluatorTypes' ; import { throwIfCancellationRequested } from '../common/cancellationUtils' ; import { convertPositionToOffset } from '../common/positionUtils' ; import { Position } from '../common/textRange' ; import { CallNode , NameNode , ParseNodeType } from '../parser/parseNodes' ; import { ParseResults } from '../parser/parser' ; import { getDocumentationPartsForTypeAndDecl , getFunctionDocStringFromType } from './tooltipUtils' ; export interface ParamInfo { startOffset : number ; endOffset : number ; text : string ; documentation ? : string | undefined ; } export interface SignatureInfo { label : string ; documentation ? : MarkupContent | undefined ; parameters ? : ParamInfo [ ] | undefined ; activeParameter ? : number | undefined ; } export interface SignatureHelpResults { signatures : SignatureInfo [ ] ; callHasParameters : boolean ; } export class SignatureHelpProvider { static getSignatureHelpForPosition ( parseResults , position , sourceMapper , evaluator , format , token ) { throwIfCancellationRequested ( token ) ; const offset = convertPositionToOffset ( position , parseResults . tokenizerOutput . lines ) ; if ( offset === undefined ) { return undefined ; } let node = ParseTreeUtils . findNodeByOffset ( parseResults . parseTree , offset ) ; const initialNode = node ; const initialDepth = node ? ParseTreeUtils . getNodeDepth ( node ) : 0 ; let curOffset = offset - 1 ; while ( curOffset >= 0 ) { if ( parseResults . text . substr ( curOffset , 1 ) === ',' ) { break ; } const curNode = ParseTreeUtils . findNodeByOffset ( parseResults . parseTree , curOffset ) ; if ( curNode && curNode !== initialNode ) { if ( ParseTreeUtils . getNodeDepth ( curNode ) > initialDepth ) { node = curNode ; } break ; } curOffset -- ; } if ( node === undefined ) { return undefined ; } const callInfo = getCallNodeAndActiveParameterIndex ( node , offset , parseResults . tokenizerOutput . tokens ) ; if ( ! callInfo ) { return ; } const callSignatureInfo = evaluator . getCallSignatureInfo ( callInfo . callNode , callInfo . activeIndex , callInfo . activeOrFake ) ; if ( ! callSignatureInfo ) { return undefined ; } const signatures = callSignatureInfo . signatures . map ( ( sig ) => this . _makeSignature ( callSignatureInfo . callNode , sig , sourceMapper , evaluator , format ) ) ; const callHasParameters = ! ! callSignatureInfo . callNode . arguments ?. length ; return { signatures , callHasParameters , } ; } private static _makeSignature ( callNode , signature , sourceMapper , evaluator , format ) { const functionType = signature . type ; const stringParts = evaluator . printFunctionParts ( functionType ) ; const parameters = [ ] ; const functionDocString = getFunctionDocStringFromType ( functionType , sourceMapper , evaluator ) ?? this . _getDocStringFromCallNode ( callNode , sourceMapper , evaluator ) ; let label = '(' ; const params = functionType . details . parameters ; stringParts [ 0 ] . forEach ( ( paramString , paramIndex ) => { let paramName = '' ; if ( paramIndex < params . length ) { paramName = params [ paramIndex ] . name || '' ; } else if ( params . length > 0 ) { paramName = params [ params . length - 1 ] . name || '' ; } parameters . push ( { startOffset : label . length , endOffset : label . length + paramString . length , text : paramString , documentation : extractParameterDocumentation ( functionDocString || '' , paramName ) , } ) ; label += paramString ; if ( paramIndex < stringParts [ 0 ] . length - 1 ) { label += ', ' ; } } ) ; label += ') -> ' + stringParts [ 1 ] ; let activeParameter ; if ( signature . activeParam ) { activeParameter = params . indexOf ( signature . activeParam ) ; if ( activeParameter === - 1 ) { activeParameter = undefined ; } } const sigInfo = { label , parameters , activeParameter , } ; if ( functionDocString ) { if ( format === MarkupKind . Markdown ) { sigInfo . documentation = { kind : MarkupKind . Markdown , value : convertDocStringToMarkdown ( functionDocString ) , } ; } else { sigInfo . documentation = { kind : MarkupKind . PlainText , value : convertDocStringToPlainText ( functionDocString ) , } ; } } return sigInfo ; } private static _getDocStringFromCallNode ( callNode , sourceMapper , evaluator ) { let name ; const expr = callNode . leftExpression ; if ( expr . nodeType === ParseNodeType . Name ) { name = expr ; } else if ( expr . nodeType === ParseNodeType . MemberAccess ) { name = expr . memberName ; } if ( ! name ) { return undefined ; } for ( const decl of evaluator . getDeclarationsForNameNode ( name ) ?? [ ] ) { const resolveDecl = evaluator . resolveAliasDeclaration ( decl , true ) ; if ( ! resolveDecl ) { continue ; } const type = evaluator . getType ( name ) ; if ( ! type ) { continue ; } const parts = getDocumentationPartsForTypeAndDecl ( sourceMapper , type , resolveDecl , evaluator ) ; if ( parts . length > 0 ) { return parts . join ( '\n\n' ) ; } } return undefined ; } }