1 module served.commands.calltips; 2 3 import served.extension; 4 import served.types; 5 import served.utils.ddoc; 6 7 import workspaced.api; 8 import workspaced.com.dcd; 9 import workspaced.com.dcdext; 10 import workspaced.coms; 11 12 import std.algorithm : max; 13 import std.experimental.logger; 14 import std.json; 15 import std..string : strip, stripRight; 16 17 /** 18 * Convert DCD calltips to LSP compatible `SignatureHelp` objects 19 * Params: 20 * calltips = Ddoc strings for each available calltip 21 * symbols = array of possible signatures as DCD symbols 22 * textTilCursor = The entire contents of the file being edited up 23 * until the cursor 24 */ 25 SignatureHelp convertDCDCalltips(DCDExtComponent dcdext, string[] calltips, 26 DCDCompletions.Symbol[] symbols, CalltipsSupport extractedParams) 27 { 28 SignatureInformation[] signatures; 29 int[] paramsCounts; // Number of params for each calltip 30 SignatureHelp help; 31 32 foreach (i, calltip; calltips) 33 { 34 if (!calltip.length) 35 continue; 36 37 auto sig = SignatureInformation(calltip); 38 immutable DCDCompletions.Symbol symbol = symbols[i]; 39 const docs = parseDdoc(symbol.documentation); 40 if (docs != Comment.init) 41 sig.documentation = MarkupContent(docs.ddocToMarked); 42 else if (symbol.documentation.length) 43 sig.documentation = MarkupContent(symbol.documentation); 44 45 CalltipsSupport funcParams = dcdext.extractCallParameters(calltip, 46 cast(int) calltip.length - 1, true); 47 if (funcParams != CalltipsSupport.init) 48 { 49 if (extractedParams.inTemplateParameters && !funcParams.hasTemplate) 50 continue; 51 52 auto args = extractedParams.inTemplateParameters 53 ? funcParams.templateArgs : funcParams.functionArgs; 54 if (args.length && args[$ - 1].variadic) 55 paramsCounts ~= int.max; 56 else 57 paramsCounts ~= cast(int) args.length; 58 59 ParameterInformation[] retParams; 60 foreach (param; args) 61 { 62 int[2] range = param.nameRange[1] == 0 ? param.contentRange : param.nameRange; 63 string paramName = calltip[range[0] .. range[1]]; 64 JSONValue paramLabel; 65 if (capabilities.textDocument.signatureHelp.supportsLabelOffset) 66 paramLabel = JSONValue([ 67 JSONValue(cast(int) calltip[0 .. param.contentRange[0]].countUTF16Length), 68 JSONValue(cast(int) calltip[0 .. param.contentRange[1]].countUTF16Length) 69 ]); 70 else 71 paramLabel = JSONValue(calltip[param.contentRange[0] .. param.contentRange[1]]); 72 Optional!MarkupContent paramDocs; 73 if (docs != Comment.init) 74 { 75 auto docString = getParamDocumentation(docs, paramName); 76 if (docString.length) 77 { 78 Optional!(string[]) formats = capabilities.textDocument 79 .signatureHelp.signatureInformation.documentationFormat; 80 MarkupKind kind = formats.isNull || formats.length == 0 81 ? MarkupKind.markdown : cast(MarkupKind) formats[0]; 82 string prefix = kind == MarkupKind.markdown ? "**" ~ paramName ~ "**: " : paramName 83 ~ ": "; 84 string docRet = kind == MarkupKind.markdown 85 ? ddocToMarkdown(docString.strip) : docString.strip; 86 paramDocs = MarkupContent(kind, (prefix ~ docRet).stripRight).opt; 87 } 88 } 89 retParams ~= ParameterInformation(paramLabel, paramDocs); 90 } 91 92 sig.parameters = retParams.opt; 93 } 94 95 help.signatures ~= sig; 96 } 97 98 int writtenParamsCount = cast(int)(extractedParams.inTemplateParameters 99 ? extractedParams.templateArgs.length : extractedParams.functionArgs.length); 100 101 size_t[] possibleFunctions; 102 foreach (i, count; paramsCounts) 103 if (count >= writtenParamsCount) 104 possibleFunctions ~= i; 105 106 if (extractedParams.activeParameter != -1) 107 help.activeParameter = extractedParams.activeParameter.opt; 108 109 help.activeSignature = possibleFunctions.length ? cast(int) possibleFunctions[0] : 0; 110 111 return help; 112 } 113 114 @protocolMethod("textDocument/signatureHelp") 115 SignatureHelp provideSignatureHelp(TextDocumentPositionParams params) 116 { 117 auto document = documents[params.textDocument.uri]; 118 string file = document.uri.uriToFile; 119 if (document.languageId == "d") 120 return provideDSignatureHelp(params, file, document); 121 else if (document.languageId == "diet") 122 return provideDietSignatureHelp(params, file, document); 123 else 124 return SignatureHelp.init; 125 } 126 127 SignatureHelp provideDSignatureHelp(TextDocumentPositionParams params, 128 string file, ref Document document) 129 { 130 if (!backend.hasBest!DCDComponent(file) || !backend.hasBest!DCDExtComponent(file)) 131 return SignatureHelp.init; 132 133 auto currOffset = cast(int) document.positionToBytes(params.position); 134 135 scope codeText = document.rawText.idup; 136 137 DCDExtComponent dcdext = backend.best!DCDExtComponent(file); 138 auto callParams = dcdext.extractCallParameters(codeText, cast(int) currOffset); 139 if (callParams == CalltipsSupport.init) 140 return SignatureHelp.init; 141 142 DCDCompletions result = backend.best!DCDComponent(file) 143 .listCompletion(codeText, callParams.functionParensRange[0] + 1).getYield; 144 145 if (result == DCDCompletions.init) 146 return SignatureHelp.init; 147 148 switch (result.type) 149 { 150 case DCDCompletions.Type.calltips: 151 return convertDCDCalltips(dcdext, 152 result.calltips, result.symbols, callParams); 153 default: 154 trace("Unexpected result from DCD: ", result); 155 goto case; 156 case DCDCompletions.Type.identifiers: 157 return SignatureHelp.init; 158 } 159 } 160 161 SignatureHelp provideDietSignatureHelp(TextDocumentPositionParams params, 162 string file, ref Document document) 163 { 164 import served.utils.diet; 165 import dc = dietc.complete; 166 167 auto completion = updateDietFile(file, document.rawText.idup); 168 169 size_t offset = document.positionToBytes(params.position); 170 auto raw = completion.completeAt(offset); 171 CompletionItem[] ret; 172 173 if (raw is dc.Completion.completeD) 174 { 175 string code; 176 dc.extractD(completion, offset, code, offset); 177 if (offset <= code.length && backend.hasBest!DCDComponent(file) 178 && backend.hasBest!DCDExtComponent(file)) 179 { 180 auto dcdext = backend.best!DCDExtComponent(file); 181 182 auto callParams = dcdext.extractCallParameters(code, cast(int) offset); 183 if (callParams == CalltipsSupport.init) 184 return SignatureHelp.init; 185 186 auto dcd = backend.best!DCDComponent(file).listCompletion(code, 187 callParams.functionParensRange[0] + 1).getYield; 188 if (dcd.type == DCDCompletions.Type.calltips) 189 return convertDCDCalltips(dcdext, dcd.calltips, dcd.symbols, callParams); 190 } 191 } 192 return SignatureHelp.init; 193 }