Step 114: go-to-line
This commit is contained in:
@@ -217,6 +217,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1
|
||||
- [x] Step 111: **IMPLEMENTED** — Symbol outline panel with LSP + AST fallback (2/2 tests pass)
|
||||
- [x] Step 112: **IMPLEMENTED** — Breadcrumb navigation with scope roles (1/1 tests pass)
|
||||
- [x] Step 113: **IMPLEMENTED** — Project-wide search panel with regex and glob filters (3/3 tests pass)
|
||||
- [x] Step 114: **IMPLEMENTED** — Go-to-line popup with :line:col parsing (4/4 tests pass)
|
||||
|
||||
---
|
||||
|
||||
@@ -313,6 +314,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
|
||||
**Step 111:** Compile and pass (2/2)
|
||||
**Step 112:** Compile and pass (1/1)
|
||||
**Step 113:** Compile and pass (3/3)
|
||||
**Step 114:** Compile and pass (4/4)
|
||||
|
||||
---
|
||||
|
||||
@@ -364,7 +366,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
|
||||
|
||||
## What's Next
|
||||
|
||||
Sprint 4 in progress. Step 113 (project-wide search) done. Next: Step 114 (go-to-line).
|
||||
Sprint 4 in progress. Step 114 (go-to-line) done. Next: Step 115 (wire orchestrator).
|
||||
|
||||
---
|
||||
|
||||
@@ -439,3 +441,4 @@ Sprint 4 in progress. Step 113 (project-wide search) done. Next: Step 114 (go-to
|
||||
| 2026-02-09 | Codex | Step 111: Symbol outline panel with LSP + AST fallback. 2/2 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 112: Breadcrumb navigation with scope roles. 1/1 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 113: Project-wide search panel with regex and glob filters. 3/3 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 114: Go-to-line popup with :line:col parsing. 4/4 tests pass. |
|
||||
|
||||
@@ -614,6 +614,10 @@ add_executable(step113_test tests/step113_test.cpp)
|
||||
target_include_directories(step113_test PRIVATE src)
|
||||
target_link_libraries(step113_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
add_executable(step114_test tests/step114_test.cpp)
|
||||
target_include_directories(step114_test PRIVATE src)
|
||||
target_link_libraries(step114_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
find_package(SDL2 CONFIG REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(glad CONFIG REQUIRED)
|
||||
|
||||
45
editor/src/GoToLine.h
Normal file
45
editor/src/GoToLine.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
// Step 114: Go-to-line parsing helpers
|
||||
|
||||
#include <string>
|
||||
#include <cctype>
|
||||
|
||||
inline std::string trimCopy(const std::string& s) {
|
||||
size_t start = 0;
|
||||
while (start < s.size() && std::isspace((unsigned char)s[start])) ++start;
|
||||
size_t end = s.size();
|
||||
while (end > start && std::isspace((unsigned char)s[end - 1])) --end;
|
||||
return s.substr(start, end - start);
|
||||
}
|
||||
|
||||
inline bool parseLineColInput(const std::string& input, int& outLine, int& outCol) {
|
||||
std::string s = trimCopy(input);
|
||||
if (s.empty()) return false;
|
||||
if (!s.empty() && s[0] == ':') s = s.substr(1);
|
||||
|
||||
size_t colon = s.find(':');
|
||||
std::string lineStr = (colon == std::string::npos) ? s : s.substr(0, colon);
|
||||
std::string colStr = (colon == std::string::npos) ? "" : s.substr(colon + 1);
|
||||
|
||||
if (lineStr.empty()) return false;
|
||||
int line = 0;
|
||||
for (char c : lineStr) {
|
||||
if (!std::isdigit((unsigned char)c)) return false;
|
||||
line = line * 10 + (c - '0');
|
||||
}
|
||||
if (line <= 0) return false;
|
||||
|
||||
int col = 1;
|
||||
if (!colStr.empty()) {
|
||||
col = 0;
|
||||
for (char c : colStr) {
|
||||
if (!std::isdigit((unsigned char)c)) return false;
|
||||
col = col * 10 + (c - '0');
|
||||
}
|
||||
if (col <= 0) return false;
|
||||
}
|
||||
|
||||
outLine = line;
|
||||
outCol = col;
|
||||
return true;
|
||||
}
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "ContextAPI.h"
|
||||
#include "Breadcrumbs.h"
|
||||
#include "ProjectSearch.h"
|
||||
#include "GoToLine.h"
|
||||
#include "ast/Serialization.h"
|
||||
#include "ast/Generator.h"
|
||||
#include "ast/Annotation.h"
|
||||
@@ -113,6 +114,9 @@ struct EditorState {
|
||||
char searchExclude[256] = {};
|
||||
bool searchUseRegex = true;
|
||||
std::vector<ProjectSearch::FileResult> searchResults;
|
||||
bool showGoToLine = false;
|
||||
char goToLineBuf[64] = {};
|
||||
bool goToLineError = false;
|
||||
|
||||
// Bottom panel
|
||||
int bottomTab = 0; // 0=Output, 1=AST, 2=Highlighted
|
||||
@@ -510,6 +514,13 @@ struct EditorState {
|
||||
registerCommand("search.findInFiles", "Search: Find in Files",
|
||||
keys.getBinding("search.findInFiles").toString(),
|
||||
[this]() { showProjectSearch = !showProjectSearch; });
|
||||
registerCommand("nav.goToLine", "Navigate: Go to Line",
|
||||
keys.getBinding("nav.goToLine").toString(),
|
||||
[this]() {
|
||||
showGoToLine = true;
|
||||
goToLineBuf[0] = '\0';
|
||||
goToLineError = false;
|
||||
});
|
||||
registerCommand("view.whitespace", "View: Toggle Whitespace", "",
|
||||
[this]() { showWhitespace = !showWhitespace; });
|
||||
registerCommand("view.minimap", "View: Toggle Minimap", "",
|
||||
@@ -1644,6 +1655,11 @@ int main(int, char**) {
|
||||
else if (action == "edit.redo") state.doRedo();
|
||||
else if (action == "search.find") state.showFind = !state.showFind;
|
||||
else if (action == "search.findInFiles") state.showProjectSearch = !state.showProjectSearch;
|
||||
else if (action == "nav.goToLine") {
|
||||
state.showGoToLine = true;
|
||||
state.goToLineBuf[0] = '\0';
|
||||
state.goToLineError = false;
|
||||
}
|
||||
else if (action == "file.save") state.doSave();
|
||||
else if (action == "file.new") {
|
||||
std::string lang = state.active() ? state.active()->language : "python";
|
||||
@@ -1778,6 +1794,14 @@ int main(int, char**) {
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Navigate")) {
|
||||
if (ImGui::MenuItem("Go to Line...", state.keys.getBinding("nav.goToLine").toString().c_str())) {
|
||||
state.showGoToLine = true;
|
||||
state.goToLineBuf[0] = '\0';
|
||||
state.goToLineError = false;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Language")) {
|
||||
if (ImGui::MenuItem("Python", nullptr, state.active() && state.active()->language == "python"))
|
||||
state.setLanguage("python");
|
||||
@@ -2041,6 +2065,49 @@ int main(int, char**) {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Go To Line (Ctrl+G)
|
||||
// ---------------------------------------------------------------
|
||||
if (state.showGoToLine) {
|
||||
ImGui::OpenPopup("GoToLine");
|
||||
}
|
||||
if (ImGui::BeginPopupModal("GoToLine", &state.showGoToLine, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
int totalLines = state.active() ? countLines(state.active()->editBuf) : 0;
|
||||
ImGui::Text("Enter line or :line:col");
|
||||
ImGui::TextDisabled("Total lines: %d", totalLines);
|
||||
ImGui::SetNextItemWidth(240);
|
||||
bool submit = ImGui::InputText("##gotoLineInput", state.goToLineBuf,
|
||||
sizeof(state.goToLineBuf),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
if (ImGui::Button("Go")) submit = true;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel")) {
|
||||
state.showGoToLine = false;
|
||||
state.goToLineError = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
if (submit) {
|
||||
int line = 0;
|
||||
int col = 0;
|
||||
if (parseLineColInput(state.goToLineBuf, line, col)) {
|
||||
if (totalLines > 0) line = std::max(1, std::min(line, totalLines));
|
||||
col = std::max(1, col);
|
||||
if (state.active()) {
|
||||
state.jumpTo(state.active(), line - 1, col - 1);
|
||||
}
|
||||
state.showGoToLine = false;
|
||||
state.goToLineError = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
} else {
|
||||
state.goToLineError = true;
|
||||
}
|
||||
}
|
||||
if (state.goToLineError) {
|
||||
ImGui::TextColored(ImVec4(0.9f, 0.4f, 0.4f, 1.0f), "Invalid format.");
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// LSP Server Settings
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
45
editor/tests/step114_test.cpp
Normal file
45
editor/tests/step114_test.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Step 114 TDD Test: Go-to-line parsing
|
||||
#include "GoToLine.h"
|
||||
#include <iostream>
|
||||
|
||||
static void expect(bool cond, const std::string& name, int& passed, int& failed) {
|
||||
if (cond) {
|
||||
std::cout << "Test " << (passed + failed + 1) << " PASS: " << name << "\n";
|
||||
++passed;
|
||||
} else {
|
||||
std::cout << "Test " << (passed + failed + 1) << " FAIL: " << name << "\n";
|
||||
++failed;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
{
|
||||
int line = 0, col = 0;
|
||||
bool ok = parseLineColInput("10", line, col) && line == 10 && col == 1;
|
||||
expect(ok, "line only", passed, failed);
|
||||
}
|
||||
|
||||
{
|
||||
int line = 0, col = 0;
|
||||
bool ok = parseLineColInput(":7:3", line, col) && line == 7 && col == 3;
|
||||
expect(ok, "colon prefix", passed, failed);
|
||||
}
|
||||
|
||||
{
|
||||
int line = 0, col = 0;
|
||||
bool ok = parseLineColInput("5:9", line, col) && line == 5 && col == 9;
|
||||
expect(ok, "line and col", passed, failed);
|
||||
}
|
||||
|
||||
{
|
||||
int line = 0, col = 0;
|
||||
bool ok = !parseLineColInput("abc", line, col);
|
||||
expect(ok, "invalid input", passed, failed);
|
||||
}
|
||||
|
||||
std::cout << "\n=== Step 114 Results: " << passed << " passed, " << failed << " failed ===\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -340,7 +340,7 @@ Whetstone-specific AST navigation.
|
||||
Regex support. Include/exclude glob filters.
|
||||
*New:* `ProjectSearch.h`
|
||||
|
||||
- [ ] **Step 114: Go-to-line**
|
||||
- [x] **Step 114: Go-to-line**
|
||||
Ctrl+G opens a quick input: type line number → jump. Supports `:line:col`
|
||||
format. Shows total line count.
|
||||
*Modifies:* `main.cpp`
|
||||
|
||||
Reference in New Issue
Block a user