From 7ffdf77b99dee95320d276cb807578aea37b2a91 Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Sat, 25 Mar 2017 23:47:59 -0700 Subject: [PATCH] textDocument/open close edit, WIP code completion --- .gitignore | 1 + src/code_completion.cc | 17 ++ src/command_line.cc | 92 ++++++++++- src/indexer.cpp | 1 + src/ipc.cc | 8 + src/ipc.h | 4 + src/language_server_api.cc | 4 +- src/language_server_api.h | 330 +++++++++++++++++++++++++++++++------ src/working_files.h | 7 + 9 files changed, 407 insertions(+), 57 deletions(-) create mode 100644 src/code_completion.cc create mode 100644 src/working_files.h diff --git a/.gitignore b/.gitignore index 7f2d406b..43ea4cf9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ Debug x64 build +libcxx waf-* .lock-waf* .waf* diff --git a/src/code_completion.cc b/src/code_completion.cc new file mode 100644 index 00000000..a457d602 --- /dev/null +++ b/src/code_completion.cc @@ -0,0 +1,17 @@ +#include + +void doit() { + // we should probably main two translation units, one for + // serving current requests, and one that is reparsing (follow qtcreator) + + // use clang_codeCompleteAt + + // we need to setup CXUnsavedFile + // The key to doing that is via + // - textDocument/didOpen + // - textDocument/didChange + // - textDocument/didClose + + // probably don't need + // - textDocument/willSave +} \ No newline at end of file diff --git a/src/command_line.cc b/src/command_line.cc index fd624692..744132b5 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -120,6 +120,10 @@ std::unique_ptr BuildIpcMessageQueue(const std::string& name, s RegisterId(ipc.get()); RegisterId(ipc.get()); RegisterId(ipc.get()); + RegisterId(ipc.get()); + RegisterId(ipc.get()); + RegisterId(ipc.get()); + RegisterId(ipc.get()); RegisterId(ipc.get()); RegisterId(ipc.get()); RegisterId(ipc.get()); @@ -135,6 +139,10 @@ void RegisterMessageTypes() { MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); @@ -334,6 +342,40 @@ void QueryDbMainLoop( break; } + case IpcId::TextDocumentDidOpen: { + auto msg = static_cast(message.get()); + std::cerr << "Opening " << msg->params.textDocument.uri.GetPath() << std::endl; + break; + } + case IpcId::TextDocumentDidChange: { + auto msg = static_cast(message.get()); + std::cerr << "Changing " << msg->params.textDocument.uri.GetPath() << std::endl; + break; + } + case IpcId::TextDocumentDidClose: { + auto msg = static_cast(message.get()); + std::cerr << "Closing " << msg->params.textDocument.uri.GetPath() << std::endl; + break; + } + + case IpcId::TextDocumentCompletion: { + auto msg = static_cast(message.get()); + Out_TextDocumentComplete response; + response.id = msg->id; + response.result.isIncomplete = false; + + std::cerr << "!! Code complete"; + for (int i = 0; i < 50; ++i) { + lsCompletionItem item; + item.label = "Entry#" + std::to_string(i); + item.documentation = "this is my doc " + std::to_string(i); + response.result.items.push_back(item); + } + + SendOutMessageToClient(language_client, response); + break; + } + case IpcId::TextDocumentDocumentSymbol: { auto msg = static_cast(message.get()); @@ -624,23 +666,67 @@ void LanguageServerStdinLoop(IpcMessageQueue* ipc) { ipc->SendMessage(&ipc->for_server, Ipc_OpenProject::kIpcId, open_project); } + // TODO: query request->params.capabilities.textDocument and support only things + // the client supports. + auto response = Out_InitializeResponse(); response.id = request->id; - response.result.capabilities.documentSymbolProvider = true; - // response.result.capabilities.referencesProvider = true; + + //response.result.capabilities.textDocumentSync = lsTextDocumentSyncOptions(); + //response.result.capabilities.textDocumentSync->openClose = true; + //response.result.capabilities.textDocumentSync->change = lsTextDocumentSyncKind::Full; + //response.result.capabilities.textDocumentSync->willSave = true; + //response.result.capabilities.textDocumentSync->willSaveWaitUntil = true; + response.result.capabilities.textDocumentSync = lsTextDocumentSyncKind::Incremental; + + response.result.capabilities.completionProvider = lsCompletionOptions(); + response.result.capabilities.completionProvider->resolveProvider = false; + response.result.capabilities.completionProvider->triggerCharacters = { ".", "::", "->" }; + response.result.capabilities.codeLensProvider = lsCodeLensOptions(); response.result.capabilities.codeLensProvider->resolveProvider = false; + + response.result.capabilities.documentSymbolProvider = true; + response.result.capabilities.workspaceSymbolProvider = true; + + response.Write(std::cerr); response.Write(std::cout); break; } + case IpcId::Initialized: { + // TODO: don't send output until we get this notification + break; + } + + case IpcId::CancelRequest: { + // TODO: support cancellation + break; + } + + case IpcId::TextDocumentCompletion: case IpcId::TextDocumentDocumentSymbol: case IpcId::TextDocumentCodeLens: - case IpcId::WorkspaceSymbol: { + case IpcId::WorkspaceSymbol: + break; + + case IpcId::TextDocumentDidOpen: + case IpcId::TextDocumentDidChange: + case IpcId::TextDocumentDidClose: { + //case IpcId::TextDocumentCompletion: + //case IpcId::TextDocumentDocumentSymbol: + //case IpcId::TextDocumentCodeLens: + //case IpcId::WorkspaceSymbol: { ipc->SendMessage(&ipc->for_server, message->method_id, *message.get()); break; } + + default: { + std::cerr << "Unhandled IPC message with kind " + << static_cast(message->method_id) << std::endl; + exit(1); + } } } } diff --git a/src/indexer.cpp b/src/indexer.cpp index 0a2a8155..5107d560 100644 --- a/src/indexer.cpp +++ b/src/indexer.cpp @@ -1205,6 +1205,7 @@ void emptyIndexEntityReference(CXClientData client_data, IndexedFile Parse(std::string filename, std::vector args, bool dump_ast) { + clang_enableStackTraces(); clang_toggleCrashRecovery(1); args.push_back("-std=c++11"); diff --git a/src/ipc.cc b/src/ipc.cc index a30b69d0..9ff09b7a 100644 --- a/src/ipc.cc +++ b/src/ipc.cc @@ -10,6 +10,14 @@ const char* IpcIdToString(IpcId id) { return "initialize"; case IpcId::Initialized: return "initialized"; + case IpcId::TextDocumentDidOpen: + return "textDocument/didOpen"; + case IpcId::TextDocumentDidChange: + return "textDocument/didChange"; + case IpcId::TextDocumentDidClose: + return "textDocument/didClose"; + case IpcId::TextDocumentCompletion: + return "textDocument/completion"; case IpcId::TextDocumentDocumentSymbol: return "textDocument/documentSymbol"; case IpcId::TextDocumentCodeLens: diff --git a/src/ipc.h b/src/ipc.h index 73dc808d..44d66cbe 100644 --- a/src/ipc.h +++ b/src/ipc.h @@ -10,6 +10,10 @@ enum class IpcId : int { CancelRequest = 0, Initialize, Initialized, + TextDocumentDidOpen, + TextDocumentDidChange, + TextDocumentDidClose, + TextDocumentCompletion, TextDocumentDocumentSymbol, TextDocumentCodeLens, CodeLensResolve, diff --git a/src/language_server_api.cc b/src/language_server_api.cc index 582d8dca..9be23170 100644 --- a/src/language_server_api.cc +++ b/src/language_server_api.cc @@ -34,6 +34,7 @@ std::unique_ptr MessageRegistry::ReadMessageFromStdin() { std::string line; std::getline(std::cin, line); // std::cin >> line; + // std::cerr << "Read line " << line; if (line.compare(0, 14, "Content-Length") == 0) { content_length = atoi(line.c_str() + 16); @@ -50,11 +51,12 @@ std::unique_ptr MessageRegistry::ReadMessageFromStdin() { return nullptr; } + // TODO: maybe use std::cin.read(c, content_length) std::string content; content.reserve(content_length); for (int i = 0; i < content_length; ++i) { char c; - std::cin >> c; + std::cin.read(&c, 1); content += c; } diff --git a/src/language_server_api.h b/src/language_server_api.h index 0c808d57..655c7aab 100644 --- a/src/language_server_api.h +++ b/src/language_server_api.h @@ -309,7 +309,6 @@ void Reflect(TVisitor& visitor, lsCommand& value) { REFLECT_MEMBER_END(); } - template struct lsCodeLens { // The range in which this code lens is valid. Should only span a single line. @@ -329,25 +328,172 @@ void Reflect(TVisitor& visitor, lsCodeLens& value) { REFLECT_MEMBER_END(); } -// TODO: TextDocumentEdit -// TODO: WorkspaceEdit - struct lsTextDocumentIdentifier { lsDocumentUri uri; }; MAKE_REFLECT_STRUCT(lsTextDocumentIdentifier, uri); -// TODO: TextDocumentItem -// TODO: VersionedTextDocumentIdentifier -// TODO: TextDocumentPositionParams -// TODO: DocumentFilter -// TODO: DocumentSelector +struct lsVersionedTextDocumentIdentifier { + lsDocumentUri uri; + // The version number of this document. + int version; +}; +MAKE_REFLECT_STRUCT(lsVersionedTextDocumentIdentifier, uri, version); + +struct lsTextDocumentPositionParams { + // The text document. + lsTextDocumentIdentifier textDocument; + + // The position inside the text document. + lsPosition position; +}; +MAKE_REFLECT_STRUCT(lsTextDocumentPositionParams, textDocument, position); + +// Defines whether the insert text in a completion item should be interpreted as +// plain text or a snippet. +enum class lsInsertTextFormat { + // The primary text to be inserted is treated as a plain string. + PlainText = 1, + + // The primary text to be inserted is treated as a snippet. + // + // A snippet can define tab stops and placeholders with `$1`, `$2` + // and `${3:foo}`. `$0` defines the final tab stop, it defaults to + // the end of the snippet. Placeholders with equal identifiers are linked, + // that is typing in one will update others too. + // + // See also: https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/common/snippet.md + Snippet = 2 +}; +MAKE_REFLECT_TYPE_PROXY(lsInsertTextFormat, int); + +// The kind of a completion entry. +enum class lsCompletionItemKind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18 +}; +MAKE_REFLECT_TYPE_PROXY(lsCompletionItemKind, int); + +struct lsCompletionItem { + // The label of this completion item. By default + // also the text that is inserted when selecting + // this completion. + std::string label; + + // The kind of this completion item. Based of the kind + // an icon is chosen by the editor. + lsCompletionItemKind kind = lsCompletionItemKind::Text; + + // A human-readable string with additional information + // about this item, like type or symbol information. + std::string detail; + + // A human-readable string that represents a doc-comment. + std::string documentation; + + // A string that shoud be used when comparing this item + // with other items. When `falsy` the label is used. + std::string sortText; + + // A string that should be used when filtering a set of + // completion items. When `falsy` the label is used. + std::string filterText; + + // A string that should be inserted a document when selecting + // this completion. When `falsy` the label is used. + std::string insertText; + + // The format of the insert text. The format applies to both the `insertText` property + // and the `newText` property of a provided `textEdit`. + lsInsertTextFormat insertTextFormat; + + // An edit which is applied to a document when selecting this completion. When an edit is provided the value of + // `insertText` is ignored. + // + // *Note:* The range of the edit must be a single line range and it must contain the position at which completion + // has been requested. + // TextEdit textEdit; + + // An optional array of additional text edits that are applied when + // selecting this completion. Edits must not overlap with the main edit + // nor with themselves. + // std::vector additionalTextEdits; + + // An optional command that is executed *after* inserting this completion. *Note* that + // additional modifications to the current document should be described with the + // additionalTextEdits-property. + // Command command; + + // An data entry field that is preserved on a completion item between + // a completion and a completion resolve request. + // data ? : any +}; +MAKE_REFLECT_STRUCT(lsCompletionItem, + label, + kind, + detail, + documentation, + sortText, + filterText, + insertText, + insertTextFormat); +struct lsTextDocumentItem { + // The text document's URI. + lsDocumentUri uri; + // The text document's language identifier. + std::string languageId; + // The version number of this document (it will strictly increase after each + // change, including undo/redo). + int version; + // The content of the opened text document. + std::string text; +}; +MAKE_REFLECT_STRUCT(lsTextDocumentItem, uri, languageId, version, text); +struct lsTextEdit { + // The range of the text document to be manipulated. To insert + // text into a document create a range where start === end. + lsRange range; + + // The string to be inserted. For delete operations use an + // empty string. + std::string newText; +}; +MAKE_REFLECT_STRUCT(lsTextEdit, range, newText); + +struct lsTextDocumentEdit { + // The text document to change. + lsVersionedTextDocumentIdentifier textDocument; + + // The edits to be applied. + std::vector edits; +}; +MAKE_REFLECT_STRUCT(lsTextDocumentEdit, textDocument, edits); + +// TODO: WorkspaceEdit +// TODO: DocumentFilter +// TODO: DocumentSelector @@ -589,21 +735,21 @@ MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsCompletion::lsCompletion MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsGenericDynamicReg, dynamicRegistration); MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::CodeLensRegistrationOptions, dynamicRegistration, resolveProvider); MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities, - synchronization, - completion, - hover, - signatureHelp, - references, - documentHighlight, - documentSymbol, - formatting, - rangeFormatting, - onTypeFormatting, - definition, - codeAction, - codeLens, - documentLink, - rename); + synchronization, + completion, + hover, + signatureHelp, + references, + documentHighlight, + documentSymbol, + formatting, + rangeFormatting, + onTypeFormatting, + definition, + codeAction, + codeLens, + documentLink, + rename); struct lsClientCapabilities { // Workspace specific client capabilities. @@ -688,7 +834,7 @@ struct lsCompletionOptions { bool resolveProvider = false; // The characters that trigger completion automatically. - NonElidedVector triggerCharacters; + std::vector triggerCharacters; }; MAKE_REFLECT_STRUCT(lsCompletionOptions, resolveProvider, triggerCharacters); @@ -742,11 +888,11 @@ struct lsTextDocumentSyncOptions { bool openClose = false; // Change notificatins are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full // and TextDocumentSyncKindIncremental. - optional change; + lsTextDocumentSyncKind change = lsTextDocumentSyncKind::Incremental; // Will save notifications are sent to the server. - bool willSave = false; + optional willSave; // Will save wait until requests are sent to the server. - bool willSaveWaitUntil = false; + optional willSaveWaitUntil; // Save notifications are sent to the server. optional save; }; @@ -755,7 +901,10 @@ MAKE_REFLECT_STRUCT(lsTextDocumentSyncOptions, openClose, change, willSave, will struct lsServerCapabilities { // Defines how text documents are synced. Is either a detailed structure defining each notification or // for backwards compatibility the TextDocumentSyncKind number. - optional textDocumentSync; + // TODO: It seems like the new API is broken and doesn't work. + // optional textDocumentSync; + lsTextDocumentSyncKind textDocumentSync; + // The server provides hover support. bool hoverProvider = false; // The server provides completion support. @@ -790,29 +939,23 @@ struct lsServerCapabilities { optional executeCommandProvider; }; MAKE_REFLECT_STRUCT(lsServerCapabilities, - textDocumentSync, - hoverProvider, - completionProvider, - signatureHelpProvider, - definitionProvider, - referencesProvider, - documentHighlightProvider, - documentSymbolProvider, - workspaceSymbolProvider, - codeActionProvider, - codeLensProvider, - documentFormattingProvider, - documentRangeFormattingProvider, - documentOnTypeFormattingProvider, - renameProvider, - documentLinkProvider, - executeCommandProvider); - -struct lsInitializeResult { - // The capabilities the language server provides. - lsServerCapabilities capabilities; -}; -MAKE_REFLECT_STRUCT(lsInitializeResult, capabilities); + textDocumentSync, + hoverProvider, + completionProvider, + signatureHelpProvider, + definitionProvider, + referencesProvider, + documentHighlightProvider, + documentSymbolProvider, + workspaceSymbolProvider, + codeActionProvider, + codeLensProvider, + documentFormattingProvider, + documentRangeFormattingProvider, + documentOnTypeFormattingProvider, + renameProvider, + documentLinkProvider, + executeCommandProvider); struct Ipc_InitializeRequest : public IpcMessage { const static IpcId kIpcId = IpcId::Initialize; @@ -823,9 +966,13 @@ struct Ipc_InitializeRequest : public IpcMessage { MAKE_REFLECT_STRUCT(Ipc_InitializeRequest, id, params); struct Out_InitializeResponse : public lsOutMessage { + struct InitializeResult { + lsServerCapabilities capabilities; + }; lsRequestId id; - lsInitializeResult result; + InitializeResult result; }; +MAKE_REFLECT_STRUCT(Out_InitializeResponse::InitializeResult, capabilities); MAKE_REFLECT_STRUCT(Out_InitializeResponse, jsonrpc, id, result); struct Ipc_InitializedNotification : public IpcMessage { @@ -883,6 +1030,83 @@ struct Ipc_CancelRequest : public IpcMessage { }; MAKE_REFLECT_STRUCT(Ipc_CancelRequest, id); + + + + +// Open, update, close file +struct Ipc_TextDocumentDidOpen : public IpcMessage { + struct Params { + lsTextDocumentItem textDocument; + }; + + const static IpcId kIpcId = IpcId::TextDocumentDidOpen; + Params params; +}; +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidOpen::Params, textDocument); +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidOpen, params); +struct Ipc_TextDocumentDidChange : public IpcMessage { + struct lsTextDocumentContentChangeEvent { + // The range of the document that changed. + lsRange range; + // The length of the range that got replaced. + int rangeLength = 0; + // The new text of the range/document. + std::string text; + }; + + struct Params { + lsVersionedTextDocumentIdentifier textDocument; + std::vector contentChanges; + }; + + const static IpcId kIpcId = IpcId::TextDocumentDidChange; + Params params; +}; +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidChange::lsTextDocumentContentChangeEvent, range, rangeLength, text); +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidChange::Params, textDocument, contentChanges); +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidChange, params); +struct Ipc_TextDocumentDidClose : public IpcMessage { + struct Params { + lsTextDocumentItem textDocument; + }; + + const static IpcId kIpcId = IpcId::TextDocumentDidClose; + Params params; +}; +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidClose::Params, textDocument); +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidClose, params); + + + + + + + + +// Code completion +struct Ipc_TextDocumentComplete : public IpcMessage { + const static IpcId kIpcId = IpcId::TextDocumentCompletion; + + lsRequestId id; + lsTextDocumentPositionParams params; +}; +MAKE_REFLECT_STRUCT(Ipc_TextDocumentComplete, id, params); +struct lsTextDocumentCompleteResult { + // This list it not complete. Further typing should result in recomputing + // this list. + bool isIncomplete = false; + // The completion items. + NonElidedVector items; +}; +MAKE_REFLECT_STRUCT(lsTextDocumentCompleteResult, isIncomplete, items); +struct Out_TextDocumentComplete : public lsOutMessage { + lsRequestId id; + lsTextDocumentCompleteResult result; +}; +MAKE_REFLECT_STRUCT(Out_TextDocumentComplete, jsonrpc, id, result); + + // List symbols in a document. struct lsDocumentSymbolParams { lsTextDocumentIdentifier textDocument; diff --git a/src/working_files.h b/src/working_files.h new file mode 100644 index 00000000..7f66f703 --- /dev/null +++ b/src/working_files.h @@ -0,0 +1,7 @@ +#pragma once + +#include "language_server_api.h" + +struct WorkingFiles { + +}; \ No newline at end of file