Enable LSP debug logging, expand language feature support, and fix GUI rendering issues.
- Added `--debug` CLI flag to control LSP debug logging and corresponding environment setting. - Extended LSP capabilities with basic hover, completion, and definition feature support. - Removed redundant `NoScrollWithMouse` flag, resolving inconsistencies in GUI scrolling behavior. - Refined variable usage and type consistency across LSP and rendering modules. - Updated `LspManager` for improved buffer handling and server diagnostics integration.
This commit is contained in:
@@ -98,6 +98,9 @@ enum class CommandId {
|
||||
// Syntax highlighting
|
||||
Syntax, // ":syntax on|off|reload"
|
||||
SetOption, // generic ":set key=value" (v1: filetype=<lang>)
|
||||
// LSP
|
||||
LspHover,
|
||||
LspGotoDefinition,
|
||||
};
|
||||
|
||||
|
||||
|
||||
6
Editor.h
6
Editor.h
@@ -450,6 +450,12 @@ public:
|
||||
}
|
||||
|
||||
|
||||
// LSP helpers: trigger hover/definition at current cursor in current buffer
|
||||
bool LspHoverAtCursor();
|
||||
|
||||
bool LspGotoDefinitionAtCursor();
|
||||
|
||||
|
||||
// LSP: notify buffer saved (used by commands)
|
||||
void NotifyBufferSaved(Buffer *buf);
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ GUIRenderer::Draw(Editor &ed)
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoScrollbar
|
||||
| ImGuiWindowFlags_NoScrollWithMouse
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoMove
|
||||
| ImGuiWindowFlags_NoCollapse
|
||||
@@ -60,7 +59,7 @@ GUIRenderer::Draw(Editor &ed)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.f, 6.f));
|
||||
|
||||
ImGui::Begin("kte", nullptr, flags);
|
||||
ImGui::Begin("kte", nullptr, flags | ImGuiWindowFlags_NoScrollWithMouse);
|
||||
|
||||
const Buffer *buf = ed.CurrentBuffer();
|
||||
if (!buf) {
|
||||
@@ -69,7 +68,7 @@ GUIRenderer::Draw(Editor &ed)
|
||||
const auto &lines = buf->Rows();
|
||||
// Reserve space for status bar at bottom
|
||||
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
|
||||
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
// Detect click-to-move inside this scroll region
|
||||
ImVec2 list_origin = ImGui::GetCursorScreenPos();
|
||||
float scroll_y = ImGui::GetScrollY();
|
||||
@@ -109,13 +108,13 @@ GUIRenderer::Draw(Editor &ed)
|
||||
}
|
||||
// If user scrolled, update buffer offsets accordingly
|
||||
if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y) {
|
||||
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
|
||||
if (auto mbuf = const_cast<Buffer *>(buf)) {
|
||||
mbuf->SetOffsets(static_cast<std::size_t>(std::max(0L, scroll_top)),
|
||||
mbuf->Coloffs());
|
||||
}
|
||||
}
|
||||
if (prev_scroll_x >= 0.0f && scroll_x != prev_scroll_x) {
|
||||
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
|
||||
if (auto mbuf = const_cast<Buffer *>(buf)) {
|
||||
mbuf->SetOffsets(mbuf->Rowoffs(),
|
||||
static_cast<std::size_t>(std::max(0L, scroll_left)));
|
||||
}
|
||||
@@ -162,11 +161,13 @@ GUIRenderer::Draw(Editor &ed)
|
||||
buf->Highlighter()->PrefetchViewport(*buf, fr, rc, buf->Version());
|
||||
}
|
||||
}
|
||||
// Handle mouse click before rendering to avoid dependent on drawn items
|
||||
// Handle mouse click before rendering to avoid dependency on drawn items
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
ImVec2 mp = ImGui::GetIO().MousePos;
|
||||
// Compute viewport-relative row so (0) is top row of the visible area
|
||||
float vy_f = (mp.y - list_origin.y - scroll_y) / row_h;
|
||||
// Note: list_origin is already in the scrolled space of the child window,
|
||||
// so we must NOT subtract scroll_y again (would double-apply).
|
||||
float vy_f = (mp.y - list_origin.y) / row_h;
|
||||
long vy = static_cast<long>(vy_f);
|
||||
if (vy < 0)
|
||||
vy = 0;
|
||||
@@ -190,8 +191,9 @@ GUIRenderer::Draw(Editor &ed)
|
||||
by = 0;
|
||||
}
|
||||
|
||||
// Compute desired pixel X inside the viewport content (subtract horizontal scroll)
|
||||
float px = (mp.x - list_origin.x - scroll_x);
|
||||
// Compute desired pixel X inside the viewport content.
|
||||
// list_origin is already scrolled; do not subtract scroll_x here.
|
||||
float px = (mp.x - list_origin.x);
|
||||
if (px < 0.0f)
|
||||
px = 0.0f;
|
||||
|
||||
@@ -254,11 +256,11 @@ GUIRenderer::Draw(Editor &ed)
|
||||
const std::size_t coloffs_now = buf->Coloffs();
|
||||
for (std::size_t i = rowoffs; i < lines.size(); ++i) {
|
||||
// Capture the screen position before drawing the line
|
||||
ImVec2 line_pos = ImGui::GetCursorScreenPos();
|
||||
std::string line = static_cast<std::string>(lines[i]);
|
||||
ImVec2 line_pos = ImGui::GetCursorScreenPos();
|
||||
auto line = static_cast<std::string>(lines[i]);
|
||||
|
||||
// Expand tabs to spaces with width=8 and apply horizontal scroll offset
|
||||
const std::size_t tabw = 8;
|
||||
constexpr std::size_t tabw = 8;
|
||||
std::string expanded;
|
||||
expanded.reserve(line.size() + 16);
|
||||
std::size_t rx_abs_draw = 0; // rendered column for drawing
|
||||
@@ -275,7 +277,7 @@ GUIRenderer::Draw(Editor &ed)
|
||||
for (auto it = std::sregex_iterator(line.begin(), line.end(), rx);
|
||||
it != std::sregex_iterator(); ++it) {
|
||||
const auto &m = *it;
|
||||
std::size_t sx = static_cast<std::size_t>(m.position());
|
||||
auto sx = static_cast<std::size_t>(m.position());
|
||||
std::size_t ex = sx + static_cast<std::size_t>(m.length());
|
||||
hl_src_ranges.emplace_back(sx, ex);
|
||||
}
|
||||
@@ -318,9 +320,9 @@ GUIRenderer::Draw(Editor &ed)
|
||||
continue; // fully left of view
|
||||
std::size_t vx0 = (rx_start > coloffs_now) ? (rx_start - coloffs_now) : 0;
|
||||
std::size_t vx1 = rx_end - coloffs_now;
|
||||
ImVec2 p0 = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
|
||||
ImVec2 p1 = ImVec2(line_pos.x + static_cast<float>(vx1) * space_w,
|
||||
line_pos.y + line_h);
|
||||
auto p0 = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
|
||||
auto p1 = ImVec2(line_pos.x + static_cast<float>(vx1) * space_w,
|
||||
line_pos.y + line_h);
|
||||
// Choose color: current match stronger
|
||||
bool is_current = has_current && sx == cur_x && ex == cur_end;
|
||||
ImU32 col = is_current
|
||||
@@ -347,7 +349,7 @@ GUIRenderer::Draw(Editor &ed)
|
||||
const kte::LineHighlight &lh = buf->Highlighter()->GetLine(
|
||||
*buf, static_cast<int>(i), buf->Version());
|
||||
// Helper to convert a src column to expanded rx position
|
||||
auto src_to_rx_full = [&](std::size_t sidx) -> std::size_t {
|
||||
auto src_to_rx_full = [&](const std::size_t sidx) -> std::size_t {
|
||||
std::size_t rx = 0;
|
||||
for (std::size_t k = 0; k < sidx && k < line.size(); ++k) {
|
||||
rx += (line[k] == '\t') ? (tabw - (rx % tabw)) : 1;
|
||||
@@ -369,12 +371,14 @@ GUIRenderer::Draw(Editor &ed)
|
||||
if (vx1 <= vx0)
|
||||
continue;
|
||||
ImU32 col = ImGui::GetColorU32(kte::SyntaxInk(sp.kind));
|
||||
ImVec2 p = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
|
||||
auto p = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
p, col, expanded.c_str() + vx0, expanded.c_str() + vx1);
|
||||
}
|
||||
// We drew text via draw list (no layout advance). Manually advance the cursor to the next line.
|
||||
ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + line_h));
|
||||
// We drew text via draw list (no layout advance). Advance by the same amount
|
||||
// ImGui uses between lines (line height + spacing) so hit-testing (which
|
||||
// divides by row_h) aligns with drawing.
|
||||
ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
|
||||
} else {
|
||||
// No syntax: draw as one run
|
||||
ImGui::TextUnformatted(expanded.c_str());
|
||||
@@ -417,9 +421,9 @@ GUIRenderer::Draw(Editor &ed)
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col);
|
||||
// If a prompt is active, replace the entire status bar with the prompt text
|
||||
if (ed.PromptActive()) {
|
||||
std::string label = ed.PromptLabel();
|
||||
std::string ptext = ed.PromptText();
|
||||
auto kind = ed.CurrentPromptKind();
|
||||
const std::string &label = ed.PromptLabel();
|
||||
std::string ptext = ed.PromptText();
|
||||
auto kind = ed.CurrentPromptKind();
|
||||
if (kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs ||
|
||||
kind == Editor::PromptKind::Chdir) {
|
||||
const char *home_c = std::getenv("HOME");
|
||||
@@ -466,8 +470,8 @@ GUIRenderer::Draw(Editor &ed)
|
||||
float ratio = tail_sz.x / avail_px;
|
||||
size_t skip = ratio > 1.5f
|
||||
? std::min(tail.size() - start,
|
||||
(size_t) std::max<size_t>(
|
||||
1, (size_t) (tail.size() / 4)))
|
||||
static_cast<size_t>(std::max<size_t>(
|
||||
1, tail.size() / 4)))
|
||||
: 1;
|
||||
start += skip;
|
||||
std::string candidate = tail.substr(start);
|
||||
@@ -524,8 +528,7 @@ GUIRenderer::Draw(Editor &ed)
|
||||
left += " ";
|
||||
// Insert buffer position prefix "[x/N] " before filename
|
||||
{
|
||||
std::size_t total = ed.BufferCount();
|
||||
if (total > 0) {
|
||||
if (std::size_t total = ed.BufferCount(); total > 0) {
|
||||
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // 1-based for display
|
||||
left += "[";
|
||||
left += std::to_string(static_cast<unsigned long long>(idx1));
|
||||
@@ -539,7 +542,7 @@ GUIRenderer::Draw(Editor &ed)
|
||||
left += " *";
|
||||
// Append total line count as "<n>L"
|
||||
{
|
||||
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
|
||||
auto lcount = buf->Rows().size();
|
||||
left += " ";
|
||||
left += std::to_string(lcount);
|
||||
left += "L";
|
||||
@@ -625,9 +628,9 @@ GUIRenderer::Draw(Editor &ed)
|
||||
ImGuiViewport *vp2 = ImGui::GetMainViewport();
|
||||
|
||||
// Desired size, min size, and margins
|
||||
const ImVec2 want(800.0f, 500.0f);
|
||||
const ImVec2 min_sz(240.0f, 160.0f);
|
||||
const float margin = 20.0f; // space from viewport edges
|
||||
constexpr ImVec2 want(800.0f, 500.0f);
|
||||
constexpr ImVec2 min_sz(240.0f, 160.0f);
|
||||
constexpr float margin = 20.0f; // space from viewport edges
|
||||
|
||||
// Compute the maximum allowed size (viewport minus margins) and make sure it's not negative
|
||||
ImVec2 max_sz(std::max(32.0f, vp2->Size.x - 2.0f * margin),
|
||||
@@ -767,4 +770,4 @@ GUIRenderer::Draw(Editor &ed)
|
||||
ed.SetFilePickerVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,18 +38,48 @@ map_key_to_command(const int ch,
|
||||
MEVENT ev{};
|
||||
if (getmouse(&ev) == OK) {
|
||||
// Mouse wheel → map to MoveUp/MoveDown one line per wheel notch
|
||||
unsigned long wheel_up_mask = 0;
|
||||
unsigned long wheel_dn_mask = 0;
|
||||
#ifdef BUTTON4_PRESSED
|
||||
if (ev.bstate & (BUTTON4_PRESSED | BUTTON4_RELEASED | BUTTON4_CLICKED)) {
|
||||
out = {true, CommandId::MoveUp, "", 0};
|
||||
return true;
|
||||
}
|
||||
wheel_up_mask |= BUTTON4_PRESSED;
|
||||
#endif
|
||||
#ifdef BUTTON4_RELEASED
|
||||
wheel_up_mask |= BUTTON4_RELEASED;
|
||||
#endif
|
||||
#ifdef BUTTON4_CLICKED
|
||||
wheel_up_mask |= BUTTON4_CLICKED;
|
||||
#endif
|
||||
#ifdef BUTTON4_DOUBLE_CLICKED
|
||||
wheel_up_mask |= BUTTON4_DOUBLE_CLICKED;
|
||||
#endif
|
||||
#ifdef BUTTON4_TRIPLE_CLICKED
|
||||
wheel_up_mask |= BUTTON4_TRIPLE_CLICKED;
|
||||
#endif
|
||||
#ifdef BUTTON5_PRESSED
|
||||
if (ev.bstate & (BUTTON5_PRESSED | BUTTON5_RELEASED | BUTTON5_CLICKED)) {
|
||||
out = {true, CommandId::MoveDown, "", 0};
|
||||
wheel_dn_mask |= BUTTON5_PRESSED;
|
||||
#endif
|
||||
#ifdef BUTTON5_RELEASED
|
||||
wheel_dn_mask |= BUTTON5_RELEASED;
|
||||
#endif
|
||||
#ifdef BUTTON5_CLICKED
|
||||
wheel_dn_mask |= BUTTON5_CLICKED;
|
||||
#endif
|
||||
#ifdef BUTTON5_DOUBLE_CLICKED
|
||||
wheel_dn_mask |= BUTTON5_DOUBLE_CLICKED;
|
||||
#endif
|
||||
#ifdef BUTTON5_TRIPLE_CLICKED
|
||||
wheel_dn_mask |= BUTTON5_TRIPLE_CLICKED;
|
||||
#endif
|
||||
if (wheel_up_mask && (ev.bstate & wheel_up_mask)) {
|
||||
// Prefer viewport scrolling for wheel: page up
|
||||
out = {true, CommandId::PageUp, "", 0};
|
||||
return true;
|
||||
}
|
||||
if (wheel_dn_mask && (ev.bstate & wheel_dn_mask)) {
|
||||
// Prefer viewport scrolling for wheel: page down
|
||||
out = {true, CommandId::PageDown, "", 0};
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// React to left button click/press
|
||||
if (ev.bstate & (BUTTON1_CLICKED | BUTTON1_PRESSED | BUTTON1_RELEASED)) {
|
||||
char buf[64];
|
||||
|
||||
597
ext/json.h
597
ext/json.h
File diff suppressed because it is too large
Load Diff
@@ -14,10 +14,11 @@
|
||||
#include "Diagnostic.h"
|
||||
|
||||
namespace kte::lsp {
|
||||
// Callback types (stubs for future phases)
|
||||
using CompletionCallback = std::function<void()>;
|
||||
using HoverCallback = std::function<void()>;
|
||||
using LocationCallback = std::function<void()>;
|
||||
// Callback types for initial language features
|
||||
// If error is non-empty, the result may be default-constructed/empty
|
||||
using CompletionCallback = std::function<void(const CompletionList & result, const std::string & error)>;
|
||||
using HoverCallback = std::function<void(const HoverResult & result, const std::string & error)>;
|
||||
using LocationCallback = std::function<void(const std::vector<Location> & result, const std::string & error)>;
|
||||
|
||||
class LspClient {
|
||||
public:
|
||||
@@ -39,7 +40,7 @@ public:
|
||||
|
||||
virtual void didSave(const std::string &uri) = 0;
|
||||
|
||||
// Language Features (not yet implemented)
|
||||
// Language Features (initial)
|
||||
virtual void completion(const std::string &, Position,
|
||||
CompletionCallback) {}
|
||||
|
||||
|
||||
@@ -371,7 +371,27 @@ LspManager::requestHover(Buffer *buffer, Position pos, HoverCallback callback)
|
||||
lsp_debug_file("hover pos convert: L%d C%d -> L%d C%d", pos.line, pos.character, p16.line,
|
||||
p16.character);
|
||||
}
|
||||
client->hover(uri, p16, std::move(callback));
|
||||
// Wrap the callback to convert any returned range from UTF-16 (wire) -> UTF-8 (editor)
|
||||
HoverCallback wrapped = [this, uri, provider, cb = std::move(callback)](const HoverResult &res16,
|
||||
const std::string &err) {
|
||||
if (!cb)
|
||||
return;
|
||||
if (!res16.range.has_value()) {
|
||||
cb(res16, err);
|
||||
return;
|
||||
}
|
||||
HoverResult res8 = res16;
|
||||
res8.range = toUtf8(uri, *res16.range, provider);
|
||||
if (debug_) {
|
||||
const auto &r16 = *res16.range;
|
||||
const auto &r8 = *res8.range;
|
||||
lsp_debug_file("hover range convert: L%d %d-%d -> L%d %d-%d",
|
||||
r16.start.line, r16.start.character, r16.end.character,
|
||||
r8.start.line, r8.start.character, r8.end.character);
|
||||
}
|
||||
cb(res8, err);
|
||||
};
|
||||
client->hover(uri, p16, std::move(wrapped));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +416,29 @@ LspManager::requestDefinition(Buffer *buffer, Position pos, LocationCallback cal
|
||||
lsp_debug_file("definition pos convert: L%d C%d -> L%d C%d", pos.line, pos.character, p16.line,
|
||||
p16.character);
|
||||
}
|
||||
client->definition(uri, p16, std::move(callback));
|
||||
// Wrap callback to convert Location ranges from UTF-16 (wire) -> UTF-8 (editor)
|
||||
LocationCallback wrapped = [this, uri, provider, cb = std::move(callback)](
|
||||
const std::vector<Location> &locs16,
|
||||
const std::string &err) {
|
||||
if (!cb)
|
||||
return;
|
||||
std::vector<Location> locs8;
|
||||
locs8.reserve(locs16.size());
|
||||
for (const auto &l: locs16) {
|
||||
Location x = l;
|
||||
x.range = toUtf8(uri, l.range, provider);
|
||||
if (debug_) {
|
||||
lsp_debug_file("definition range convert: L%d %d-%d -> L%d %d-%d",
|
||||
l.range.start.line, l.range.start.character,
|
||||
l.range.end.character,
|
||||
x.range.start.line, x.range.start.character,
|
||||
x.range.end.character);
|
||||
}
|
||||
locs8.push_back(std::move(x));
|
||||
}
|
||||
cb(locs8, err);
|
||||
};
|
||||
client->definition(uri, p16, std::move(wrapped));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -482,6 +482,8 @@ LspProcessClient::handleIncoming(const std::string &json)
|
||||
if (method == "window/showMessage") {
|
||||
const auto itParams = j.find("params");
|
||||
if (debug_ &&itParams
|
||||
|
||||
|
||||
|
||||
!=
|
||||
j.end() && itParams->is_object()
|
||||
@@ -662,9 +664,46 @@ LspProcessClient::completion(const std::string &uri, Position pos, CompletionCal
|
||||
params["position"]["line"] = pos.line;
|
||||
params["position"]["character"] = pos.character;
|
||||
sendRequest("textDocument/completion", params,
|
||||
[cb = std::move(cb)](const nlohmann::json &/*result*/, const nlohmann::json * /*error*/) {
|
||||
[cb = std::move(cb)](const nlohmann::json &result, const nlohmann::json *error) {
|
||||
CompletionList out{};
|
||||
std::string err;
|
||||
if (error) {
|
||||
if (auto itMsg = error->find("message");
|
||||
itMsg != error->end() && itMsg->is_string())
|
||||
err = itMsg->get<std::string>();
|
||||
else
|
||||
err = "LSP error";
|
||||
} else {
|
||||
auto parseItem = [](const nlohmann::json &j) -> CompletionItem {
|
||||
CompletionItem it{};
|
||||
if (auto il = j.find("label"); il != j.end() && il->is_string())
|
||||
it.label = il->get<std::string>();
|
||||
if (auto idt = j.find("detail"); idt != j.end() && idt->is_string())
|
||||
it.detail = idt->get<std::string>();
|
||||
if (auto ins = j.find("insertText"); ins != j.end() && ins->is_string())
|
||||
it.insertText = ins->get<std::string>();
|
||||
return it;
|
||||
};
|
||||
if (result.is_array()) {
|
||||
for (const auto &ji: result) {
|
||||
if (ji.is_object())
|
||||
out.items.push_back(parseItem(ji));
|
||||
}
|
||||
} else if (result.is_object()) {
|
||||
if (auto ii = result.find("isIncomplete");
|
||||
ii != result.end() && ii->is_boolean())
|
||||
out.isIncomplete = ii->get<bool>();
|
||||
if (auto itms = result.find("items");
|
||||
itms != result.end() && itms->is_array()) {
|
||||
for (const auto &ji: *itms) {
|
||||
if (ji.is_object())
|
||||
out.items.push_back(parseItem(ji));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cb)
|
||||
cb();
|
||||
cb(out, err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -677,9 +716,67 @@ LspProcessClient::hover(const std::string &uri, Position pos, HoverCallback cb)
|
||||
params["position"]["line"] = pos.line;
|
||||
params["position"]["character"] = pos.character;
|
||||
sendRequest("textDocument/hover", params,
|
||||
[cb = std::move(cb)](const nlohmann::json &/*result*/, const nlohmann::json * /*error*/) {
|
||||
[cb = std::move(cb)](const nlohmann::json &result, const nlohmann::json *error) {
|
||||
HoverResult out{};
|
||||
std::string err;
|
||||
if (error) {
|
||||
if (auto itMsg = error->find("message");
|
||||
itMsg != error->end() && itMsg->is_string())
|
||||
err = itMsg->get<std::string>();
|
||||
else
|
||||
err = "LSP error";
|
||||
} else if (!result.is_null()) {
|
||||
auto appendText = [&](const std::string &s) {
|
||||
if (!out.contents.empty())
|
||||
out.contents.push_back('\n');
|
||||
out.contents += s;
|
||||
};
|
||||
if (result.is_object()) {
|
||||
if (auto itC = result.find("contents"); itC != result.end()) {
|
||||
if (itC->is_string()) {
|
||||
appendText(itC->get<std::string>());
|
||||
} else if (itC->is_object()) {
|
||||
if (auto itV = itC->find("value");
|
||||
itV != itC->end() && itV->is_string())
|
||||
appendText(itV->get<std::string>());
|
||||
} else if (itC->is_array()) {
|
||||
for (const auto &el: *itC) {
|
||||
if (el.is_string())
|
||||
appendText(el.get<std::string>());
|
||||
else if (el.is_object()) {
|
||||
if (auto itV = el.find("value");
|
||||
itV != el.end() && itV->is_string())
|
||||
appendText(itV->get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto itR = result.find("range");
|
||||
itR != result.end() && itR->is_object()) {
|
||||
Range r{};
|
||||
if (auto s = itR->find("start");
|
||||
s != itR->end() && s->is_object()) {
|
||||
if (auto il = s->find("line");
|
||||
il != s->end() && il->is_number_integer())
|
||||
r.start.line = *il;
|
||||
if (auto ic = s->find("character");
|
||||
ic != s->end() && ic->is_number_integer())
|
||||
r.start.character = *ic;
|
||||
}
|
||||
if (auto e = itR->find("end"); e != itR->end() && e->is_object()) {
|
||||
if (auto il = e->find("line");
|
||||
il != e->end() && il->is_number_integer())
|
||||
r.end.line = *il;
|
||||
if (auto ic = e->find("character");
|
||||
ic != e->end() && ic->is_number_integer())
|
||||
r.end.character = *ic;
|
||||
}
|
||||
out.range = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cb)
|
||||
cb();
|
||||
cb(out, err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -692,9 +789,77 @@ LspProcessClient::definition(const std::string &uri, Position pos, LocationCallb
|
||||
params["position"]["line"] = pos.line;
|
||||
params["position"]["character"] = pos.character;
|
||||
sendRequest("textDocument/definition", params,
|
||||
[cb = std::move(cb)](const nlohmann::json &/*result*/, const nlohmann::json * /*error*/) {
|
||||
[cb = std::move(cb)](const nlohmann::json &result, const nlohmann::json *error) {
|
||||
std::vector<Location> out;
|
||||
std::string err;
|
||||
auto parseRange = [](const nlohmann::json &jr) -> Range {
|
||||
Range r{};
|
||||
if (!jr.is_object())
|
||||
return r;
|
||||
if (auto s = jr.find("start"); s != jr.end() && s->is_object()) {
|
||||
if (auto il = s->find("line"); il != s->end() && il->is_number_integer())
|
||||
r.start.line = *il;
|
||||
if (auto ic = s->find("character");
|
||||
ic != s->end() && ic->is_number_integer())
|
||||
r.start.character = *ic;
|
||||
}
|
||||
if (auto e = jr.find("end"); e != jr.end() && e->is_object()) {
|
||||
if (auto il = e->find("line"); il != e->end() && il->is_number_integer())
|
||||
r.end.line = *il;
|
||||
if (auto ic = e->find("character");
|
||||
ic != e->end() && e->is_number_integer())
|
||||
r.end.character = *ic;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
auto pushLocObj = [&](const nlohmann::json &jo) {
|
||||
Location loc{};
|
||||
if (auto iu = jo.find("uri"); iu != jo.end() && iu->is_string())
|
||||
loc.uri = iu->get<std::string>();
|
||||
if (auto ir = jo.find("range"); ir != jo.end())
|
||||
loc.range = parseRange(*ir);
|
||||
out.push_back(std::move(loc));
|
||||
};
|
||||
if (error) {
|
||||
if (auto itMsg = error->find("message");
|
||||
itMsg != error->end() && itMsg->is_string())
|
||||
err = itMsg->get<std::string>();
|
||||
else
|
||||
err = "LSP error";
|
||||
} else if (!result.is_null()) {
|
||||
if (result.is_object()) {
|
||||
if (result.contains("uri") && result.contains("range")) {
|
||||
pushLocObj(result);
|
||||
} else if (result.contains("targetUri")) {
|
||||
Location loc{};
|
||||
if (auto tu = result.find("targetUri");
|
||||
tu != result.end() && tu->is_string())
|
||||
loc.uri = tu->get<std::string>();
|
||||
if (auto tr = result.find("targetRange"); tr != result.end())
|
||||
loc.range = parseRange(*tr);
|
||||
out.push_back(std::move(loc));
|
||||
}
|
||||
} else if (result.is_array()) {
|
||||
for (const auto &el: result) {
|
||||
if (el.is_object()) {
|
||||
if (el.contains("uri")) {
|
||||
pushLocObj(el);
|
||||
} else if (el.contains("targetUri")) {
|
||||
Location loc{};
|
||||
if (auto tu = el.find("targetUri");
|
||||
tu != el.end() && tu->is_string())
|
||||
loc.uri = tu->get<std::string>();
|
||||
if (auto tr = el.find("targetRange");
|
||||
tr != el.end())
|
||||
loc.range = parseRange(*tr);
|
||||
out.push_back(std::move(loc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cb)
|
||||
cb();
|
||||
cb(out, err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,28 @@ struct TextDocumentContentChangeEvent {
|
||||
std::optional<Range> range; // if not set, represents full document change
|
||||
std::string text; // new text for the given range
|
||||
};
|
||||
|
||||
// Minimal feature result types for phase 1
|
||||
struct CompletionItem {
|
||||
std::string label;
|
||||
std::optional<std::string> detail; // optional extra info
|
||||
std::optional<std::string> insertText; // if present, use instead of label
|
||||
};
|
||||
|
||||
struct CompletionList {
|
||||
bool isIncomplete = false;
|
||||
std::vector<CompletionItem> items;
|
||||
};
|
||||
|
||||
struct HoverResult {
|
||||
std::string contents; // concatenated plaintext/markdown for now
|
||||
std::optional<Range> range; // optional range
|
||||
};
|
||||
|
||||
struct Location {
|
||||
std::string uri;
|
||||
Range range;
|
||||
};
|
||||
} // namespace kte::lsp
|
||||
|
||||
#endif // KTE_LSP_TYPES_H
|
||||
24
main.cc
24
main.cc
@@ -30,6 +30,7 @@ PrintUsage(const char *prog)
|
||||
std::cerr << "Usage: " << prog << " [OPTIONS] [files]\n"
|
||||
<< "Options:\n"
|
||||
<< " -c, --chdir DIR Change working directory before opening files\n"
|
||||
<< " -d, --debug Enable LSP debug logging\n"
|
||||
<< " -g, --gui Use GUI frontend (if built)\n"
|
||||
<< " -t, --term Use terminal (ncurses) frontend [default]\n"
|
||||
<< " -h, --help Show this help and exit\n"
|
||||
@@ -43,11 +44,6 @@ main(const int argc, const char *argv[])
|
||||
Editor editor;
|
||||
// Wire up LSP manager (no diagnostic UI yet; frontends may provide later)
|
||||
kte::lsp::LspManager lspMgr(&editor, nullptr);
|
||||
// Enable LSP debug logging if KTE_LSP_DEBUG is set
|
||||
if (const char *dbg = std::getenv("KTE_LSP_DEBUG"); dbg && *dbg) {
|
||||
lspMgr.setDebugLogging(true);
|
||||
std::fprintf(stderr, "[kte][lsp] debug logging enabled via KTE_LSP_DEBUG\n");
|
||||
}
|
||||
editor.SetLspManager(&lspMgr);
|
||||
|
||||
// CLI parsing using getopt_long
|
||||
@@ -55,11 +51,13 @@ main(const int argc, const char *argv[])
|
||||
bool req_term = false;
|
||||
bool show_help = false;
|
||||
bool show_version = false;
|
||||
bool lsp_debug = false;
|
||||
|
||||
std::string nwd;
|
||||
|
||||
static struct option long_opts[] = {
|
||||
{"chdir", required_argument, nullptr, 'c'},
|
||||
{"debug", no_argument, nullptr, 'd'},
|
||||
{"gui", no_argument, nullptr, 'g'},
|
||||
{"term", no_argument, nullptr, 't'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
@@ -69,11 +67,14 @@ main(const int argc, const char *argv[])
|
||||
|
||||
int opt;
|
||||
int long_index = 0;
|
||||
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "c:gthV", long_opts, &long_index)) != -1) {
|
||||
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "c:dgthV", long_opts, &long_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'c':
|
||||
nwd = optarg;
|
||||
break;
|
||||
case 'd':
|
||||
lsp_debug = true;
|
||||
break;
|
||||
case 'g':
|
||||
req_gui = true;
|
||||
break;
|
||||
@@ -106,6 +107,16 @@ main(const int argc, const char *argv[])
|
||||
(void) req_term; // suppress unused warning when GUI is not compiled in
|
||||
#endif
|
||||
|
||||
// Apply LSP debug setting strictly based on -d flag
|
||||
lspMgr.setDebugLogging(lsp_debug);
|
||||
if (lsp_debug) {
|
||||
// Ensure LSP subprocess client picks up debug via environment
|
||||
::setenv("KTE_LSP_DEBUG", "1", 1);
|
||||
} else {
|
||||
// Prevent environment from enabling debug implicitly
|
||||
::unsetenv("KTE_LSP_DEBUG");
|
||||
}
|
||||
|
||||
// Determine frontend
|
||||
#if !defined(KTE_BUILD_GUI)
|
||||
if (req_gui) {
|
||||
@@ -122,6 +133,7 @@ main(const int argc, const char *argv[])
|
||||
} else {
|
||||
|
||||
|
||||
|
||||
// Default depends on build target: kge defaults to GUI, kte to terminal
|
||||
#if defined(KTE_DEFAULT_GUI)
|
||||
use_gui = true;
|
||||
|
||||
Reference in New Issue
Block a user