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 Variant!(string, uint[2]) paramLabel; 65 if (capabilities.textDocument.orDefault 66 .signatureHelp.orDefault 67 .signatureInformation.orDefault 68 .parameterInformation.orDefault 69 .labelOffsetSupport.orDefault) 70 paramLabel = cast(uint[2])[ 71 cast(uint) calltip[0 .. param.contentRange[0]].countUTF16Length, 72 cast(uint) calltip[0 .. param.contentRange[1]].countUTF16Length 73 ]; 74 else 75 paramLabel = calltip[param.contentRange[0] .. param.contentRange[1]]; 76 Variant!(void, string, MarkupContent) paramDocs; 77 if (docs != Comment.init) 78 { 79 auto docString = getParamDocumentation(docs, paramName); 80 if (docString.length) 81 { 82 MarkupKind[] formats = capabilities.textDocument.orDefault 83 .signatureHelp.orDefault 84 .signatureInformation.orDefault 85 .documentationFormat.orDefault; 86 MarkupKind kind = formats.length == 0 87 ? MarkupKind.markdown 88 : formats[0]; 89 string prefix = kind == MarkupKind.markdown ? "**" ~ paramName ~ "**: " : paramName 90 ~ ": "; 91 string docRet = kind == MarkupKind.markdown 92 ? ddocToMarkdown(docString.strip) : docString.strip; 93 paramDocs = MarkupContent(kind, (prefix ~ docRet).stripRight); 94 } 95 } 96 retParams ~= ParameterInformation(paramLabel, paramDocs); 97 } 98 99 sig.parameters = retParams.opt; 100 } 101 102 help.signatures ~= sig; 103 } 104 105 int writtenParamsCount = cast(int)(extractedParams.inTemplateParameters 106 ? extractedParams.templateArgs.length : extractedParams.functionArgs.length); 107 108 size_t[] possibleFunctions; 109 foreach (i, count; paramsCounts) 110 if (count >= writtenParamsCount) 111 possibleFunctions ~= i; 112 113 if (extractedParams.activeParameter != -1) 114 help.activeParameter = extractedParams.activeParameter; 115 116 help.activeSignature = possibleFunctions.length ? cast(int) possibleFunctions[0] : 0; 117 118 return help; 119 } 120 121 @protocolMethod("textDocument/signatureHelp") 122 SignatureHelp provideSignatureHelp(TextDocumentPositionParams params) 123 { 124 auto document = documents[params.textDocument.uri]; 125 string file = document.uri.uriToFile; 126 if (document.languageId == "d") 127 return provideDSignatureHelp(params, file, document); 128 else if (document.languageId == "diet") 129 return provideDietSignatureHelp(params, file, document); 130 else 131 return SignatureHelp.init; 132 } 133 134 SignatureHelp provideDSignatureHelp(TextDocumentPositionParams params, 135 string file, ref Document document) 136 { 137 if (!backend.hasBest!DCDComponent(file) || !backend.hasBest!DCDExtComponent(file)) 138 return SignatureHelp.init; 139 140 auto currOffset = cast(int) document.positionToBytes(params.position); 141 142 scope codeText = document.rawText.idup; 143 144 DCDExtComponent dcdext = backend.best!DCDExtComponent(file); 145 auto callParams = dcdext.extractCallParameters(codeText, cast(int) currOffset); 146 if (callParams == CalltipsSupport.init) 147 return SignatureHelp.init; 148 149 DCDCompletions result = backend.best!DCDComponent(file) 150 .listCompletion(codeText, callParams.functionParensRange[0] + 1).getYield; 151 152 if (result == DCDCompletions.init) 153 return SignatureHelp.init; 154 155 switch (result.type) 156 { 157 case DCDCompletions.Type.calltips: 158 return convertDCDCalltips(dcdext, 159 result.calltips, result.symbols, callParams); 160 default: 161 trace("Unexpected result from DCD: ", result); 162 goto case; 163 case DCDCompletions.Type.identifiers: 164 return SignatureHelp.init; 165 } 166 } 167 168 SignatureHelp provideDietSignatureHelp(TextDocumentPositionParams params, 169 string file, ref Document document) 170 { 171 import served.utils.diet; 172 import dc = dietc.complete; 173 174 auto completion = updateDietFile(file, document.rawText.idup); 175 176 size_t offset = document.positionToBytes(params.position); 177 auto raw = completion.completeAt(offset); 178 CompletionItem[] ret; 179 180 if (raw is dc.Completion.completeD) 181 { 182 string code; 183 dc.extractD(completion, offset, code, offset); 184 if (offset <= code.length && backend.hasBest!DCDComponent(file) 185 && backend.hasBest!DCDExtComponent(file)) 186 { 187 auto dcdext = backend.best!DCDExtComponent(file); 188 189 auto callParams = dcdext.extractCallParameters(code, cast(int) offset); 190 if (callParams == CalltipsSupport.init) 191 return SignatureHelp.init; 192 193 auto dcd = backend.best!DCDComponent(file).listCompletion(code, 194 callParams.functionParensRange[0] + 1).getYield; 195 if (dcd.type == DCDCompletions.Type.calltips) 196 return convertDCDCalltips(dcdext, dcd.calltips, dcd.symbols, callParams); 197 } 198 } 199 return SignatureHelp.init; 200 }