diff --git a/.gitignore b/.gitignore index f66c3d4..64edc94 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ vscode/node_modules +vscode/out +lsp-server/build diff --git a/lsp-server/.clang-format b/lsp-server/.clang-format new file mode 100644 index 0000000..bd37759 --- /dev/null +++ b/lsp-server/.clang-format @@ -0,0 +1,43 @@ +--- +Language: Cpp +BasedOnStyle: Microsoft +AccessModifierOffset: -4 +AlignEscapedNewlines: Left +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortFunctionsOnASingleLine: All +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: true + AfterUnion: true + AfterExternBlock: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +ColumnLimit: 0 +CommentPragmas: "suppress" +ConstructorInitializerAllOnOneLineOrOnePerLine: true +Cpp11BracedListStyle: false +FixNamespaceComments: false +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^.*(precomp|pch|stdafx)' + Priority: -1 + - Regex: '^".*"' + Priority: 1 + - Regex: '^<.*>' + Priority: 2 + - Regex: '.*' + Priority: 3 +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "BEGIN_TEST_METHOD_PROPERTIES|BEGIN_MODULE|BEGIN_TEST_CLASS|BEGIN_TEST_METHOD" +MacroBlockEnd: "END_TEST_METHOD_PROPERTIES|END_MODULE|END_TEST_CLASS|END_TEST_METHOD" +NamespaceIndentation: All +PointerAlignment: Left +ReflowComments: false +SortIncludes: false +SpaceAfterTemplateKeyword: false +SpacesInAngles: false +SpacesInContainerLiterals: false diff --git a/lsp-server/CMakeLists.txt b/lsp-server/CMakeLists.txt new file mode 100644 index 0000000..22ce205 --- /dev/null +++ b/lsp-server/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.10) +project(tsl-server) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(nlohmann_json REQUIRED) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) +set(SOURCES + src/main.cpp + src/language/tsl_keywords.cpp + src/lsp/dispacther.cpp + src/lsp/server.cpp + src/lsp/logger.cpp + src/provider/base/provider_registry.cpp + src/provider/initialize/initialize_provider.cpp + src/provider/initialized/initialized_provider.cpp + src/provider/text_document/did_open_provider.cpp + src/provider/text_document/did_change_provider.cpp + src/provider/text_document/completion_provider.cpp + src/provider/trace/set_trace_provider.cpp +) + +add_executable(${PROJECT_NAME} ${SOURCES}) +target_link_libraries(${PROJECT_NAME} + nlohmann_json::nlohmann_json +) + diff --git a/lsp-server/src/language/tsl_keywords.cpp b/lsp-server/src/language/tsl_keywords.cpp new file mode 100644 index 0000000..034f796 --- /dev/null +++ b/lsp-server/src/language/tsl_keywords.cpp @@ -0,0 +1,281 @@ +#include +#include +#include +#include "./tsl_keywords.hpp" + +namespace tsl +{ + std::vector TslKeywords::keywords_; + std::unordered_set TslKeywords::keyword_set_; + bool TslKeywords::initialized_ = false; + + const std::vector& TslKeywords::GetAllKeywords() + { + InitKeywords(); + return keywords_; + } + + std::vector TslKeywords::GetKeywordsByCategory(KeywordCategory category) + { + InitKeywords(); + std::vector result; + + for (const auto& keyword : keywords_) + if (keyword.category == category) + result.push_back(keyword); + return result; + } + + std::vector TslKeywords::GetCompletionItems(const std::string& prefix) + { + InitKeywords(); + std::vector result; + std::string lower_prefix = ToLowerCase(prefix); + + for (const auto& keyword : keywords_) + { + if (prefix.empty() || StartsWith(ToLowerCase(keyword.keyword), lower_prefix)) + { + lsp::CompletionItem item; + item.label = keyword.keyword; + item.kind = keyword.completion_kind; + item.detail = keyword.description; + item.insert_text = keyword.keyword; + result.push_back(item); + } + } + // 按字母顺序排序 + std::sort(result.begin(), result.end(), + [](const lsp::CompletionItem& a, const lsp::CompletionItem& b) { + return a.label < b.label; + }); + return result; + } + + KeywordCategory TslKeywords::GetKeywordCategory(const std::string& word) + { + InitKeywords(); + std::string lower_word = ToLowerCase(word); + + for (const auto& keyword : keywords_) + { + if (ToLowerCase(keyword.keyword) == lower_word) { + return keyword.category; + } + } + + // 如果没找到,返回一个默认值(可以考虑抛出异常) + return KeywordCategory::kProgramStructure; + } + + void TslKeywords::InitKeywords() + { + if (initialized_) + return; + keywords_ = { + // Program Structure + { "program", KeywordCategory::kProgramStructure, "Program declaration", lsp::CompletionItemKind::kKeyword }, + { "function", KeywordCategory::kProgramStructure, "Function declaration", lsp::CompletionItemKind::kFunction }, + { "procedure", KeywordCategory::kProgramStructure, "Procedure declaration", lsp::CompletionItemKind::kFunction }, + { "unit", KeywordCategory::kProgramStructure, "Unit declaration", lsp::CompletionItemKind::kModule }, + { "uses", KeywordCategory::kProgramStructure, "Uses clause", lsp::CompletionItemKind::kKeyword }, + { "implementation", KeywordCategory::kProgramStructure, "Implementation section", lsp::CompletionItemKind::kKeyword }, + { "interface", KeywordCategory::kProgramStructure, "Interface section", lsp::CompletionItemKind::kInterface }, + { "initialization", KeywordCategory::kProgramStructure, "Initialization section", lsp::CompletionItemKind::kKeyword }, + { "finalization", KeywordCategory::kProgramStructure, "Finalization section", lsp::CompletionItemKind::kKeyword }, + + // Data Types + { "string", KeywordCategory::kDataTypes, "String data type", lsp::CompletionItemKind::kClass }, + { "integer", KeywordCategory::kDataTypes, "Integer data type", lsp::CompletionItemKind::kClass }, + { "boolean", KeywordCategory::kDataTypes, "Boolean data type", lsp::CompletionItemKind::kClass }, + { "int64", KeywordCategory::kDataTypes, "64-bit integer data type", lsp::CompletionItemKind::kClass }, + { "real", KeywordCategory::kDataTypes, "Real number data type", lsp::CompletionItemKind::kClass }, + { "array", KeywordCategory::kDataTypes, "Array data type", lsp::CompletionItemKind::kClass }, + + // Class Types + { "type", KeywordCategory::kClassTypes, "Type declaration", lsp::CompletionItemKind::kKeyword }, + { "class", KeywordCategory::kClassTypes, "Class declaration", lsp::CompletionItemKind::kClass }, + { "fakeclass", KeywordCategory::kClassTypes, "Fake class declaration", lsp::CompletionItemKind::kClass }, + { "new", KeywordCategory::kClassTypes, "Object instantiation", lsp::CompletionItemKind::kKeyword }, + + // Class Modifiers + { "override", KeywordCategory::kClassModifiers, "Override method", lsp::CompletionItemKind::kKeyword }, + { "overload", KeywordCategory::kClassModifiers, "Overload method", lsp::CompletionItemKind::kKeyword }, + { "virtual", KeywordCategory::kClassModifiers, "Virtual method", lsp::CompletionItemKind::kKeyword }, + { "property", KeywordCategory::kClassModifiers, "Property declaration", lsp::CompletionItemKind::kProperty }, + { "self", KeywordCategory::kClassModifiers, "Self reference", lsp::CompletionItemKind::kVariable }, + { "inherited", KeywordCategory::kClassModifiers, "Inherited method call", lsp::CompletionItemKind::kKeyword }, + + // Access Modifiers + { "public", KeywordCategory::kAccessModifiers, "Public access modifier", lsp::CompletionItemKind::kKeyword }, + { "protected", KeywordCategory::kAccessModifiers, "Protected access modifier", lsp::CompletionItemKind::kKeyword }, + { "private", KeywordCategory::kAccessModifiers, "Private access modifier", lsp::CompletionItemKind::kKeyword }, + { "published", KeywordCategory::kAccessModifiers, "Published access modifier", lsp::CompletionItemKind::kKeyword }, + + // Property Accessors + { "read", KeywordCategory::kPropertyAccessors, "Property read accessor", lsp::CompletionItemKind::kKeyword }, + { "write", KeywordCategory::kPropertyAccessors, "Property write accessor", lsp::CompletionItemKind::kKeyword }, + + // Constructors + { "create", KeywordCategory::kConstructors, "Constructor method", lsp::CompletionItemKind::kConstructor }, + { "destroy", KeywordCategory::kConstructors, "Destructor method", lsp::CompletionItemKind::kMethod }, + { "operator", KeywordCategory::kConstructors, "Operator overload", lsp::CompletionItemKind::kOperator }, + + // Variable Modifiers + { "external", KeywordCategory::kVariableModifiers, "External declaration", lsp::CompletionItemKind::kKeyword }, + { "const", KeywordCategory::kVariableModifiers, "Constant declaration", lsp::CompletionItemKind::kConstant }, + { "out", KeywordCategory::kVariableModifiers, "Output parameter", lsp::CompletionItemKind::kKeyword }, + { "var", KeywordCategory::kVariableModifiers, "Variable declaration", lsp::CompletionItemKind::kVariable }, + { "global", KeywordCategory::kVariableModifiers, "Global variable", lsp::CompletionItemKind::kVariable }, + { "static", KeywordCategory::kVariableModifiers, "Static variable", lsp::CompletionItemKind::kVariable }, + + // Conditionals + { "if", KeywordCategory::kConditionals, "If statement", lsp::CompletionItemKind::kKeyword }, + { "else", KeywordCategory::kConditionals, "Else clause", lsp::CompletionItemKind::kKeyword }, + { "then", KeywordCategory::kConditionals, "Then clause", lsp::CompletionItemKind::kKeyword }, + { "case", KeywordCategory::kConditionals, "Case statement", lsp::CompletionItemKind::kKeyword }, + { "of", KeywordCategory::kConditionals, "Of clause", lsp::CompletionItemKind::kKeyword }, + + // Loops + { "for", KeywordCategory::kLoops, "For loop", lsp::CompletionItemKind::kKeyword }, + { "while", KeywordCategory::kLoops, "While loop", lsp::CompletionItemKind::kKeyword }, + { "do", KeywordCategory::kLoops, "Do clause", lsp::CompletionItemKind::kKeyword }, + { "downto", KeywordCategory::kLoops, "Downto clause", lsp::CompletionItemKind::kKeyword }, + { "step", KeywordCategory::kLoops, "Step clause", lsp::CompletionItemKind::kKeyword }, + { "until", KeywordCategory::kLoops, "Until clause", lsp::CompletionItemKind::kKeyword }, + { "repeat", KeywordCategory::kLoops, "Repeat loop", lsp::CompletionItemKind::kKeyword }, + { "to", KeywordCategory::kLoops, "To clause", lsp::CompletionItemKind::kKeyword }, + + // Branch Control + { "break", KeywordCategory::kBranchControl, "Break statement", lsp::CompletionItemKind::kKeyword }, + { "continue", KeywordCategory::kBranchControl, "Continue statement", lsp::CompletionItemKind::kKeyword }, + { "goto", KeywordCategory::kBranchControl, "Goto statement", lsp::CompletionItemKind::kKeyword }, + { "label", KeywordCategory::kBranchControl, "Label declaration", lsp::CompletionItemKind::kKeyword }, + { "exit", KeywordCategory::kBranchControl, "Exit statement", lsp::CompletionItemKind::kKeyword }, + + // Return Control + { "return", KeywordCategory::kReturnControl, "Return statement", lsp::CompletionItemKind::kKeyword }, + { "debugreturn", KeywordCategory::kReturnControl, "Debug return statement", lsp::CompletionItemKind::kKeyword }, + { "debugrunenv", KeywordCategory::kReturnControl, "Debug run environment", lsp::CompletionItemKind::kKeyword }, + { "debugrunenvdo", KeywordCategory::kReturnControl, "Debug run environment do", lsp::CompletionItemKind::kKeyword }, + + // Block Control + { "begin", KeywordCategory::kBlockControl, "Begin block", lsp::CompletionItemKind::kKeyword }, + { "end", KeywordCategory::kBlockControl, "End block", lsp::CompletionItemKind::kKeyword }, + { "with", KeywordCategory::kBlockControl, "With statement", lsp::CompletionItemKind::kKeyword }, + + // References + { "weakref", KeywordCategory::kReferences, "Weak reference", lsp::CompletionItemKind::kKeyword }, + { "autoref", KeywordCategory::kReferences, "Auto reference", lsp::CompletionItemKind::kKeyword }, + + // Namespace + { "namespace", KeywordCategory::kNamespace, "Namespace declaration", lsp::CompletionItemKind::kModule }, + + // Exceptions + { "except", KeywordCategory::kExceptions, "Exception handling", lsp::CompletionItemKind::kKeyword }, + { "raise", KeywordCategory::kExceptions, "Raise exception", lsp::CompletionItemKind::kKeyword }, + { "try", KeywordCategory::kExceptions, "Try block", lsp::CompletionItemKind::kKeyword }, + { "finally", KeywordCategory::kExceptions, "Finally block", lsp::CompletionItemKind::kKeyword }, + { "exceptobject", KeywordCategory::kExceptions, "Exception object", lsp::CompletionItemKind::kKeyword }, + + // Logical Operators + { "and", KeywordCategory::kLogicalOperators, "Logical AND", lsp::CompletionItemKind::kOperator }, + { "in", KeywordCategory::kLogicalOperators, "In operator", lsp::CompletionItemKind::kOperator }, + { "is", KeywordCategory::kLogicalOperators, "Is operator", lsp::CompletionItemKind::kOperator }, + { "not", KeywordCategory::kLogicalOperators, "Logical NOT", lsp::CompletionItemKind::kOperator }, + { "or", KeywordCategory::kLogicalOperators, "Logical OR", lsp::CompletionItemKind::kOperator }, + + // Arithmetic Operators + { "div", KeywordCategory::kArithmeticOperators, "Integer division", lsp::CompletionItemKind::kOperator }, + { "mod", KeywordCategory::kArithmeticOperators, "Modulo operation", lsp::CompletionItemKind::kOperator }, + + // Bitwise Operators + { "ror", KeywordCategory::kBitwiseOperators, "Rotate right", lsp::CompletionItemKind::kOperator }, + { "rol", KeywordCategory::kBitwiseOperators, "Rotate left", lsp::CompletionItemKind::kOperator }, + { "shr", KeywordCategory::kBitwiseOperators, "Shift right", lsp::CompletionItemKind::kOperator }, + { "shl", KeywordCategory::kBitwiseOperators, "Shift left", lsp::CompletionItemKind::kOperator }, + + // Set Operators + { "union", KeywordCategory::kSetOperators, "Set union", lsp::CompletionItemKind::kOperator }, + { "minus", KeywordCategory::kSetOperators, "Set difference", lsp::CompletionItemKind::kOperator }, + { "union2", KeywordCategory::kSetOperators, "Set union (alternative)", lsp::CompletionItemKind::kOperator }, + + // SQL Control + { "select", KeywordCategory::kSqlControl, "SQL SELECT", lsp::CompletionItemKind::kKeyword }, + { "vselect", KeywordCategory::kSqlControl, "Virtual SELECT", lsp::CompletionItemKind::kKeyword }, + { "sselect", KeywordCategory::kSqlControl, "Special SELECT", lsp::CompletionItemKind::kKeyword }, + { "update", KeywordCategory::kSqlControl, "SQL UPDATE", lsp::CompletionItemKind::kKeyword }, + { "delete", KeywordCategory::kSqlControl, "SQL DELETE", lsp::CompletionItemKind::kKeyword }, + { "mselect", KeywordCategory::kSqlControl, "Multiple SELECT", lsp::CompletionItemKind::kKeyword }, + + // SQL Keywords + { "sqlin", KeywordCategory::kSqlKeywords, "SQL IN", lsp::CompletionItemKind::kKeyword }, + { "from", KeywordCategory::kSqlKeywords, "SQL FROM", lsp::CompletionItemKind::kKeyword }, + { "where", KeywordCategory::kSqlKeywords, "SQL WHERE", lsp::CompletionItemKind::kKeyword }, + { "group", KeywordCategory::kSqlKeywords, "SQL GROUP", lsp::CompletionItemKind::kKeyword }, + { "by", KeywordCategory::kSqlKeywords, "SQL BY", lsp::CompletionItemKind::kKeyword }, + { "order", KeywordCategory::kSqlKeywords, "SQL ORDER", lsp::CompletionItemKind::kKeyword }, + { "distinct", KeywordCategory::kSqlKeywords, "SQL DISTINCT", lsp::CompletionItemKind::kKeyword }, + { "join", KeywordCategory::kSqlKeywords, "SQL JOIN", lsp::CompletionItemKind::kKeyword }, + + // SQL Operators (note: some overlap with logical operators) + { "on", KeywordCategory::kSqlOperators, "SQL ON", lsp::CompletionItemKind::kOperator }, + { "like", KeywordCategory::kSqlOperators, "SQL LIKE", lsp::CompletionItemKind::kOperator }, + + // Calling Conventions + { "cdecl", KeywordCategory::kCallingConventions, "C calling convention", lsp::CompletionItemKind::kKeyword }, + { "pascal", KeywordCategory::kCallingConventions, "Pascal calling convention", lsp::CompletionItemKind::kKeyword }, + { "stdcall", KeywordCategory::kCallingConventions, "Standard calling convention", lsp::CompletionItemKind::kKeyword }, + { "safecall", KeywordCategory::kCallingConventions, "Safe calling convention", lsp::CompletionItemKind::kKeyword }, + { "fastcall", KeywordCategory::kCallingConventions, "Fast calling convention", lsp::CompletionItemKind::kKeyword }, + { "register", KeywordCategory::kCallingConventions, "Register calling convention", lsp::CompletionItemKind::kKeyword }, + + // System Keywords + { "setuid", KeywordCategory::kSystemKeywords, "Set user ID", lsp::CompletionItemKind::kKeyword }, + { "sudo", KeywordCategory::kSystemKeywords, "Super user do", lsp::CompletionItemKind::kKeyword }, + + // Builtin Variables + { "paramcount", KeywordCategory::kBuiltinVariables, "Parameter count", lsp::CompletionItemKind::kVariable }, + { "realparamcount", KeywordCategory::kBuiltinVariables, "Real parameter count", lsp::CompletionItemKind::kVariable }, + { "params", KeywordCategory::kBuiltinVariables, "Parameters array", lsp::CompletionItemKind::kVariable }, + { "system", KeywordCategory::kBuiltinVariables, "System variable", lsp::CompletionItemKind::kVariable }, + { "tslassigning", KeywordCategory::kBuiltinVariables, "TSL assigning", lsp::CompletionItemKind::kVariable }, + { "likeeps", KeywordCategory::kBuiltinVariables, "Like EPS", lsp::CompletionItemKind::kVariable }, + { "likeepsrate", KeywordCategory::kBuiltinVariables, "Like EPS rate", lsp::CompletionItemKind::kVariable }, + + // Builtin Functions + { "echo", KeywordCategory::kBuiltinFunctions, "Echo function", lsp::CompletionItemKind::kFunction }, + { "mtic", KeywordCategory::kBuiltinFunctions, "MTIC function", lsp::CompletionItemKind::kFunction }, + { "mtoc", KeywordCategory::kBuiltinFunctions, "MTOC function", lsp::CompletionItemKind::kFunction }, + + // Boolean Constants + { "false", KeywordCategory::kBooleanConstants, "Boolean false", lsp::CompletionItemKind::kConstant }, + { "true", KeywordCategory::kBooleanConstants, "Boolean true", lsp::CompletionItemKind::kConstant }, + + // Math Constants + { "inf", KeywordCategory::kMathConstants, "Infinity", lsp::CompletionItemKind::kConstant }, + { "nan", KeywordCategory::kMathConstants, "Not a Number", lsp::CompletionItemKind::kConstant }, + + // Null Constants + { "nil", KeywordCategory::kNullConstants, "Null value", lsp::CompletionItemKind::kConstant } + }; + keyword_set_.clear(); + for (const auto& keyword : keywords_) + keyword_set_.insert(ToLowerCase(keyword.keyword)); + initialized_ = true; + } + + std::string TslKeywords::ToLowerCase(const std::string& str) + { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), ::tolower); + return result; + } + + bool TslKeywords::StartsWith(const std::string& str, const std::string& prefix) + { + if (prefix.length() > str.length()) + return false; + return str.compare(0, prefix.length(), prefix) == 0; + } +} diff --git a/lsp-server/src/language/tsl_keywords.hpp b/lsp-server/src/language/tsl_keywords.hpp new file mode 100644 index 0000000..3d6a135 --- /dev/null +++ b/lsp-server/src/language/tsl_keywords.hpp @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include +#include "../lsp/lsp_types.hpp" + +namespace tsl +{ + enum class KeywordCategory + { + kProgramStructure, // program, function, procedure, unit, uses, implementation, interface, initialization, finalization + kDataTypes, // string, integer, boolean, int64, real, array + kClassTypes, // type, class, fakeclass, new + kClassModifiers, // override, overload, virtual, property, self, inherited + kAccessModifiers, // public, protected, private, published + kPropertyAccessors, // read, write + kConstructors, // create, destroy, operator + kVariableModifiers, // external, const, out, var, global, static + kConditionals, // if, else, then, case, of + kLoops, // for, while, do, downto, step, until, repeat, to + kBranchControl, // break, continue, goto, label, exit + kReturnControl, // return, debugreturn, debugrunenv, debugrunenvdo + kBlockControl, // begin, end, with + kReferences, // weakref, autoref + kNamespace, // namespace + kExceptions, // except, raise, try, finally, exceptobject + kLogicalOperators, // and, in, is, not, or + kArithmeticOperators, // div, mod + kBitwiseOperators, // ror, rol, shr, shl + kSetOperators, // union, minus, union2 + kSqlControl, // select, vselect, sselect, update, delete, mselect + kSqlOperators, // on, like, in (SQL context) + kSqlKeywords, // sqlin, from, where, group, by, order, distinct, join + kCallingConventions, // cdecl, pascal, stdcall, safecall, fastcall, register + kSystemKeywords, // setuid, sudo + kBuiltinVariables, // paramcount, realparamcount, params, system, tslassigning, likeeps, likeepsrate + kBuiltinFunctions, // echo, mtic, mtoc + kBooleanConstants, // false, true + kMathConstants, // inf, nan + kNullConstants // nil + }; + + // 关键字信息 + struct KeywordInfo + { + std::string keyword; + KeywordCategory category; + std::string description; + lsp::CompletionItemKind completion_kind; + }; + + class TslKeywords + { + public: + static const std::vector& GetAllKeywords(); + static std::vector GetKeywordsByCategory(KeywordCategory category); + static std::vector GetCompletionItems(const std::string& prefix = ""); + static bool IsKeyword(const std::string& word); + static KeywordCategory GetKeywordCategory(const std::string& word); + + private: + static void InitKeywords(); + static std::string ToLowerCase(const std::string& str); + static bool StartsWith(const std::string& str, const std::string& prefix); + + private: + static std::vector keywords_; + static std::unordered_set keyword_set_; + static bool initialized_; + }; +} diff --git a/lsp-server/src/lsp/dispacther.cpp b/lsp-server/src/lsp/dispacther.cpp new file mode 100644 index 0000000..08f9ffe --- /dev/null +++ b/lsp-server/src/lsp/dispacther.cpp @@ -0,0 +1,63 @@ +#include "./dispacther.hpp" + +namespace lsp { + + + void RequestDispatcher::RegisterProvider(const std::string& method, RequestProvider handler) + { + providers_[method] = std::move(handler); + } + + nlohmann::json RequestDispatcher::Dispatch(const LspRequest& request) + { + auto it = providers_.find(request.method); + if (it != providers_.end()) { + try + { + return it->second(request); + } catch (const std::exception& e) + { + return HandleException(request, e.what()); + } + } + return HandleUnknownMethod(request); + } + + bool RequestDispatcher::SupportsMethod(const std::string& method) const + { + return providers_.find(method) != providers_.end(); + } + + std::vector RequestDispatcher::GetSupportedMethods() const + { + std::vector methods; + for (const auto& pair : providers_) { + methods.push_back(pair.first); + } + return methods; + } + + nlohmann::json RequestDispatcher::HandleUnknownMethod(const LspRequest& request) + { + nlohmann::json resp; + resp["jsonrpc"] = "2.0"; + resp["id"] = request.id; + resp["error"] = { + {"code", -32601}, + {"message", "Method not found: " + request.method} + }; + return resp; + } + + nlohmann::json RequestDispatcher::HandleException(const LspRequest& request, const std::string& error_message) + { + nlohmann::json resp; + resp["jsonrpc"] = "2.0"; + resp["id"] = request.id; + resp["error"] = { + {"code", -32603}, // Internal error + {"message", "Internal error: " + error_message} + }; + return resp; + } +} diff --git a/lsp-server/src/lsp/dispacther.hpp b/lsp-server/src/lsp/dispacther.hpp new file mode 100644 index 0000000..c9bff29 --- /dev/null +++ b/lsp-server/src/lsp/dispacther.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "./lsp_types.hpp" + +namespace lsp +{ + + // 请求处理函数类型 + using RequestProvider = std::function; + + class RequestDispatcher + { + public: + RequestDispatcher() = default; + void RegisterProvider(const std::string& method, RequestProvider provider); // 可选:重命名 + nlohmann::json Dispatch(const LspRequest& request); + bool SupportsMethod(const std::string& method) const; + std::vector GetSupportedMethods() const; + + private: + nlohmann::json HandleUnknownMethod(const LspRequest& request); + nlohmann::json HandleException(const LspRequest& request, const std::string& error_message); + + private: + std::unordered_map providers_; + }; + +} diff --git a/lsp-server/src/lsp/logger.cpp b/lsp-server/src/lsp/logger.cpp new file mode 100644 index 0000000..95cda38 --- /dev/null +++ b/lsp-server/src/lsp/logger.cpp @@ -0,0 +1,60 @@ +#include "./logger.hpp" + +namespace lsp::log +{ + + Logger::~Logger() + { + if (file_stream_.is_open()) + file_stream_.close(); + } + + Logger& Logger::Instance() + { + static Logger logger; + return logger; + } + + void Logger::SetLevel(LogLevel level) + { + std::lock_guard lock(mutex_); + level_ = level; + } + + void Logger::SetLogFile(const std::string& filename) + { + std::lock_guard lock(mutex_); + if (file_stream_.is_open()) + file_stream_.close(); + file_stream_.open(filename, std::ios::app); + use_file_ = file_stream_.is_open(); + } + + void Logger::EnableStderr(bool enable) + { + std::lock_guard lock(mutex_); + use_stderr_ = enable; + } + + const char* Logger::LevelToString(LogLevel level) + { + switch (level) + { + case LogLevel::kOff: + return "OFF"; + case LogLevel::kError: + return "ERROR"; + case LogLevel::kWarn: + return "WARN"; + case LogLevel::kInfo: + return "INFO"; + case LogLevel::kDebug: + return "DEBUG"; + case LogLevel::kVerbose: + return "VERBOSE"; + default: + return "UNKNOWN"; + } + } + +} diff --git a/lsp-server/src/lsp/logger.hpp b/lsp-server/src/lsp/logger.hpp new file mode 100644 index 0000000..ecc94af --- /dev/null +++ b/lsp-server/src/lsp/logger.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include +#include + +namespace lsp::log +{ + enum class LogLevel + { + kOff = 0, + kError = 1, + kWarn = 2, + kInfo = 3, + kDebug = 4, + kVerbose = 5 + }; + + class Logger + { + public: + static Logger& Instance(); + void SetLevel(LogLevel level); + void SetLogFile(const std::string& filename); + void EnableStderr(bool enable); + + template + void log(LogLevel level, Args&&... args) { + if (level > level_) + return; + + std::lock_guard lock(mutex_); + + std::ostringstream oss; + oss << "[TSL-LSP:" << LevelToString(level) << "] "; + (oss << ... << args); + oss << std::endl; + + std::string message = oss.str(); + + if (use_stderr_) + { + std::cerr << message; + } + + if (use_file_ && file_stream_.is_open()) + { + file_stream_ << message; + file_stream_.flush(); + } + } + + private: + Logger() = default; + ~Logger(); + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + + const char* LevelToString(LogLevel level); + + private: + LogLevel level_; + bool use_file_ = false; + bool use_stderr_ = false; + std::ofstream file_stream_; + std::mutex mutex_; + }; + + template + void Error(Args&&... args) { + Logger::Instance().log(LogLevel::kError, std::forward(args)...); + } + + template + void Warn(Args&&... args) { + Logger::Instance().log(LogLevel::kWarn, std::forward(args)...); + } + + template + void Info(Args&&... args) { + Logger::Instance().log(LogLevel::kInfo, std::forward(args)...); + } + + template + void Debug(Args&&... args) { + Logger::Instance().log(LogLevel::kDebug, std::forward(args)...); + } + + template + void Verbose(Args&&... args) { + Logger::Instance().log(LogLevel::kVerbose, std::forward(args)...); + } +} diff --git a/lsp-server/src/lsp/lsp_types.hpp b/lsp-server/src/lsp/lsp_types.hpp new file mode 100644 index 0000000..3f548a9 --- /dev/null +++ b/lsp-server/src/lsp/lsp_types.hpp @@ -0,0 +1,63 @@ +#pragma once +#include +#include +#include + +namespace lsp +{ + // LSP请求结构 + struct LspRequest + { + std::string jsonrpc; + std::string method; + nlohmann::json params; + nlohmann::json id; + + LspRequest(const nlohmann::json& json) : + jsonrpc(json.value("jsonrpc", "2.0")), + method(json.value("method", "")), + params(json.value("params", nlohmann::json::object())), + id(json.value("id", nlohmann::json::array())) {} + }; + + // 补全项类型枚举 + enum class CompletionItemKind + { + kText = 1, + kMethod = 2, + kFunction = 3, + kConstructor = 4, + kField = 5, + kVariable = 6, + kClass = 7, + kInterface = 8, + kModule = 9, + kProperty = 10, + kUnit = 11, + kValue = 12, + kEnum = 13, + kKeyword = 14, + kSnippet = 15, + kColor = 16, + kFile = 17, + kReference = 18, + kFolder = 19, + kEnumMember = 20, + kConstant = 21, + kStruct = 22, + kEvent = 23, + kOperator = 24, + kTypeParameter = 25 + }; + + // 补全项目 + struct CompletionItem + { + std::string label; + CompletionItemKind kind; + std::optional detail; + std::optional documentation; + std::optional insert_text; + }; + +} diff --git a/lsp-server/src/lsp/server.cpp b/lsp-server/src/lsp/server.cpp new file mode 100644 index 0000000..457c98f --- /dev/null +++ b/lsp-server/src/lsp/server.cpp @@ -0,0 +1,126 @@ +#include +#include +#include "../provider/base/provider_registry.hpp" +#include "./server.hpp" +#include "./logger.hpp" + +namespace lsp +{ + LspServer::LspServer() + { + providers::RegisterAllProviders(dispatcher_); + log::Debug("LSP server initialized with providers."); + } + + void LspServer::Run() + { + log::Info("LSP server starting main loop..."); + while (true) + { + try + { + std::optional message = ReadMessage(); + if (!message) continue; + nlohmann::json response = HandleRequest(*message); + if (!response.empty()) + SendResponse(response); + } + catch(const std::exception& e) + { + log::Error("Error processing message: ", e.what()); + } + } + log::Info("LSP server main loop ended"); + } + + std::optional LspServer::ReadMessage() + { + std::string line; + size_t content_length = 0; + + // 读取 LSP Header + while (std::getline(std::cin, line)) + { + log::Verbose("Received header line: ", line); + + // 去掉尾部 \r + if (!line.empty() && line.back() == '\r') + line.pop_back(); + if (line.empty()) + break; + // 查找 Content-Length + if (line.find("Content-Length:") == 0) + { + std::string length_str = line.substr(15); // 跳过 "Content-Length:" + size_t start = length_str.find_first_not_of(" "); + if (start != std::string::npos) + { + length_str = length_str.substr(start); + try + { + content_length = std::stoul(length_str); + log::Verbose("Content-Length: ", content_length); + } + catch (const std::exception& e) + { + log::Error("Failed to parse Content-Length: ", e.what()); + return std::nullopt; + } + } + } + } + + if (content_length == 0) + { + log::Warn("No Content-Length found in header"); + return std::nullopt; + } + + // 读取内容体 + std::string body(content_length, '\0'); + std::cin.read(&body[0], content_length); + + log::Verbose("Message body: ", body); + + if (std::cin.gcount() != static_cast(content_length)) + { + log::Error("Read incomplete message body, expected: ", content_length, ", got: ", std::cin.gcount()); + return std::nullopt; + } + return body; + } + + nlohmann::json LspServer::HandleRequest(const std::string& raw_request) + { + try + { + nlohmann::json json = nlohmann::json::parse(raw_request); + LspRequest request(json); + log::Debug("Processing method: ", request.method); + + nlohmann::json response = dispatcher_.Dispatch(request); + return response; + } + catch (const std::exception& e) + { + log::Error("Failed to handle request: ", e.what()); + return nlohmann::json(); + } + } + + void LspServer::SendResponse(const nlohmann::json& response) + { + std::string response_str = response.dump(); + size_t byte_length = response_str.length(); + + // 调试:显示实际发送的原始内容 + log::Debug("Response length: ", byte_length); + log::Debug("Raw response content: [", response_str, "]"); + + std::cout << "Content-Length: " << byte_length << "\r\n\r\n"; + std::cout << response_str; + std::cout.flush(); + + log::Verbose("Response sent successfully"); + } +} diff --git a/lsp-server/src/lsp/server.hpp b/lsp-server/src/lsp/server.hpp new file mode 100644 index 0000000..9d2941c --- /dev/null +++ b/lsp-server/src/lsp/server.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include "./dispacther.hpp" + +namespace lsp +{ + + class LspServer + { + public: + LspServer(); + ~LspServer() = default; + void Run(); + + private: + // 读取LSP消息 + std::optional ReadMessage(); + // 处理LSP请求 + nlohmann::json HandleRequest(const std::string& raw_request); + // 发送LSP响应 + void SendResponse(const nlohmann::json& response); + + private: + RequestDispatcher dispatcher_; + }; +} diff --git a/lsp-server/src/main.cpp b/lsp-server/src/main.cpp new file mode 100644 index 0000000..3aac54e --- /dev/null +++ b/lsp-server/src/main.cpp @@ -0,0 +1,66 @@ +#include +#include +#include "./lsp/server.hpp" +#include "./lsp/logger.hpp" + +void ParseArgs(int argc, char* argv[]) +{ + for (int i = 1; i < argc; ++i) + { + std::string arg = argv[i]; + + if (arg == "--log=verbose") + { + lsp::log::Logger::Instance().SetLevel(lsp::log::LogLevel::kVerbose); + } else if (arg == "--log=debug") + { + lsp::log::Logger::Instance().SetLevel(lsp::log::LogLevel::kDebug); + } else if (arg == "--log=info") + { + lsp::log::Logger::Instance().SetLevel(lsp::log::LogLevel::kInfo); + } else if (arg == "--log=warn") + { + lsp::log::Logger::Instance().SetLevel(lsp::log::LogLevel::kWarn); + } else if (arg == "--log=error") + { + lsp::log::Logger::Instance().SetLevel(lsp::log::LogLevel::kError); + } else if (arg == "--log=off") + { + lsp::log::Logger::Instance().SetLevel(lsp::log::LogLevel::kOff); + } else if (arg.find("--log-file=") == 0) + { + std::string filename = arg.substr(11); // 跳过 "--log-file=" + lsp::log::Logger::Instance().SetLogFile(filename); + } + else if (arg == "--log-stderr") + { + lsp::log::Logger::Instance().EnableStderr(true); + } + } +} + +int main(int argc, char* argv[]) +{ + ParseArgs(argc, argv); + try + { + lsp::log::Info("TSL-LSP server starting..."); + lsp::LspServer server; + server.Run(); + } + catch (const std::exception& e) + { + std::cerr << "[TSL-LSP] Server fatal error: " << e.what() << std::endl; + lsp::log::Error("Server fatal error: ", e.what()); + return 1; + } + catch (...) + { + std::cerr << "[TSL-LSP] Server unknown fatal error" << std::endl; + lsp::log::Error("Server unknown fatal error"); + return 1; + } + + lsp::log::Info("TSL-LSP server stopped normally"); + return 0; +} diff --git a/lsp-server/src/provider/base/provider_interface.hpp b/lsp-server/src/provider/base/provider_interface.hpp new file mode 100644 index 0000000..eb5ab27 --- /dev/null +++ b/lsp-server/src/provider/base/provider_interface.hpp @@ -0,0 +1,23 @@ + +#pragma once +#include +#include "../../lsp/lsp_types.hpp" + +namespace lsp::providers +{ + + // LSP请求提供者接口基类 + class ILspProvider + { + public: + virtual ~ILspProvider() = default; + + // 处理LSP请求 + virtual nlohmann::json ProvideResponse(const LspRequest& request) = 0; + // 获取支持的LSP方法名 + virtual std::string GetMethod() const = 0; + // 获取提供者名称(用于日志和调试) + virtual std::string GetProviderName() const = 0; + }; + +} diff --git a/lsp-server/src/provider/base/provider_registry.cpp b/lsp-server/src/provider/base/provider_registry.cpp new file mode 100644 index 0000000..8e13975 --- /dev/null +++ b/lsp-server/src/provider/base/provider_registry.cpp @@ -0,0 +1,27 @@ +#include "./provider_registry.hpp" +#include "../initialize/initialize_provider.hpp" +#include "../initialized/initialized_provider.hpp" +#include "../text_document/did_open_provider.hpp" +#include "../text_document/did_change_provider.hpp" +#include "../text_document/completion_provider.hpp" +#include "../trace/set_trace_provider.hpp" +#include "../../lsp/logger.hpp" + +namespace lsp::providers +{ + + void RegisterAllProviders(RequestDispatcher& dispatcher) + { + log::Info("Registering LSP providers..."); + + RegisterProvider(dispatcher); + RegisterProvider(dispatcher); + RegisterProvider(dispatcher); + RegisterProvider(dispatcher); + RegisterProvider(dispatcher); + RegisterProvider(dispatcher); + + log::Info("Successfully registered ", dispatcher.GetSupportedMethods().size(), " LSP providers"); + } + +} diff --git a/lsp-server/src/provider/base/provider_registry.hpp b/lsp-server/src/provider/base/provider_registry.hpp new file mode 100644 index 0000000..be57f8c --- /dev/null +++ b/lsp-server/src/provider/base/provider_registry.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "../../lsp/dispacther.hpp" +#include "./provider_interface.hpp" +#include "../../lsp/logger.hpp" + +namespace lsp::providers +{ + + // 模板函数:注册provider + template + void RegisterProvider(RequestDispatcher& dispatcher) + { + static_assert(std::is_base_of_v, + "Provider must inherit from ILspProvider"); + + auto provider = std::make_shared(); + + log::Info("Registering ", provider->GetProviderName(), " for method: ", provider->GetMethod()); + + dispatcher.RegisterProvider( + provider->GetMethod(), + [provider](const LspRequest& request) -> nlohmann::json { + return provider->ProvideResponse(request); + }); + } + + // 批量注册provider + void RegisterAllProviders(RequestDispatcher& dispatcher); + +} diff --git a/lsp-server/src/provider/initialize/initialize_provider.cpp b/lsp-server/src/provider/initialize/initialize_provider.cpp new file mode 100644 index 0000000..2ff57da --- /dev/null +++ b/lsp-server/src/provider/initialize/initialize_provider.cpp @@ -0,0 +1,55 @@ +#include "./initialize_provider.hpp" +#include "../../lsp/logger.hpp" + +namespace lsp::providers::initialize +{ + + nlohmann::json InitializeProvider::ProvideResponse(const LspRequest& request) + { + nlohmann::json response; + response["jsonrpc"] = "2.0"; + response["id"] = request.id; + response["result"] = BuildInitializeResult(); + log::Debug("InitializeProvider: Providing response for method '", request.method, "' with id ", request.id); + return response; + } + + inline std::string InitializeProvider::GetMethod() const + { + return "initialize"; + } + + inline std::string InitializeProvider::GetProviderName() const + { + return "InitializeProvider"; + } + + nlohmann::json InitializeProvider::BuildInitializeResult() + { + nlohmann::json result; + result["capabilities"] = BuildServerCapabilities(); + result["serverInfo"] = BuildServerInfo(); + return result; + } + + nlohmann::json InitializeProvider::BuildServerCapabilities() + { + nlohmann::json capabilities; + capabilities["textDocumentSync"] = nlohmann::json(); + capabilities["textDocumentSync"]["change"] = 2; + capabilities["textDocumentSync"]["openClose"] = true; + capabilities["textDocumentSync"]["save"] = true; + capabilities["completionProvider"] = nlohmann::json(); + capabilities["completionProvider"]["resolveProvider"] = false; + return capabilities; + } + + nlohmann::json InitializeProvider::BuildServerInfo() + { + nlohmann::json serverInfo; + serverInfo["name"] = "TSL Language Server"; + serverInfo["version"] = "1.0.0"; + return serverInfo; + } + +} diff --git a/lsp-server/src/provider/initialize/initialize_provider.hpp b/lsp-server/src/provider/initialize/initialize_provider.hpp new file mode 100644 index 0000000..79f2904 --- /dev/null +++ b/lsp-server/src/provider/initialize/initialize_provider.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "../base/provider_interface.hpp" +#include + +namespace lsp::providers::initialize +{ + class InitializeProvider : public ILspProvider + { + public: + InitializeProvider() = default; + nlohmann::json ProvideResponse(const LspRequest& request) override; + std::string GetMethod() const override; + std::string GetProviderName() const override; + + private: + nlohmann::json BuildServerCapabilities(); + nlohmann::json BuildServerInfo(); + nlohmann::json BuildInitializeResult(); + }; +} diff --git a/lsp-server/src/provider/initialized/initialized_provider.cpp b/lsp-server/src/provider/initialized/initialized_provider.cpp new file mode 100644 index 0000000..4c9a35b --- /dev/null +++ b/lsp-server/src/provider/initialized/initialized_provider.cpp @@ -0,0 +1,23 @@ +#include "./initialized_provider.hpp" +#include "../../lsp/logger.hpp" + +namespace lsp::providers::initialized +{ + + nlohmann::json InitializedProvider::ProvideResponse(const LspRequest& request) + { + log::Debug("InitializedProvider: Providing response for method '", request.method, "' with id ", request.id); + return nlohmann::json(); + } + + inline std::string InitializedProvider::GetMethod() const + { + return "initialized"; + } + + inline std::string InitializedProvider::GetProviderName() const + { + return "InitializedProvider"; + } + +} diff --git a/lsp-server/src/provider/initialized/initialized_provider.hpp b/lsp-server/src/provider/initialized/initialized_provider.hpp new file mode 100644 index 0000000..6e8e5f5 --- /dev/null +++ b/lsp-server/src/provider/initialized/initialized_provider.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "../base/provider_interface.hpp" + +namespace lsp::providers::initialized +{ + class InitializedProvider : public ILspProvider + { + public: + InitializedProvider() = default; + nlohmann::json ProvideResponse(const LspRequest& request) override; + std::string GetMethod() const override; + std::string GetProviderName() const override; + }; +} diff --git a/lsp-server/src/provider/text_document/completion_provider.cpp b/lsp-server/src/provider/text_document/completion_provider.cpp new file mode 100644 index 0000000..1970ef6 --- /dev/null +++ b/lsp-server/src/provider/text_document/completion_provider.cpp @@ -0,0 +1,212 @@ +#include "./completion_provider.hpp" +#include "../../lsp/logger.hpp" + +namespace lsp::providers::text_document +{ + nlohmann::json CompletionProvider::ProvideResponse(const LspRequest& request) + { + log::Debug("CompletionProvider: Providing response for method '", request.method, "' with id ", request.id); + try + { + nlohmann::json response = BuildCompletionResponse(request); + return response; + } + catch (const std::exception& e) + { + log::Error(GetProviderName(), ": Error - ", e.what()); + nlohmann::json errorResponse = CreateErrorResponse(request.id, -32603, e.what()); + return errorResponse; + } + } + + inline std::string CompletionProvider::GetMethod() const + { + return "textDocument/completion"; + } + + inline std::string CompletionProvider::GetProviderName() const + { + return "CompletionProvider"; + } + + nlohmann::json CompletionProvider::BuildCompletionResponse(const LspRequest& request) + { + nlohmann::json response; + response["jsonrpc"] = "2.0"; + response["id"] = request.id; + + // 验证必要参数 + if (!request.params.contains("textDocument") || !request.params.contains("position")) + { + log::Warn(GetProviderName(), ": Missing required parameters in request"); + // 返回空补全列表而非错误 + response["result"] = BuildCompletionResult({}); + return response; + } + + // 提取参数 + std::string uri = ExtractDocumentUri(request.params); + nlohmann::json position = ExtractPosition(request.params); + std::string prefix = ExtractPrefix(request.params); + + log::Verbose( + GetProviderName(), ": Processing completion request for URI='", uri, "', Position=", position.dump(), ", Prefix='", prefix, "'"); + + // 收集所有补全项 + std::vector allItems; + + // 添加关键字补全 + auto keywordItems = ProvideKeywordCompletions(prefix); + allItems.insert(allItems.end(), keywordItems.begin(), keywordItems.end()); + + // 添加上下文相关补全 + auto contextualItems = ProvideContextualCompletions(uri, position, prefix); + allItems.insert(allItems.end(), contextualItems.begin(), contextualItems.end()); + + // 构建响应 + response["result"] = BuildCompletionResult(allItems); + + log::Info(GetProviderName(), ": Provided ", allItems.size(), " completion items"); + + return response; + } + + nlohmann::json CompletionProvider::BuildCompletionResult(const std::vector& items) + { + nlohmann::json result; + result["isIncomplete"] = false; // 表示这是完整的补全列表 + result["items"] = nlohmann::json::array(); + + for (const auto& item : items) + result["items"].push_back(CompletionItemToJson(item)); + + return result; + } + + std::string CompletionProvider::ExtractDocumentUri(const nlohmann::json& params) + { + if (params.contains("textDocument") && params["textDocument"].contains("uri")) + { + std::string uri = params["textDocument"]["uri"].get(); + log::Verbose("ExtractDocumentUri: Found URI: ", uri); + return uri; + } + log::Warn("ExtractDocumentUri: No URI found in parameters"); + return ""; + } + + nlohmann::json CompletionProvider::ExtractPosition(const nlohmann::json& params) + { + if (params.contains("position")) + { + nlohmann::json pos = params["position"]; + log::Verbose("ExtractPosition: Found position: ", pos.dump()); + return pos; + } + // 返回默认位置 + nlohmann::json defaultPos; + defaultPos["line"] = 0; + defaultPos["character"] = 0; + log::Warn("ExtractPosition: No position found in parameters, using default (0, 0)"); + return defaultPos; + } + + std::string CompletionProvider::ExtractPrefix(const nlohmann::json& params) + { + // 方法1: 直接从参数中获取prefix + if (params.contains("prefix")) + { + std::string prefix = params["prefix"].get(); + log::Verbose("ExtractPrefix: Found prefix form params: '", prefix, "'"); + return prefix; + } + + // 方法2: 从context中获取prefix + if (params.contains("context") && params["context"].contains("prefix")) + { + std::string prefix = params["context"]["prefix"].get(); + log::Verbose("ExtractPrefix: Found prefix form params: '", prefix, "'"); + return prefix; + } + + // TODO: 理想情况下,应该从文档内容和位置计算前缀 + // 这需要维护文档内容的状态 + log::Verbose("ExtractPrefix: No prefix found, returning empty string"); + return ""; + } + + std::vector CompletionProvider::ProvideKeywordCompletions(const std::string& prefix) + { + std::vector items; + + // 从tsl_keywords_获取补全项 + auto tslItems = tsl_keywords_.GetCompletionItems(prefix); + + for (const auto& tslItem : tslItems) + { + CompletionItem item; + item.label = tslItem.label; + item.kind = CompletionItemKind::kKeyword; // LSP CompletionItemKind.Keyword + item.detail = "TSL Keyword"; + item.documentation = "TSL language keyword"; + item.insert_text = tslItem.label; + + items.push_back(item); + } + log::Debug("ProvideKeywordCompletions: Found ", items.size(), " keyword completions"); + return items; + } + + std::vector CompletionProvider::ProvideContextualCompletions( + const std::string& uri, + const nlohmann::json& position, + const std::string& prefix) + { + log::Debug("ProvideContextualCompletions: Processing contextual completions for URI: ", uri); + std::vector items; + + // TODO: 基于上下文提供补全 + // 这里可以添加: + // - 变量名补全 + // - 函数名补全 + // - 类型补全 + // - 属性补全等 + log::Debug("ProvideContextualCompletions: Found ", items.size(), " contextual completions"); + return items; + } + + nlohmann::json CompletionProvider::CompletionItemToJson(const CompletionItem& item) + { + nlohmann::json json; + + json["label"] = item.label; + json["kind"] = item.kind; + + if (!item.detail) + json["detail"] = item.detail; + + if (!item.documentation) + json["documentation"] = item.documentation; + + if (!item.insert_text) + json["insertText"] = item.insert_text; + + return json; + } + + nlohmann::json CompletionProvider::CreateErrorResponse(const nlohmann::json& id, int code, const std::string& message) + { + nlohmann::json response; + response["jsonrpc"] = "2.0"; + response["id"] = id; + + nlohmann::json error; + error["code"] = code; + error["message"] = GetProviderName() + ": " + message; + + response["error"] = error; + log::Error("CreateErrorResponse: Created error response with code ", code, " and message: ", message); + return response; + } + +} diff --git a/lsp-server/src/provider/text_document/completion_provider.hpp b/lsp-server/src/provider/text_document/completion_provider.hpp new file mode 100644 index 0000000..5ce8ead --- /dev/null +++ b/lsp-server/src/provider/text_document/completion_provider.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include "../base/provider_interface.hpp" +#include "../../lsp/lsp_types.hpp" +#include "../../language/tsl_keywords.hpp" + +namespace lsp::providers::text_document +{ + class CompletionProvider : public ILspProvider + { + public: + CompletionProvider() = default; + + nlohmann::json ProvideResponse(const LspRequest& request) override; + std::string GetMethod() const override; + std::string GetProviderName() const override; + + private: + // 构建完整的补全响应 + nlohmann::json BuildCompletionResponse(const LspRequest& request); + + // 构建补全结果 + nlohmann::json BuildCompletionResult(const std::vector& items); + + // 从请求中提取文档信息 + std::string ExtractDocumentUri(const nlohmann::json& params); + nlohmann::json ExtractPosition(const nlohmann::json& params); + + // 获取补全前缀 + std::string ExtractPrefix(const nlohmann::json& params); + + // 提供不同类型的补全 + std::vector ProvideKeywordCompletions(const std::string& prefix); + std::vector ProvideContextualCompletions( + const std::string& uri, + const nlohmann::json& position, + const std::string& prefix); + + // 将CompletionItem转换为JSON + nlohmann::json CompletionItemToJson(const CompletionItem& item); + + // 创建错误响应 + nlohmann::json CreateErrorResponse(const nlohmann::json& id, int code, const std::string& message); + + private: + tsl::TslKeywords tsl_keywords_; + }; +} diff --git a/lsp-server/src/provider/text_document/did_change_provider.cpp b/lsp-server/src/provider/text_document/did_change_provider.cpp new file mode 100644 index 0000000..abde9cb --- /dev/null +++ b/lsp-server/src/provider/text_document/did_change_provider.cpp @@ -0,0 +1,63 @@ +#include "./did_change_provider.hpp" +#include "./did_open_provider.hpp" +#include "../../lsp/logger.hpp" + +namespace lsp::providers::text_document +{ + + nlohmann::json DidChangeProvider::ProvideResponse(const LspRequest& request) + { + log::Debug("DidChangeProvider: Providing response for method '", request.method, "' with id ", request.id); + try + { + auto params = request.params; + if (params.contains("textDocument") && params.contains("contentChanges")) + { + auto textDoc = params["textDocument"]; + std::string uri = textDoc["uri"]; + auto changes = params["contentChanges"]; + + ApplyContentChanges(uri, changes); + } + } + catch (const std::exception& e) + { + // 处理错误,但不返回错误响应,因为这是通知 + } + + // 通知不需要响应 + return nlohmann::json(); + } + + inline std::string DidChangeProvider::GetMethod() const + { + return "textDocument/didChange"; + } + + inline std::string DidChangeProvider::GetProviderName() const + { + return "DidChangeProvider"; + } + + void DidChangeProvider::ApplyContentChanges(const std::string& uri, const nlohmann::json& changes) + { + // 简化实现:假设是全文替换 + for (const auto& change : changes) + { + if (change.contains("text")) + { + // 如果没有range,表示全文替换 + if (!change.contains("range")) + { + DidOpenProvider::document_store[uri] = change["text"]; + } + else + { + // 这里可以实现增量更新,现在简化为全文替换 + DidOpenProvider::document_store[uri] = change["text"]; + } + } + } + } + +} diff --git a/lsp-server/src/provider/text_document/did_change_provider.hpp b/lsp-server/src/provider/text_document/did_change_provider.hpp new file mode 100644 index 0000000..57f1d39 --- /dev/null +++ b/lsp-server/src/provider/text_document/did_change_provider.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "../base/provider_interface.hpp" + +namespace lsp::providers::text_document +{ + class DidChangeProvider : public ILspProvider + { + public: + DidChangeProvider() = default; + nlohmann::json ProvideResponse(const LspRequest& request) override; + std::string GetMethod() const override; + std::string GetProviderName() const override; + + private: + void ApplyContentChanges(const std::string& uri, const nlohmann::json& changes); + }; +} diff --git a/lsp-server/src/provider/text_document/did_open_provider.cpp b/lsp-server/src/provider/text_document/did_open_provider.cpp new file mode 100644 index 0000000..81957fc --- /dev/null +++ b/lsp-server/src/provider/text_document/did_open_provider.cpp @@ -0,0 +1,48 @@ +#include "./did_open_provider.hpp" +#include "../../lsp/logger.hpp" + +namespace lsp::providers::text_document +{ + std::unordered_map DidOpenProvider::document_store; + + nlohmann::json DidOpenProvider::ProvideResponse(const LspRequest& request) + { + log::Debug("DidOpenProvider: Providing response for method '", request.method, "' with id ", request.id); + try + { + auto params = request.params; + if (params.contains("textDocument")) + { + auto textDoc = params["textDocument"]; + std::string uri = textDoc["uri"]; + std::string text = textDoc["text"]; + + // 存储文档内容 + document_store[uri] = text; + } + } + catch (const std::exception& e) + { + // 处理错误,但不返回错误响应,因为这是通知 + } + // 通知不需要响应 + return nlohmann::json(); + } + + inline std::string DidOpenProvider::GetMethod() const + { + return "textDocument/didOpen"; + } + + inline std::string DidOpenProvider::GetProviderName() const + { + return "DidOpenProvider"; + } + + std::string DidOpenProvider::GetDocumentContent(const std::string& uri) + { + auto it = document_store.find(uri); + return (it != document_store.end()) ? it->second : ""; + } + +} diff --git a/lsp-server/src/provider/text_document/did_open_provider.hpp b/lsp-server/src/provider/text_document/did_open_provider.hpp new file mode 100644 index 0000000..9345c31 --- /dev/null +++ b/lsp-server/src/provider/text_document/did_open_provider.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include "../base/provider_interface.hpp" + +namespace lsp::providers::text_document +{ + class DidOpenProvider : public ILspProvider + { + public: + DidOpenProvider() = default; + nlohmann::json ProvideResponse(const LspRequest& request) override; + std::string GetMethod() const override; + std::string GetProviderName() const override; + + // 静态方法用于获取文档内容 + static std::string GetDocumentContent(const std::string& uri); + + public: + static std::unordered_map document_store; + }; +} diff --git a/lsp-server/src/provider/trace/set_trace_provider.cpp b/lsp-server/src/provider/trace/set_trace_provider.cpp new file mode 100644 index 0000000..a8ac5d3 --- /dev/null +++ b/lsp-server/src/provider/trace/set_trace_provider.cpp @@ -0,0 +1,37 @@ +#include "./set_trace_provider.hpp" + +namespace lsp::providers::trace +{ + + nlohmann::json SetTraceProvider::ProvideResponse(const LspRequest& request) + { + try + { + auto params = request.params; + if (params.contains("value")) + { + std::string trace_value = params["value"]; + // 这里可以设置跟踪级别 + // 例如:设置全局跟踪变量 + } + } + catch (const std::exception& e) + { + // 处理错误 + } + + // 通知不需要响应 + return nlohmann::json(); + } + + inline std::string SetTraceProvider::GetMethod() const + { + return "$/setTrace"; + } + + inline std::string SetTraceProvider::GetProviderName() const + { + return "SetTraceProvider"; + } + +} diff --git a/lsp-server/src/provider/trace/set_trace_provider.hpp b/lsp-server/src/provider/trace/set_trace_provider.hpp new file mode 100644 index 0000000..43485c6 --- /dev/null +++ b/lsp-server/src/provider/trace/set_trace_provider.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "../base/provider_interface.hpp" + +namespace lsp::providers::trace +{ + class SetTraceProvider : public ILspProvider + { + public: + SetTraceProvider() = default; + nlohmann::json ProvideResponse(const LspRequest& request) override; + std::string GetMethod() const override; + std::string GetProviderName() const override; + }; +} diff --git a/test/test_lsp.bash b/test/test_lsp.bash new file mode 100644 index 0000000..81003e7 --- /dev/null +++ b/test/test_lsp.bash @@ -0,0 +1,3 @@ +JSON='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":null,"rootUri":"file:///tmp","capabilities":{}}}' +LENGTH=$(echo -n "$JSON" | wc -c) +printf "Content-Length: %d\r\n\r\n%s" $LENGTH "$JSON" | ../lsp-server/build/arch/tsl-server --log=verbose --log-stderr diff --git a/test/test_lsp.tsl b/test/test_lsp.tsl new file mode 100644 index 0000000..e69de29 diff --git a/vscode/.prettierrc b/vscode/.prettierrc new file mode 100644 index 0000000..222250f --- /dev/null +++ b/vscode/.prettierrc @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "bracketSpacing": false, + "arrowParens": "avoid", + "printWidth": 120, + "singleQuote": true, + "trailingComma": "none", + "tabWidth": 2, + "proseWrap": "never", + "semi": false +} diff --git a/vscode/.vscodeignore b/vscode/.vscodeignore index 6c7b69a..fdaad55 100644 --- a/vscode/.vscodeignore +++ b/vscode/.vscodeignore @@ -1 +1,4 @@ +src/ +*.ts +tsconfig*.json .gitignore diff --git a/vscode/CHANGELOG.md b/vscode/CHANGELOG.md index a126d56..64679f7 100644 --- a/vscode/CHANGELOG.md +++ b/vscode/CHANGELOG.md @@ -2,7 +2,11 @@ Notable changes to the `TSL` extension will be documented in this file. -## [1.4.0]: 2025-6-17 +## [2.0.0]: 2025-06-22 + +- 初步支持`LSP`--关键字补全 + +## [1.4.0]: 2025-06-17 - 修复:`[weakref]typename: type;`时,变量和类型高亮失效 - 调整关键字分类 diff --git a/vscode/README.md b/vscode/README.md index 96f0b8a..75cc30a 100644 --- a/vscode/README.md +++ b/vscode/README.md @@ -1,3 +1,6 @@ -# TSL +# TSL-Devkit -`VSCode` 的 `ts[lf]`语法插件 +`tsl`的`vscode`插件工具集 + +- 支持`textMate`语法高亮 +- 支持`LSP`补全 diff --git a/vscode/bin/tsl-server b/vscode/bin/tsl-server new file mode 100644 index 0000000..9339c99 Binary files /dev/null and b/vscode/bin/tsl-server differ diff --git a/vscode/bin/tsl-server.exe b/vscode/bin/tsl-server.exe new file mode 100644 index 0000000..fce21b0 Binary files /dev/null and b/vscode/bin/tsl-server.exe differ diff --git a/vscode/generate_extension.sh b/vscode/generate_extension.sh new file mode 100644 index 0000000..f6472ca --- /dev/null +++ b/vscode/generate_extension.sh @@ -0,0 +1,3 @@ +cp ../lsp-server/build/arch/tsl-server ./bin/tsl-server +cp ../lsp-server/build/msys2/tsl-server.exe ./bin/tsl-server.exe +npx vsce package diff --git a/vscode/package-lock.json b/vscode/package-lock.json index a160bba..b21efc8 100644 --- a/vscode/package-lock.json +++ b/vscode/package-lock.json @@ -1,15 +1,22 @@ { "name": "tsl-devkit", - "version": "1.2.0", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tsl-devkit", - "version": "1.2.0", - "license": "Please see LICENSE.txt", + "version": "1.4.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, "devDependencies": { + "@types/node": "^24.0.3", + "@types/vscode": "^1.101.0", "@vscode/vsce": "^3.5.0", + "cp": "^0.2.0", + "prettier": "^3.5.3", "typescript": "^5.8.3" }, "engines": { @@ -595,6 +602,16 @@ "@textlint/ast-node-types": "^14.8.1" } }, + "node_modules/@types/node": { + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -609,6 +626,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/vscode": { + "version": "1.101.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.101.0.tgz", + "integrity": "sha512-ZWf0IWa+NGegdW3iU42AcDTFHWW7fApLdkdnBqwYEtHVIBGbTu0ZNQKP/kX3Ds/uMJXIMQNAojHR4vexCEEz5Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@typespec/ts-http-runtime": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.3.tgz", @@ -954,7 +978,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -1275,6 +1298,13 @@ "dev": true, "license": "MIT" }, + "node_modules/cp": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cp/-/cp-0.2.0.tgz", + "integrity": "sha512-4ftCvShHjIZG/zzomHyunNpBof3sOFTTmU6s6q9DdqAL/ANqrKV3pr6Z6kVfBI4hjn59DFLImrBqn7GuuMqSZA==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3074,6 +3104,22 @@ "node": ">=10" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -3350,7 +3396,6 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3917,6 +3962,13 @@ "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, "node_modules/unicorn-magic": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", @@ -3989,6 +4041,66 @@ "url": "https://bevry.me/fund" } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", + "license": "MIT", + "dependencies": { + "minimatch": "^5.1.0", + "semver": "^7.3.7", + "vscode-languageserver-protocol": "3.17.5" + }, + "engines": { + "vscode": "^1.82.0" + } + }, + "node_modules/vscode-languageclient/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/vscode-languageclient/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", diff --git a/vscode/package.json b/vscode/package.json index af6af4e..df05f4a 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -1,14 +1,13 @@ { "name": "tsl-devkit", "displayName": "TSL", - "version": "1.4.0", + "version": "2.0.0", "description": "TSL syntax highlighter support for VSCode.", "publisher": "csh", "homepage": "https://git.mytsl.cn/csh/tsl-devkit", "author": { "name": "csh" }, - "scripts": {}, "repository": { "type": "git", "url": "https://git.mytsl.cn/csh/tsl-devkit" @@ -21,6 +20,10 @@ "categories": [ "Programming Languages" ], + "activationEvents": [ + "onLanguage:tsl" + ], + "main": "./out/extension.js", "contributes": { "languages": [ { @@ -42,13 +45,30 @@ "scopeName": "source.tsl", "path": "./syntaxes/tsl.tmLanguage.json" } + ], + "commands": [ + { + "command": "extension.start", + "title": "Start TSL Language Server" + } ] }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ." + }, "devDependencies": { "@vscode/vsce": "^3.5.0", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "@types/node": "^24.0.3", + "@types/vscode": "^1.101.0", + "prettier": "^3.5.3" }, "engines": { "vscode": "^1.101.0" + }, + "dependencies": { + "vscode-languageclient": "^9.0.1" } } diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts new file mode 100644 index 0000000..abe62d7 --- /dev/null +++ b/vscode/src/extension.ts @@ -0,0 +1,66 @@ +import * as path from 'path' +import * as vscode from 'vscode' +import * as fs from 'fs' +import {LanguageClient, LanguageClientOptions, ServerOptions, TransportKind} from 'vscode-languageclient/node' + +let client: LanguageClient + +function findSystemTslServer(): string | null { + const binary = process.platform === 'win32' ? 'tsl-server.exe' : 'tsl-server' + const pathDirs = process.env.PATH?.split(path.delimiter) || [] + + for (const dir of pathDirs) { + const fullPath = path.join(dir, binary) + if (fs.existsSync(fullPath)) { + return fullPath + } + } + + return null +} + +export function activate(context: vscode.ExtensionContext) { + const bundledPath = context.asAbsolutePath( + path.join('bin', process.platform === 'win32' ? 'tsl-server.exe' : 'tsl-server') + ) + + // 查找路径优先级:插件目录 -> 系统 PATH + let serverExe: string | null = null + if (fs.existsSync(bundledPath)) { + serverExe = bundledPath + } else { + serverExe = findSystemTslServer() + } + + if (!serverExe) { + vscode.window.showErrorMessage( + "Cannot find tsl-server. Please install it globally or include it in your extension's server directory." + ) + return + } + + const serverOptions: ServerOptions = { + run: {command: serverExe, transport: TransportKind.stdio, args: ['--log=verbose', '--log-stderr']}, + debug: {command: serverExe, transport: TransportKind.stdio} + } + + const clientOptions: LanguageClientOptions = { + documentSelector: [{scheme: 'file', language: 'tsl'}], + synchronize: { + fileEvents: vscode.workspace.createFileSystemWatcher('**/*.tsl') + } + } + + client = new LanguageClient( + 'tslLanguageServer', // 客户端唯一ID + 'TSL Language Server', // 用户可见名称 + serverOptions, + clientOptions + ) + + client.start() +} + +export function deactivate(): Thenable | undefined { + return client ? client.stop() : undefined +} diff --git a/vscode/tsconfig.json b/vscode/tsconfig.json new file mode 100644 index 0000000..29338b8 --- /dev/null +++ b/vscode/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES6", + "outDir": "out", + "rootDir": "src", + "sourceMap": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/vscode/tsl-devkit-1.4.0.vsix b/vscode/tsl-devkit-1.4.0.vsix deleted file mode 100644 index baf7996..0000000 Binary files a/vscode/tsl-devkit-1.4.0.vsix and /dev/null differ diff --git a/vscode/tsl-devkit-2.0.0.vsix b/vscode/tsl-devkit-2.0.0.vsix new file mode 100644 index 0000000..cc166e5 Binary files /dev/null and b/vscode/tsl-devkit-2.0.0.vsix differ