Step 93: LSP hover and signature help

This commit is contained in:
Bill
2026-02-09 10:02:03 -07:00
parent 0916026448
commit ede4166af3
5 changed files with 201 additions and 18 deletions

View File

@@ -49,6 +49,12 @@ public:
std::string filterText;
};
struct SignatureHelp {
std::string label;
int activeSignature = 0;
int activeParameter = 0;
};
void initialize(const std::string& rootUri,
const std::string& clientName,
int processId) {
@@ -111,6 +117,24 @@ public:
return id;
}
int requestHover(const std::string& uri, int line, int character) {
nlohmann::json params;
params["textDocument"] = {{"uri", uri}};
params["position"] = {{"line", line}, {"character", character}};
int id = sendRequest("textDocument/hover", params);
lastHoverRequestId_ = id;
return id;
}
int requestSignatureHelp(const std::string& uri, int line, int character) {
nlohmann::json params;
params["textDocument"] = {{"uri", uri}};
params["position"] = {{"line", line}, {"character", character}};
int id = sendRequest("textDocument/signatureHelp", params);
lastSignatureRequestId_ = id;
return id;
}
void handleMessage(const std::string& msg) {
nlohmann::json j;
try {
@@ -152,27 +176,47 @@ public:
if (j.contains("id") && j.contains("result")) {
int id = j["id"].get<int>();
if (lastCompletionRequestId_ > 0 && id != lastCompletionRequestId_) return;
const auto& result = j["result"];
const nlohmann::json* items = nullptr;
if (result.is_array()) {
items = &result;
} else if (result.is_object() && result.contains("items")) {
items = &result["items"];
}
if (!items || !items->is_array()) return;
if (id == lastCompletionRequestId_) {
const nlohmann::json* items = nullptr;
if (result.is_array()) {
items = &result;
} else if (result.is_object() && result.contains("items")) {
items = &result["items"];
}
if (!items || !items->is_array()) return;
std::vector<CompletionItem> parsed;
for (const auto& item : *items) {
CompletionItem ci;
ci.label = item.value("label", "");
ci.kind = item.value("kind", 0);
ci.detail = item.value("detail", "");
ci.insertText = item.value("insertText", ci.label);
ci.filterText = item.value("filterText", "");
parsed.push_back(std::move(ci));
std::vector<CompletionItem> parsed;
for (const auto& item : *items) {
CompletionItem ci;
ci.label = item.value("label", "");
ci.kind = item.value("kind", 0);
ci.detail = item.value("detail", "");
ci.insertText = item.value("insertText", ci.label);
ci.filterText = item.value("filterText", "");
parsed.push_back(std::move(ci));
}
completionItems_ = std::move(parsed);
return;
}
if (id == lastHoverRequestId_) {
if (result.contains("contents")) {
hoverContents_ = stringifyMarkup(result["contents"]);
}
return;
}
if (id == lastSignatureRequestId_) {
if (result.contains("signatures") && result["signatures"].is_array() &&
!result["signatures"].empty()) {
const auto& sig = result["signatures"][0];
signatureHelp_.label = sig.value("label", "");
signatureHelp_.activeSignature = result.value("activeSignature", 0);
signatureHelp_.activeParameter = result.value("activeParameter", 0);
}
return;
}
completionItems_ = std::move(parsed);
}
}
@@ -198,6 +242,22 @@ public:
completionItems_.clear();
}
std::string getHoverContents() const {
return hoverContents_;
}
void clearHover() {
hoverContents_.clear();
}
SignatureHelp getSignatureHelp() const {
return signatureHelp_;
}
void clearSignatureHelp() {
signatureHelp_ = SignatureHelp{};
}
void sendNotification(const std::string& method, const nlohmann::json& params) {
nlohmann::json msg;
msg["jsonrpc"] = "2.0";
@@ -229,4 +289,24 @@ private:
std::unordered_map<std::string, std::vector<Diagnostic>> diagnosticsByUri_;
int lastCompletionRequestId_ = -1;
std::vector<CompletionItem> completionItems_;
int lastHoverRequestId_ = -1;
int lastSignatureRequestId_ = -1;
std::string hoverContents_;
SignatureHelp signatureHelp_;
static std::string stringifyMarkup(const nlohmann::json& contents) {
if (contents.is_string()) return contents.get<std::string>();
if (contents.is_object()) {
if (contents.contains("value")) return contents["value"].get<std::string>();
}
if (contents.is_array()) {
std::string out;
for (const auto& item : contents) {
if (!out.empty()) out += "\n";
out += stringifyMarkup(item);
}
return out;
}
return "";
}
};

View File

@@ -91,6 +91,9 @@ struct EditorState {
bool completionVisible = false;
int completionSelected = 0;
double completionLastChange = 0.0;
bool hoverPending = false;
double hoverLastMove = 0.0;
ImVec2 hoverLastMouse = ImVec2(-1.0f, -1.0f);
BufferState* active() { return activeBuffer; }
@@ -1016,6 +1019,17 @@ int main(int, char**) {
state.completionVisible = false;
state.completionSelected = 0;
if (state.lsp) state.lsp->clearCompletionItems();
if (state.lsp && state.active() && state.active()->path.rfind("(untitled", 0) != 0) {
int cursor = state.active()->widget.getCursor();
if (cursor > 0 && state.active()->editBuf[cursor - 1] == '(') {
int lineZero = std::max(0, state.active()->cursorLine - 1);
int colZero = std::max(0, state.active()->cursorCol - 1);
state.lsp->requestSignatureHelp(EditorState::toFileUri(state.active()->path),
lineZero, colZero);
} else if (cursor > 0 && state.active()->editBuf[cursor - 1] == ')') {
state.lsp->clearSignatureHelp();
}
}
}
double now = ImGui::GetTime();
@@ -1083,6 +1097,42 @@ int main(int, char**) {
}
}
if (state.lsp && state.active()) {
if (ImGui::IsWindowHovered()) {
ImVec2 mouse = ImGui::GetMousePos();
if (mouse.x != state.hoverLastMouse.x || mouse.y != state.hoverLastMouse.y) {
state.hoverLastMouse = mouse;
state.hoverLastMove = ImGui::GetTime();
state.hoverPending = true;
state.lsp->clearHover();
}
if (state.hoverPending && (ImGui::GetTime() - state.hoverLastMove) > 0.5) {
if (state.active()->path.rfind("(untitled", 0) != 0) {
int lineZero = std::max(0, state.active()->cursorLine - 1);
int colZero = std::max(0, state.active()->cursorCol - 1);
state.lsp->requestHover(EditorState::toFileUri(state.active()->path),
lineZero, colZero);
}
state.hoverPending = false;
}
std::string hover = state.lsp->getHoverContents();
if (!hover.empty()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(hover.c_str());
ImGui::EndTooltip();
}
}
auto sig = state.lsp->getSignatureHelp();
if (!sig.label.empty()) {
ImGui::SetCursorScreenPos(ImVec2(ImGui::GetWindowPos().x + 20.0f,
ImGui::GetWindowPos().y + ImGui::GetWindowHeight() - 80.0f));
ImGui::BeginChild("##signatureHelp", ImVec2(420, 60), true);
ImGui::TextUnformatted(sig.label.c_str());
ImGui::EndChild();
}
}
ImGui::EndTabItem();
}
if (!open) {