diff --git a/.gitignore b/.gitignore index e6f3e72..c994c34 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ # *nix binaries dcd-client dcd-server +dcd-client.o +dcd-server.o # Perf reports perf.data diff --git a/actypes.d b/actypes.d index 1625c52..eb08edb 100644 --- a/actypes.d +++ b/actypes.d @@ -136,6 +136,11 @@ public: */ string symbolFile; + /** + * Documentation for the symbol. + */ + string doc; + /** * The symbol that represents the type. */ diff --git a/astconverter.d b/astconverter.d index b5273c5..0be452e 100644 --- a/astconverter.d +++ b/astconverter.d @@ -122,6 +122,7 @@ final class FirstPass : ASTVisitor dec.parameters, dec.comment); symbol.protection = protection; symbol.parent = currentSymbol; + symbol.acSymbol.doc = formatComment(dec.comment); currentSymbol.addChild(symbol); if (dec.functionBody !is null) { @@ -176,6 +177,7 @@ final class FirstPass : ASTVisitor symbol.type = t; symbol.protection = protection; symbol.parent = currentSymbol; + symbol.acSymbol.doc = formatComment(dec.comment); currentSymbol.addChild(symbol); } } @@ -260,6 +262,7 @@ final class FirstPass : ASTVisitor CompletionKind.enumName, symbolFile, dec.name.index); symbol.type = dec.type; symbol.parent = currentSymbol; + symbol.acSymbol.doc = formatComment(dec.comment); currentSymbol = symbol; if (dec.enumBody !is null) dec.enumBody.accept(this); @@ -274,6 +277,7 @@ final class FirstPass : ASTVisitor CompletionKind.enumMember, symbolFile, member.name.index); symbol.type = member.type; symbol.parent = currentSymbol; + symbol.acSymbol.doc = formatComment(member.comment); currentSymbol.addChild(symbol); } @@ -370,7 +374,7 @@ final class FirstPass : ASTVisitor versionCondition.accept(this); } - alias ASTVisitor.visit visit; + alias visit = ASTVisitor.visit; private: @@ -382,6 +386,7 @@ private: symbol.acSymbol.parts ~= classSymbols; symbol.parent = currentSymbol; symbol.protection = protection; + symbol.acSymbol.doc = formatComment(dec.comment); currentSymbol = symbol; dec.accept(this); currentSymbol = symbol.parent; @@ -396,6 +401,7 @@ private: processParameters(symbol, null, "this", parameters, doc); symbol.protection = protection; symbol.parent = currentSymbol; + symbol.acSymbol.doc = formatComment(doc); currentSymbol.addChild(symbol); if (functionBody !is null) { @@ -412,6 +418,7 @@ private: symbol.acSymbol.callTip = /*formatComment(doc) ~*/ "~this()"; symbol.protection = protection; symbol.parent = currentSymbol; + symbol.acSymbol.doc = formatComment(doc); currentSymbol.addChild(symbol); if (functionBody !is null) { @@ -553,6 +560,7 @@ private: s.parts = cast(typeof(s.parts)) symbol.parts; // TODO: Re-format callTip with new name? s.callTip = symbol.callTip; + s.doc = symbol.doc; s.qualifier = symbol.qualifier; s.location = symbol.location; s.symbolFile = symbol.symbolFile; @@ -915,12 +923,15 @@ string formatComment(string comment) re = slashPlusRegex; else re = slashStarRegex; - return (comment.replaceAll(regex(re), "") ~ "\n\n") + return (comment.replaceAll(regex(re), "")) .replaceFirst(regex("^\n"), "") .replaceAll(regex(`\\`), `\\`) .replaceAll(regex("\n"), `\n`).outdent(); } +/** + * Dummy doc comment for getCached + */ string getCached(string s) { return s.length == 0 ? "" diff --git a/autocomplete.d b/autocomplete.d index 72183a6..8796ebd 100644 --- a/autocomplete.d +++ b/autocomplete.d @@ -40,6 +40,48 @@ import modulecache; import astconverter; import stupidlog; +AutocompleteResponse getDoc(const AutocompleteRequest request) +{ + Log.trace("Getting doc comments"); + + AutocompleteResponse response; + LexerConfig config; + config.fileName = "stdin"; + StringCache* cache = new StringCache(StringCache.defaultBucketCount); + auto tokens = byToken(cast(ubyte[]) request.sourceCode, config, cache); + const(Token)[] tokenArray = void; + try { + tokenArray = tokens.array(); + } catch (Exception e) { + Log.error("Could not provide autocomplete due to lexing exception: ", e.msg); + return response; + } + auto sortedTokens = assumeSorted(tokenArray); + string partial; + + auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition); + + Log.trace("Token at cursor: ", beforeTokens[$ - 1].text); + + const(Scope)* completionScope = generateAutocompleteTrees(tokenArray, "stdin"); + auto expression = getExpression(beforeTokens); + + const(ACSymbol)*[] symbols = getSymbolsByTokenChain(completionScope, expression, + request.cursorPosition, CompletionType.ddoc); + + if (symbols.length == 0) + Log.error("Could not find symbol"); + else foreach (symbol; symbols) + { + Log.trace("Adding doc comment for ", symbol.name, ": ", symbol.doc); + response.docComments ~= symbol.doc; + } + return response; +} + +/** + * Finds the declaration of the symbol at the cursor position. + */ AutocompleteResponse findDeclaration(const AutocompleteRequest request) { Log.trace("Finding declaration"); @@ -86,7 +128,7 @@ AutocompleteResponse findDeclaration(const AutocompleteRequest request) const(ACSymbol)*[] getSymbolsByTokenChain(T)(const(Scope)* completionScope, T tokens, size_t cursorPosition, CompletionType completionType) { - Log.trace("Getting symbols from token chain", tokens); + Log.trace("Getting symbols from token chain", tokens.map!"a.text"); // Find the symbol corresponding to the beginning of the chain const(ACSymbol)*[] symbols = completionScope.getSymbolsByNameAndCursor( tokens[0].text, cursorPosition); @@ -178,12 +220,12 @@ const(ACSymbol)*[] getSymbolsByTokenChain(T)(const(Scope)* completionScope, Log.trace("Couldn't find it."); break loop; } - if (symbols[0].kind == CompletionKind.variableName + if ((symbols[0].kind == CompletionKind.variableName || symbols[0].kind == CompletionKind.memberVariableName || symbols[0].kind == CompletionKind.enumMember - || (symbols[0].kind == CompletionKind.functionName + || symbols[0].kind == CompletionKind.functionName) && (completionType == CompletionType.identifiers - || i + 1 < tokens.length))) + || i + 1 < tokens.length)) { symbols = symbols[0].type is null ? [] : [symbols[0].type]; } diff --git a/client.d b/client.d index 8aab9ec..caacbf7 100644 --- a/client.d +++ b/client.d @@ -41,12 +41,14 @@ int main(string[] args) bool shutdown; bool clearCache; bool symbolLocation; + bool doc; try { getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths, "port|p", &port, "help|h", &help, "shutdown", &shutdown, - "clearCache", &clearCache, "symbolLocation|l", &symbolLocation); + "clearCache", &clearCache, "symbolLocation|l", &symbolLocation, + "doc|d", &doc); } catch (Exception e) { @@ -119,7 +121,12 @@ int main(string[] args) request.importPaths = importPaths; request.sourceCode = sourceCode; request.cursorPosition = cursorPos; - request.kind = symbolLocation ? RequestKind.symbolLocation : RequestKind.autocomplete; + if (symbolLocation) + request.kind = RequestKind.symbolLocation; + else if (doc) + request.kind = RequestKind.doc; + else + request.kind = RequestKind.autocomplete; // Send message to server TcpSocket socket = createSocket(port); @@ -131,8 +138,10 @@ int main(string[] args) if (symbolLocation) printLocationResponse(response); + else if (doc) + printDocResponse(response); else - printCompletionResponse(response); + printCompletionResponse(response); return 0; } @@ -212,6 +221,12 @@ AutocompleteResponse getResponse(TcpSocket socket) return response; } +void printDocResponse(AutocompleteResponse response) +{ + foreach (doc; response.docComments) + writeln(doc); +} + void printLocationResponse(AutocompleteResponse response) { if (response.symbolFilePath is null) diff --git a/dscanner b/dscanner index c01c51a..27e91f1 160000 --- a/dscanner +++ b/dscanner @@ -1 +1 @@ -Subproject commit c01c51a61ea4f2e6a94a64396be0abd9e8249bab +Subproject commit 27e91f12bb71b6e42f46fa103689cb364cd693cc diff --git a/editors/textadept/modules/dmd/dcd.lua b/editors/textadept/modules/dmd/dcd.lua index cde139f..874e165 100644 --- a/editors/textadept/modules/dmd/dcd.lua +++ b/editors/textadept/modules/dmd/dcd.lua @@ -101,15 +101,15 @@ function M.cycleCalltips(delta) showCurrentCallTip() end -function M.gotoDeclaration() +local function runDCDClient(args) local fileName = os.tmpname() local mode = "w" if _G.WIN32 then fileName = os.getenv('TEMP') .. fileName mode = "wb" end - local command = M.PATH_TO_DCD_CLIENT .. " -l -c" .. buffer.current_pos .. - " > \"" .. fileName .. "\"" + local command = M.PATH_TO_DCD_CLIENT .. " " .. args + .. " -c" .. buffer.current_pos .. " > \"" .. fileName .. "\"" local p = io.popen(command, mode) p:write(buffer:get_text()) p:flush() @@ -117,6 +117,20 @@ function M.gotoDeclaration() local tmpFile = io.open(fileName, "r") local r = tmpFile:read("*a") tmpFile:close() + os.remove(fileName) + return r +end + +function M.showDoc() + local r = runDCDClient("-d") + if r ~= "\n" then + print(r) + showCalltips(r) + end +end + +function M.gotoDeclaration() + local r = runDCDClient("-l") if r ~= "Not found\n" then path, position = r:match("^(.-)\t(%d+)") if (path ~= nil and position ~= nil) then @@ -127,7 +141,6 @@ function M.gotoDeclaration() buffer:word_right_end_extend() end end - os.remove(fileName) end events.connect(events.CALL_TIP_CLICK, function(arrow) @@ -139,23 +152,9 @@ events.connect(events.CALL_TIP_CLICK, function(arrow) end end) -function M.autocomplete(ch) +function M.autocomplete() if buffer:get_lexer() ~= "dmd" then return end - local fileName = os.tmpname() - local mode = "w" - if _G.WIN32 then - fileName = os.getenv('TEMP') .. fileName - mode = "wb" - end - local command = M.PATH_TO_DCD_CLIENT .. " -c" .. buffer.current_pos .. - " > \"" .. fileName .. "\"" - local p = io.popen(command, mode) - p:write(buffer:get_text()) - p:flush() - p:close() - local tmpFile = io.open(fileName, "r") - local r = tmpFile:read("*a") - tmpFile:close() + local r = runDCDClient("") if r ~= "\n" then if r:match("^identifiers.*") then showCompletionList(r) @@ -163,7 +162,6 @@ function M.autocomplete(ch) showCalltips(r) end end - os.remove(fileName) end M.ALIAS =[[ diff --git a/editors/textadept/modules/dmd/init.lua b/editors/textadept/modules/dmd/init.lua index 894c5f3..28ecf9e 100644 --- a/editors/textadept/modules/dmd/init.lua +++ b/editors/textadept/modules/dmd/init.lua @@ -1,6 +1,6 @@ local M = {} -_M.dcd = require "dmd.dcd" +local dcd = require "dmd.dcd" if type(_G.snippets) == 'table' then _G.snippets.dmd = {} @@ -12,13 +12,13 @@ end events.connect(events.CHAR_ADDED, function(ch) if string.char(ch) == '(' or string.char(ch) == '.' then - _M.dcd.autocomplete(ch) + dcd.autocomplete() end end) local function autocomplete() - _M.dcd.registerImages() - _M.dcd.autocomplete() + dcd.registerImages() + dcd.autocomplete() if not buffer:auto_c_active() then textadept.editing.autocomplete_word(keywords) end @@ -31,9 +31,10 @@ keys.dmd = { (_USERHOME..'/modules/dmd/init.lua'):iconv('UTF-8', _CHARSET) }, }, ['c\n'] = {autocomplete}, - ['cG'] = {_M.dcd.gotoDeclaration}, - ['down'] = {_M.dcd.cycleCalltips, 1}, - ['up'] = {_M.dcd.cycleCalltips, -1}, + ['ch'] = {dcd.showDoc}, + ['cG'] = {dcd.gotoDeclaration}, + ['down'] = {dcd.cycleCalltips, 1}, + ['up'] = {dcd.cycleCalltips, -1}, } function M.set_buffer_properties() diff --git a/messages.d b/messages.d index 7649e57..2404e21 100644 --- a/messages.d +++ b/messages.d @@ -98,7 +98,12 @@ enum CompletionType : string /** * The response contains the location of a symbol declaration. */ - location = "location" + location = "location", + + /** + * The response contains documentation comments for the symbol. + */ + ddoc = "ddoc" } /** @@ -115,7 +120,9 @@ enum RequestKind : ubyte /// Shut down the server shutdown, /// Get declaration location of given symbol - symbolLocation + symbolLocation, + /// Get the doc comments for the symbol + doc } /** @@ -169,6 +176,11 @@ struct AutocompleteResponse */ size_t symbolLocation; + /** + * The documentation comment + */ + string[] docComments; + /** * The completions */ diff --git a/server.d b/server.d index 6105778..65653ae 100644 --- a/server.d +++ b/server.d @@ -102,7 +102,7 @@ int main(string[] args) // No relative paths version (Posix) chdir("/"); - while (true) + serverLoop: while (true) { auto s = socket.accept(); s.blocking = true; @@ -140,28 +140,33 @@ int main(string[] args) AutocompleteRequest request; msgpack.unpack(buffer[size_t.sizeof .. bytesReceived], request); - if (request.kind == RequestKind.addImport) + final switch (request.kind) { + case RequestKind.addImport: ModuleCache.addImportPaths(request.importPaths); - } - else if (request.kind == RequestKind.clearCache) - { + break; + case RequestKind.clearCache: Log.info("Clearing cache."); ModuleCache.clear(); - } - else if (request.kind == RequestKind.shutdown) - { - Log.info("Shutting down."); break; - } - else - { - AutocompleteResponse response = - request.kind == RequestKind.autocomplete - ? complete(request) - : findDeclaration(request); + case RequestKind.shutdown: + Log.info("Shutting down."); + break serverLoop; + case RequestKind.autocomplete: + AutocompleteResponse response = complete(request); ubyte[] responseBytes = msgpack.pack(response); s.send(responseBytes); + break; + case RequestKind.doc: + AutocompleteResponse response = getDoc(request); + ubyte[] responseBytes = msgpack.pack(response); + s.send(responseBytes); + break; + case RequestKind.symbolLocation: + AutocompleteResponse response = findDeclaration(request); + ubyte[] responseBytes = msgpack.pack(response); + s.send(responseBytes); + break; } Log.info("Request processed in ", requestWatch.peek().to!("msecs", float), " milliseconds"); }