Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8634eb78f0 | |||
| 6eb240a0c4 | |||
| 4c402f5ef3 | |||
| a8abda4b87 | |||
| 7347556aa2 |
@@ -4,7 +4,7 @@ project(kte)
|
|||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(KTE_VERSION "1.5.6")
|
set(KTE_VERSION "1.5.8")
|
||||||
|
|
||||||
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
||||||
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public:
|
|||||||
virtual ~Frontend() = default;
|
virtual ~Frontend() = default;
|
||||||
|
|
||||||
// Initialize the frontend (create window/terminal, etc.)
|
// Initialize the frontend (create window/terminal, etc.)
|
||||||
virtual bool Init(Editor &ed) = 0;
|
virtual bool Init(int &argc, char **argv, Editor &ed) = 0;
|
||||||
|
|
||||||
// Execute one iteration (poll input, dispatch, draw). Set running=false to exit.
|
// Execute one iteration (poll input, dispatch, draw). Set running=false to exit.
|
||||||
virtual void Step(Editor &ed, bool &running) = 0;
|
virtual void Step(Editor &ed, bool &running) = 0;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "GUITheme.h"
|
#include "GUITheme.h"
|
||||||
#include "fonts/Font.h" // embedded default font (DefaultFont)
|
#include "fonts/Font.h" // embedded default font (DefaultFont)
|
||||||
#include "fonts/FontRegistry.h"
|
#include "fonts/FontRegistry.h"
|
||||||
|
#include "fonts/IosevkaExtended.h"
|
||||||
#include "syntax/HighlighterRegistry.h"
|
#include "syntax/HighlighterRegistry.h"
|
||||||
#include "syntax/NullHighlighter.h"
|
#include "syntax/NullHighlighter.h"
|
||||||
|
|
||||||
@@ -29,8 +30,10 @@
|
|||||||
static auto kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
|
static auto kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
|
||||||
|
|
||||||
bool
|
bool
|
||||||
GUIFrontend::Init(Editor &ed)
|
GUIFrontend::Init(int &argc, char **argv, Editor &ed)
|
||||||
{
|
{
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
// Attach editor to input handler for editor-owned features (e.g., universal argument)
|
// Attach editor to input handler for editor-owned features (e.g., universal argument)
|
||||||
input_.Attach(&ed);
|
input_.Attach(&ed);
|
||||||
// editor dimensions will be initialized during the first Step() frame
|
// editor dimensions will be initialized during the first Step() frame
|
||||||
@@ -261,11 +264,11 @@ GUIFrontend::Step(Editor &ed, bool &running)
|
|||||||
|
|
||||||
// Update editor logical rows/cols using current ImGui metrics and display size
|
// Update editor logical rows/cols using current ImGui metrics and display size
|
||||||
{
|
{
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
float line_h = ImGui::GetTextLineHeightWithSpacing();
|
float row_h = ImGui::GetTextLineHeightWithSpacing();
|
||||||
float ch_w = ImGui::CalcTextSize("M").x;
|
float ch_w = ImGui::CalcTextSize("M").x;
|
||||||
if (line_h <= 0.0f)
|
if (row_h <= 0.0f)
|
||||||
line_h = 16.0f;
|
row_h = 16.0f;
|
||||||
if (ch_w <= 0.0f)
|
if (ch_w <= 0.0f)
|
||||||
ch_w = 8.0f;
|
ch_w = 8.0f;
|
||||||
// Prefer ImGui IO display size; fall back to cached SDL window size
|
// Prefer ImGui IO display size; fall back to cached SDL window size
|
||||||
@@ -273,20 +276,20 @@ GUIFrontend::Step(Editor &ed, bool &running)
|
|||||||
float disp_h = io.DisplaySize.y > 0 ? io.DisplaySize.y : static_cast<float>(height_);
|
float disp_h = io.DisplaySize.y > 0 ? io.DisplaySize.y : static_cast<float>(height_);
|
||||||
|
|
||||||
// Account for the GUI window padding and the status bar height used in ImGuiRenderer.
|
// Account for the GUI window padding and the status bar height used in ImGuiRenderer.
|
||||||
// ImGuiRenderer pushes WindowPadding = (6,6) every frame, so use the same constants here
|
|
||||||
// to avoid mismatches that would cause premature scrolling.
|
|
||||||
const float pad_x = 6.0f;
|
const float pad_x = 6.0f;
|
||||||
const float pad_y = 6.0f;
|
const float pad_y = 6.0f;
|
||||||
// Status bar reserves one frame height (with spacing) inside the window
|
|
||||||
float status_h = ImGui::GetFrameHeightWithSpacing();
|
|
||||||
|
|
||||||
float avail_w = std::max(0.0f, disp_w - 2.0f * pad_x);
|
// Use the same logic as ImGuiRenderer for available height and status bar reservation.
|
||||||
float avail_h = std::max(0.0f, disp_h - 2.0f * pad_y - status_h);
|
float wanted_bar_h = ImGui::GetFrameHeight();
|
||||||
|
float total_avail_h = std::max(0.0f, disp_h - 2.0f * pad_y);
|
||||||
|
float actual_avail_h = std::floor((total_avail_h - wanted_bar_h) / row_h) * row_h;
|
||||||
|
|
||||||
// Visible content rows inside the scroll child
|
// Visible content rows inside the scroll child
|
||||||
auto content_rows = static_cast<std::size_t>(std::floor(avail_h / line_h));
|
auto content_rows = static_cast<std::size_t>(std::max(0.0f, std::floor(actual_avail_h / row_h)));
|
||||||
// Editor::Rows includes the status line; add 1 back for it.
|
// Editor::Rows includes the status line; add 1 back for it.
|
||||||
std::size_t rows = std::max<std::size_t>(1, content_rows + 1);
|
std::size_t rows = content_rows + 1;
|
||||||
|
|
||||||
|
float avail_w = std::max(0.0f, disp_w - 2.0f * pad_x);
|
||||||
std::size_t cols = static_cast<std::size_t>(std::max(1.0f, std::floor(avail_w / ch_w)));
|
std::size_t cols = static_cast<std::size_t>(std::max(1.0f, std::floor(avail_w / ch_w)));
|
||||||
|
|
||||||
// Only update if changed to avoid churn
|
// Only update if changed to avoid churn
|
||||||
@@ -357,14 +360,32 @@ GUIFrontend::LoadGuiFont_(const char * /*path*/, const float size_px)
|
|||||||
{
|
{
|
||||||
const ImGuiIO &io = ImGui::GetIO();
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
io.Fonts->Clear();
|
io.Fonts->Clear();
|
||||||
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
|
|
||||||
|
ImFontConfig config;
|
||||||
|
config.MergeMode = false;
|
||||||
|
|
||||||
|
// Load Basic Latin + Latin Supplement
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
kte::Fonts::DefaultFontData,
|
kte::Fonts::DefaultFontData,
|
||||||
kte::Fonts::DefaultFontSize,
|
kte::Fonts::DefaultFontSize,
|
||||||
size_px);
|
size_px,
|
||||||
if (!font) {
|
&config,
|
||||||
font = io.Fonts->AddFontDefault();
|
io.Fonts->GetGlyphRangesDefault());
|
||||||
}
|
|
||||||
(void) font;
|
// Merge Greek and Mathematical symbols from IosevkaExtended
|
||||||
|
config.MergeMode = true;
|
||||||
|
static const ImWchar extended_ranges[] = {
|
||||||
|
0x0370, 0x03FF, // Greek and Coptic
|
||||||
|
0x2200, 0x22FF, // Mathematical Operators
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
|
kte::Fonts::IosevkaExtended::DefaultFontRegularCompressedData,
|
||||||
|
kte::Fonts::IosevkaExtended::DefaultFontRegularCompressedSize,
|
||||||
|
size_px,
|
||||||
|
&config,
|
||||||
|
extended_ranges);
|
||||||
|
|
||||||
io.Fonts->Build();
|
io.Fonts->Build();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ public:
|
|||||||
|
|
||||||
~GUIFrontend() override = default;
|
~GUIFrontend() override = default;
|
||||||
|
|
||||||
bool Init(Editor &ed) override;
|
bool Init(int &argc, char **argv, Editor &ed) override;
|
||||||
|
|
||||||
void Step(Editor &ed, bool &running) override;
|
void Step(Editor &ed, bool &running) override;
|
||||||
|
|
||||||
|
|||||||
233
ImGuiRenderer.cc
233
ImGuiRenderer.cc
@@ -94,8 +94,17 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
ImGui::SetNextWindowScroll(ImVec2(target_x, target_y));
|
ImGui::SetNextWindowScroll(ImVec2(target_x, target_y));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserve space for status bar at bottom
|
// Reserve space for status bar at bottom.
|
||||||
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
|
// We calculate a height that is an exact multiple of the line height
|
||||||
|
// to avoid partial lines and "scroll past end" jitter.
|
||||||
|
float total_avail_h = ImGui::GetContentRegionAvail().y;
|
||||||
|
float wanted_bar_h = ImGui::GetFrameHeight();
|
||||||
|
float child_h_plan = std::max(0.0f, std::floor((total_avail_h - wanted_bar_h) / row_h) * row_h);
|
||||||
|
float real_bar_h = total_avail_h - child_h_plan;
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||||
|
ImGui::BeginChild("scroll", ImVec2(0, child_h_plan), false,
|
||||||
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||||
|
|
||||||
// Get child window position and scroll for click handling
|
// Get child window position and scroll for click handling
|
||||||
@@ -138,90 +147,6 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
}
|
}
|
||||||
prev_buf_rowoffs = buf_rowoffs;
|
prev_buf_rowoffs = buf_rowoffs;
|
||||||
prev_buf_coloffs = buf_coloffs;
|
prev_buf_coloffs = buf_coloffs;
|
||||||
|
|
||||||
// Synchronize cursor and scrolling.
|
|
||||||
// Ensure the cursor is visible, but avoid aggressive centering so that
|
|
||||||
// the same lines remain visible until the cursor actually goes off-screen.
|
|
||||||
{
|
|
||||||
// Compute visible row range using the child window height
|
|
||||||
float child_h = ImGui::GetWindowHeight();
|
|
||||||
long first_row = static_cast<long>(scroll_y / row_h);
|
|
||||||
long vis_rows = static_cast<long>(child_h / row_h);
|
|
||||||
if (vis_rows < 1)
|
|
||||||
vis_rows = 1;
|
|
||||||
long last_row = first_row + vis_rows - 1;
|
|
||||||
|
|
||||||
long cyr = static_cast<long>(cy);
|
|
||||||
if (cyr < first_row) {
|
|
||||||
// Scroll just enough to bring the cursor line to the top
|
|
||||||
float target = static_cast<float>(cyr) * row_h;
|
|
||||||
if (target < 0.f)
|
|
||||||
target = 0.f;
|
|
||||||
float max_y = ImGui::GetScrollMaxY();
|
|
||||||
if (max_y >= 0.f && target > max_y)
|
|
||||||
target = max_y;
|
|
||||||
ImGui::SetScrollY(target);
|
|
||||||
scroll_y = ImGui::GetScrollY();
|
|
||||||
first_row = static_cast<long>(scroll_y / row_h);
|
|
||||||
last_row = first_row + vis_rows - 1;
|
|
||||||
} else if (cyr > last_row) {
|
|
||||||
// Scroll just enough to bring the cursor line to the bottom
|
|
||||||
long new_first = cyr - vis_rows + 1;
|
|
||||||
if (new_first < 0)
|
|
||||||
new_first = 0;
|
|
||||||
float target = static_cast<float>(new_first) * row_h;
|
|
||||||
float max_y = ImGui::GetScrollMaxY();
|
|
||||||
if (target < 0.f)
|
|
||||||
target = 0.f;
|
|
||||||
if (max_y >= 0.f && target > max_y)
|
|
||||||
target = max_y;
|
|
||||||
ImGui::SetScrollY(target);
|
|
||||||
scroll_y = ImGui::GetScrollY();
|
|
||||||
first_row = static_cast<long>(scroll_y / row_h);
|
|
||||||
last_row = first_row + vis_rows - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontal scroll: ensure cursor column is visible
|
|
||||||
float child_w = ImGui::GetWindowWidth();
|
|
||||||
long vis_cols = static_cast<long>(child_w / space_w);
|
|
||||||
if (vis_cols < 1)
|
|
||||||
vis_cols = 1;
|
|
||||||
long first_col = static_cast<long>(scroll_x / space_w);
|
|
||||||
long last_col = first_col + vis_cols - 1;
|
|
||||||
|
|
||||||
// Compute cursor's rendered X position (accounting for tabs)
|
|
||||||
std::size_t cursor_rx = 0;
|
|
||||||
if (cy < lines.size()) {
|
|
||||||
std::string cur_line = static_cast<std::string>(lines[cy]);
|
|
||||||
const std::size_t tabw = 8;
|
|
||||||
for (std::size_t i = 0; i < cx && i < cur_line.size(); ++i) {
|
|
||||||
if (cur_line[i] == '\t') {
|
|
||||||
cursor_rx += tabw - (cursor_rx % tabw);
|
|
||||||
} else {
|
|
||||||
cursor_rx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
long cxr = static_cast<long>(cursor_rx);
|
|
||||||
if (cxr < first_col || cxr > last_col) {
|
|
||||||
float target_x = static_cast<float>(cxr) * space_w;
|
|
||||||
// Center horizontally if possible
|
|
||||||
target_x -= (child_w / 2.0f);
|
|
||||||
if (target_x < 0.f)
|
|
||||||
target_x = 0.f;
|
|
||||||
float max_x = ImGui::GetScrollMaxX();
|
|
||||||
if (max_x >= 0.f && target_x > max_x)
|
|
||||||
target_x = max_x;
|
|
||||||
ImGui::SetScrollX(target_x);
|
|
||||||
scroll_x = ImGui::GetScrollX();
|
|
||||||
}
|
|
||||||
// Phase 3: prefetch visible viewport highlights and warm around in background
|
|
||||||
if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
|
|
||||||
int fr = static_cast<int>(std::max(0L, first_row));
|
|
||||||
int rc = static_cast<int>(std::max(1L, vis_rows));
|
|
||||||
buf->Highlighter()->PrefetchViewport(*buf, fr, rc, buf->Version());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Cache current horizontal offset in rendered columns for click handling
|
// Cache current horizontal offset in rendered columns for click handling
|
||||||
const std::size_t coloffs_now = buf->Coloffs();
|
const std::size_t coloffs_now = buf->Coloffs();
|
||||||
|
|
||||||
@@ -489,23 +414,98 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);
|
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Synchronize cursor and scrolling after rendering all lines so content size is known.
|
||||||
|
{
|
||||||
|
float child_h_actual = ImGui::GetWindowHeight();
|
||||||
|
float child_w_actual = ImGui::GetWindowWidth();
|
||||||
|
float scroll_y_now = ImGui::GetScrollY();
|
||||||
|
float scroll_x_now = ImGui::GetScrollX();
|
||||||
|
|
||||||
|
long first_row = static_cast<long>(scroll_y_now / row_h);
|
||||||
|
long vis_rows = static_cast<long>(std::round(child_h_actual / row_h));
|
||||||
|
if (vis_rows < 1)
|
||||||
|
vis_rows = 1;
|
||||||
|
long last_row = first_row + vis_rows - 1;
|
||||||
|
|
||||||
|
long cyr = static_cast<long>(cy);
|
||||||
|
if (cyr < first_row) {
|
||||||
|
float target = static_cast<float>(cyr) * row_h;
|
||||||
|
if (target < 0.f)
|
||||||
|
target = 0.f;
|
||||||
|
float max_y = ImGui::GetScrollMaxY();
|
||||||
|
if (max_y >= 0.f && target > max_y)
|
||||||
|
target = max_y;
|
||||||
|
ImGui::SetScrollY(target);
|
||||||
|
first_row = static_cast<long>(target / row_h);
|
||||||
|
last_row = first_row + vis_rows - 1;
|
||||||
|
} else if (cyr > last_row) {
|
||||||
|
long new_first = cyr - vis_rows + 1;
|
||||||
|
if (new_first < 0)
|
||||||
|
new_first = 0;
|
||||||
|
float target = static_cast<float>(new_first) * row_h;
|
||||||
|
float max_y = ImGui::GetScrollMaxY();
|
||||||
|
if (target < 0.f)
|
||||||
|
target = 0.f;
|
||||||
|
if (max_y >= 0.f && target > max_y)
|
||||||
|
target = max_y;
|
||||||
|
ImGui::SetScrollY(target);
|
||||||
|
first_row = static_cast<long>(target / row_h);
|
||||||
|
last_row = first_row + vis_rows - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal scroll: ensure cursor column is visible
|
||||||
|
long vis_cols = static_cast<long>(std::round(child_w_actual / space_w));
|
||||||
|
if (vis_cols < 1)
|
||||||
|
vis_cols = 1;
|
||||||
|
long first_col = static_cast<long>(scroll_x_now / space_w);
|
||||||
|
long last_col = first_col + vis_cols - 1;
|
||||||
|
|
||||||
|
std::size_t cursor_rx = 0;
|
||||||
|
if (cy < lines.size()) {
|
||||||
|
std::string cur_line = static_cast<std::string>(lines[cy]);
|
||||||
|
const std::size_t tabw = 8;
|
||||||
|
for (std::size_t i = 0; i < cx && i < cur_line.size(); ++i) {
|
||||||
|
if (cur_line[i] == '\t') {
|
||||||
|
cursor_rx += tabw - (cursor_rx % tabw);
|
||||||
|
} else {
|
||||||
|
cursor_rx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long cxr = static_cast<long>(cursor_rx);
|
||||||
|
if (cxr < first_col || cxr > last_col) {
|
||||||
|
float target_x = static_cast<float>(cxr) * space_w;
|
||||||
|
target_x -= (child_w_actual / 2.0f);
|
||||||
|
if (target_x < 0.f)
|
||||||
|
target_x = 0.f;
|
||||||
|
float max_x = ImGui::GetScrollMaxX();
|
||||||
|
if (max_x >= 0.f && target_x > max_x)
|
||||||
|
target_x = max_x;
|
||||||
|
ImGui::SetScrollX(target_x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
|
||||||
|
int fr = static_cast<int>(std::max(0L, first_row));
|
||||||
|
int rc = static_cast<int>(std::max(1L, vis_rows));
|
||||||
|
buf->Highlighter()->PrefetchViewport(*buf, fr, rc, buf->Version());
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
ImGui::PopStyleVar(2); // WindowPadding, ItemSpacing
|
||||||
|
|
||||||
// Status bar spanning full width
|
// Status bar area starting right after the scroll child
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
// Compute full content width and draw a filled background rectangle
|
|
||||||
ImVec2 win_pos = ImGui::GetWindowPos();
|
ImVec2 win_pos = ImGui::GetWindowPos();
|
||||||
ImVec2 cr_min = ImGui::GetWindowContentRegionMin();
|
ImVec2 win_sz = ImGui::GetWindowSize();
|
||||||
ImVec2 cr_max = ImGui::GetWindowContentRegionMax();
|
float x0 = win_pos.x;
|
||||||
float x0 = win_pos.x + cr_min.x;
|
float x1 = win_pos.x + win_sz.x;
|
||||||
float x1 = win_pos.x + cr_max.x;
|
float y0 = ImGui::GetCursorScreenPos().y;
|
||||||
ImVec2 cursor = ImGui::GetCursorScreenPos();
|
float bar_h = real_bar_h;
|
||||||
float bar_h = ImGui::GetFrameHeight();
|
|
||||||
ImVec2 p0(x0, cursor.y);
|
ImVec2 p0(x0, y0);
|
||||||
ImVec2 p1(x1, cursor.y + bar_h);
|
ImVec2 p1(x1, y0 + bar_h);
|
||||||
ImU32 bg_col = ImGui::GetColorU32(ImGuiCol_HeaderActive);
|
ImU32 bg_col = ImGui::GetColorU32(ImGuiCol_HeaderActive);
|
||||||
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col);
|
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col);
|
||||||
|
|
||||||
// If a prompt is active, replace the entire status bar with the prompt text
|
// If a prompt is active, replace the entire status bar with the prompt text
|
||||||
if (ed.PromptActive()) {
|
if (ed.PromptActive()) {
|
||||||
std::string label = ed.PromptLabel();
|
std::string label = ed.PromptLabel();
|
||||||
@@ -560,7 +560,7 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
(size_t) std::max<size_t>(
|
(size_t) std::max<size_t>(
|
||||||
1, (size_t) (tail.size() / 4)))
|
1, (size_t) (tail.size() / 4)))
|
||||||
: 1;
|
: 1;
|
||||||
start += skip;
|
start += skip;
|
||||||
std::string candidate = tail.substr(start);
|
std::string candidate = tail.substr(start);
|
||||||
ImVec2 cand_sz = ImGui::CalcTextSize(candidate.c_str());
|
ImVec2 cand_sz = ImGui::CalcTextSize(candidate.c_str());
|
||||||
if (cand_sz.x <= avail_px) {
|
if (cand_sz.x <= avail_px) {
|
||||||
@@ -591,11 +591,9 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
|
|
||||||
ImVec2 msg_sz = ImGui::CalcTextSize(final_msg.c_str());
|
ImVec2 msg_sz = ImGui::CalcTextSize(final_msg.c_str());
|
||||||
ImGui::PushClipRect(ImVec2(p0.x, p0.y), ImVec2(p1.x, p1.y), true);
|
ImGui::PushClipRect(ImVec2(p0.x, p0.y), ImVec2(p1.x, p1.y), true);
|
||||||
ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - msg_sz.y) * 0.5f));
|
ImGui::SetCursorScreenPos(ImVec2(left_x, y0 + (bar_h - msg_sz.y) * 0.5f));
|
||||||
ImGui::TextUnformatted(final_msg.c_str());
|
ImGui::TextUnformatted(final_msg.c_str());
|
||||||
ImGui::PopClipRect();
|
ImGui::PopClipRect();
|
||||||
// Advance cursor to after the bar to keep layout consistent
|
|
||||||
ImGui::Dummy(ImVec2(x1 - x0, bar_h));
|
|
||||||
} else {
|
} else {
|
||||||
// Build left text
|
// Build left text
|
||||||
std::string left;
|
std::string left;
|
||||||
@@ -618,11 +616,11 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
std::size_t total = ed.BufferCount();
|
std::size_t total = ed.BufferCount();
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // 1-based for display
|
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // 1-based for display
|
||||||
left += "[";
|
left += "[";
|
||||||
left += std::to_string(static_cast<unsigned long long>(idx1));
|
left += std::to_string(static_cast<unsigned long long>(idx1));
|
||||||
left += "/";
|
left += "/";
|
||||||
left += std::to_string(static_cast<unsigned long long>(total));
|
left += std::to_string(static_cast<unsigned long long>(total));
|
||||||
left += "] ";
|
left += "] ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
left += fname;
|
left += fname;
|
||||||
@@ -631,9 +629,9 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
// Append total line count as "<n>L"
|
// Append total line count as "<n>L"
|
||||||
{
|
{
|
||||||
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
|
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
|
||||||
left += " ";
|
left += " ";
|
||||||
left += std::to_string(lcount);
|
left += std::to_string(lcount);
|
||||||
left += "L";
|
left += "L";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build right text (cursor/mark)
|
// Build right text (cursor/mark)
|
||||||
@@ -671,20 +669,21 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
float max_left = std::max(0.0f, right_x - left_x - pad);
|
float max_left = std::max(0.0f, right_x - left_x - pad);
|
||||||
if (max_left < left_sz.x && max_left > 10.0f) {
|
if (max_left < left_sz.x && max_left > 10.0f) {
|
||||||
// Render a clipped left using a child region
|
// Render a clipped left using a child region
|
||||||
ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - left_sz.y) * 0.5f));
|
ImGui::SetCursorScreenPos(ImVec2(left_x, y0 + (bar_h - left_sz.y) * 0.5f));
|
||||||
ImGui::PushClipRect(ImVec2(left_x, p0.y), ImVec2(right_x - pad, p1.y), true);
|
ImGui::PushClipRect(ImVec2(left_x, y0), ImVec2(right_x - pad, y0 + bar_h),
|
||||||
|
true);
|
||||||
ImGui::TextUnformatted(left.c_str());
|
ImGui::TextUnformatted(left.c_str());
|
||||||
ImGui::PopClipRect();
|
ImGui::PopClipRect();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Draw left normally
|
// Draw left normally
|
||||||
ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - left_sz.y) * 0.5f));
|
ImGui::SetCursorScreenPos(ImVec2(left_x, y0 + (bar_h - left_sz.y) * 0.5f));
|
||||||
ImGui::TextUnformatted(left.c_str());
|
ImGui::TextUnformatted(left.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw right
|
// Draw right
|
||||||
ImGui::SetCursorScreenPos(ImVec2(std::max(right_x, left_x),
|
ImGui::SetCursorScreenPos(ImVec2(std::max(right_x, left_x),
|
||||||
p0.y + (bar_h - right_sz.y) * 0.5f));
|
y0 + (bar_h - right_sz.y) * 0.5f));
|
||||||
ImGui::TextUnformatted(right.c_str());
|
ImGui::TextUnformatted(right.c_str());
|
||||||
|
|
||||||
// Draw middle message centered in remaining space
|
// Draw middle message centered in remaining space
|
||||||
@@ -696,14 +695,12 @@ ImGuiRenderer::Draw(Editor &ed)
|
|||||||
ImVec2 msg_sz = ImGui::CalcTextSize(msg.c_str());
|
ImVec2 msg_sz = ImGui::CalcTextSize(msg.c_str());
|
||||||
float msg_x = mid_left + std::max(0.0f, (mid_w - msg_sz.x) * 0.5f);
|
float msg_x = mid_left + std::max(0.0f, (mid_w - msg_sz.x) * 0.5f);
|
||||||
// Clip to middle region
|
// Clip to middle region
|
||||||
ImGui::PushClipRect(ImVec2(mid_left, p0.y), ImVec2(mid_right, p1.y), true);
|
ImGui::PushClipRect(ImVec2(mid_left, y0), ImVec2(mid_right, y0 + bar_h), true);
|
||||||
ImGui::SetCursorScreenPos(ImVec2(msg_x, p0.y + (bar_h - msg_sz.y) * 0.5f));
|
ImGui::SetCursorScreenPos(ImVec2(msg_x, y0 + (bar_h - msg_sz.y) * 0.5f));
|
||||||
ImGui::TextUnformatted(msg.c_str());
|
ImGui::TextUnformatted(msg.c_str());
|
||||||
ImGui::PopClipRect();
|
ImGui::PopClipRect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Advance cursor to after the bar to keep layout consistent
|
|
||||||
ImGui::Dummy(ImVec2(x1 - x0, bar_h));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,13 +142,13 @@ protected:
|
|||||||
p.save();
|
p.save();
|
||||||
p.setClipRect(viewport);
|
p.setClipRect(viewport);
|
||||||
|
|
||||||
// Iterate visible lines
|
// Iterate visible lines
|
||||||
for (std::size_t i = rowoffs, vis_idx = 0; i < last_row; ++i, ++vis_idx) {
|
for (std::size_t i = rowoffs, vis_idx = 0; i < last_row; ++i, ++vis_idx) {
|
||||||
// Materialize the Buffer::Line into a std::string for
|
// Materialize the Buffer::Line into a std::string for
|
||||||
// regex/iterator usage and general string ops.
|
// regex/iterator usage and general string ops.
|
||||||
const std::string line = static_cast<std::string>(lines[i]);
|
const std::string line = static_cast<std::string>(lines[i]);
|
||||||
const int y = viewport.y() + static_cast<int>(vis_idx) * line_h;
|
const int y = viewport.y() + static_cast<int>(vis_idx) * line_h;
|
||||||
const int baseline = y + fm.ascent();
|
const int baseline = y + fm.ascent();
|
||||||
|
|
||||||
// Helper: convert src col -> rx with tab expansion
|
// Helper: convert src col -> rx with tab expansion
|
||||||
auto src_to_rx_line = [&](std::size_t src_col) -> std::size_t {
|
auto src_to_rx_line = [&](std::size_t src_col) -> std::size_t {
|
||||||
@@ -453,11 +453,11 @@ protected:
|
|||||||
std::size_t total = ed_->BufferCount();
|
std::size_t total = ed_->BufferCount();
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
std::size_t idx1 = ed_->CurrentBufferIndex() + 1; // 1-based
|
std::size_t idx1 = ed_->CurrentBufferIndex() + 1; // 1-based
|
||||||
left += QStringLiteral(" [");
|
left += QStringLiteral(" [");
|
||||||
left += QString::number(static_cast<qlonglong>(idx1));
|
left += QString::number(static_cast<qlonglong>(idx1));
|
||||||
left += QStringLiteral("/");
|
left += QStringLiteral("/");
|
||||||
left += QString::number(static_cast<qlonglong>(total));
|
left += QString::number(static_cast<qlonglong>(total));
|
||||||
left += QStringLiteral("] ");
|
left += QStringLiteral("] ");
|
||||||
} else {
|
} else {
|
||||||
left += QStringLiteral(" ");
|
left += QStringLiteral(" ");
|
||||||
}
|
}
|
||||||
@@ -477,9 +477,9 @@ protected:
|
|||||||
|
|
||||||
// total lines suffix " <n>L"
|
// total lines suffix " <n>L"
|
||||||
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
|
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
|
||||||
left += QStringLiteral(" ");
|
left += QStringLiteral(" ");
|
||||||
left += QString::number(static_cast<qlonglong>(lcount));
|
left += QString::number(static_cast<qlonglong>(lcount));
|
||||||
left += QStringLiteral("L");
|
left += QStringLiteral("L");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build right segment: cursor and mark
|
// Build right segment: cursor and mark
|
||||||
@@ -602,12 +602,12 @@ protected:
|
|||||||
int d_cols = 0;
|
int d_cols = 0;
|
||||||
if (std::fabs(v_scroll_accum_) >= 1.0 && (!horiz_mode || std::fabs(v_scroll_accum_) > std::fabs(
|
if (std::fabs(v_scroll_accum_) >= 1.0 && (!horiz_mode || std::fabs(v_scroll_accum_) > std::fabs(
|
||||||
h_scroll_accum_))) {
|
h_scroll_accum_))) {
|
||||||
d_rows = static_cast<int>(v_scroll_accum_);
|
d_rows = static_cast<int>(v_scroll_accum_);
|
||||||
v_scroll_accum_ -= d_rows;
|
v_scroll_accum_ -= d_rows;
|
||||||
}
|
}
|
||||||
if (std::fabs(h_scroll_accum_) >= 1.0 && (horiz_mode || std::fabs(h_scroll_accum_) >= std::fabs(
|
if (std::fabs(h_scroll_accum_) >= 1.0 && (horiz_mode || std::fabs(h_scroll_accum_) >= std::fabs(
|
||||||
v_scroll_accum_))) {
|
v_scroll_accum_))) {
|
||||||
d_cols = static_cast<int>(h_scroll_accum_);
|
d_cols = static_cast<int>(h_scroll_accum_);
|
||||||
h_scroll_accum_ -= d_cols;
|
h_scroll_accum_ -= d_cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,11 +658,9 @@ private:
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool
|
bool
|
||||||
GUIFrontend::Init(Editor &ed)
|
GUIFrontend::Init(int &argc, char **argv, Editor &ed)
|
||||||
{
|
{
|
||||||
int argc = 0;
|
app_ = new QApplication(argc, argv);
|
||||||
char **argv = nullptr;
|
|
||||||
app_ = new QApplication(argc, argv);
|
|
||||||
|
|
||||||
window_ = new MainWindow(input_);
|
window_ = new MainWindow(input_);
|
||||||
window_->show();
|
window_->show();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
|
|
||||||
~GUIFrontend() override = default;
|
~GUIFrontend() override = default;
|
||||||
|
|
||||||
bool Init(Editor &ed) override;
|
bool Init(int &argc, char **argv, Editor &ed) override;
|
||||||
|
|
||||||
void Step(Editor &ed, bool &running) override;
|
void Step(Editor &ed, bool &running) override;
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,10 @@
|
|||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TerminalFrontend::Init(Editor &ed)
|
TerminalFrontend::Init(int &argc, char **argv, Editor &ed)
|
||||||
{
|
{
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
// Ensure Control keys reach the app: disable XON/XOFF and dsusp/susp bindings (e.g., ^S/^Q, ^Y on macOS)
|
// Ensure Control keys reach the app: disable XON/XOFF and dsusp/susp bindings (e.g., ^S/^Q, ^Y on macOS)
|
||||||
{
|
{
|
||||||
struct termios tio{};
|
struct termios tio{};
|
||||||
@@ -121,4 +123,4 @@ TerminalFrontend::Shutdown()
|
|||||||
have_old_sigint_ = false;
|
have_old_sigint_ = false;
|
||||||
}
|
}
|
||||||
endwin();
|
endwin();
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ public:
|
|||||||
// Adjust if your terminal needs a different threshold.
|
// Adjust if your terminal needs a different threshold.
|
||||||
static constexpr int kEscDelayMs = 50;
|
static constexpr int kEscDelayMs = 50;
|
||||||
|
|
||||||
bool Init(Editor &ed) override;
|
bool Init(int &argc, char **argv, Editor &ed) override;
|
||||||
|
|
||||||
void Step(Editor &ed, bool &running) override;
|
void Step(Editor &ed, bool &running) override;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#include <clocale>
|
||||||
|
#define _XOPEN_SOURCE_EXTENDED 1
|
||||||
|
#include <cwchar>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -157,35 +160,52 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
// Map to simple attributes; search highlight uses A_STANDOUT which takes precedence below
|
// Map to simple attributes; search highlight uses A_STANDOUT which takes precedence below
|
||||||
attrset(A_NORMAL);
|
attrset(A_NORMAL);
|
||||||
switch (k) {
|
switch (k) {
|
||||||
case kte::TokenKind::Keyword:
|
case kte::TokenKind::Keyword:
|
||||||
case kte::TokenKind::Type:
|
case kte::TokenKind::Type:
|
||||||
case kte::TokenKind::Constant:
|
case kte::TokenKind::Constant:
|
||||||
case kte::TokenKind::Function:
|
case kte::TokenKind::Function:
|
||||||
attron(A_BOLD);
|
attron(A_BOLD);
|
||||||
break;
|
break;
|
||||||
case kte::TokenKind::Comment:
|
case kte::TokenKind::Comment:
|
||||||
attron(A_DIM);
|
attron(A_DIM);
|
||||||
break;
|
break;
|
||||||
case kte::TokenKind::String:
|
case kte::TokenKind::String:
|
||||||
case kte::TokenKind::Char:
|
case kte::TokenKind::Char:
|
||||||
case kte::TokenKind::Number:
|
case kte::TokenKind::Number:
|
||||||
// standout a bit using A_UNDERLINE if available
|
// standout a bit using A_UNDERLINE if available
|
||||||
attron(A_UNDERLINE);
|
attron(A_UNDERLINE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
while (written < cols) {
|
while (written < cols) {
|
||||||
char ch = ' ';
|
|
||||||
bool from_src = false;
|
bool from_src = false;
|
||||||
|
wchar_t wch = L' ';
|
||||||
|
int wch_len = 1;
|
||||||
|
int disp_w = 1;
|
||||||
|
|
||||||
if (src_i < line.size()) {
|
if (src_i < line.size()) {
|
||||||
unsigned char c = static_cast<unsigned char>(line[src_i]);
|
// Decode UTF-8
|
||||||
if (c == '\t') {
|
std::mbstate_t state = std::mbstate_t();
|
||||||
|
size_t res = std::mbrtowc(
|
||||||
|
&wch, &line[src_i], line.size() - src_i, &state);
|
||||||
|
if (res == (size_t) -1 || res == (size_t) -2) {
|
||||||
|
// Invalid or incomplete; treat as single byte
|
||||||
|
wch = static_cast<unsigned char>(line[src_i]);
|
||||||
|
wch_len = 1;
|
||||||
|
} else if (res == 0) {
|
||||||
|
wch = L'\0';
|
||||||
|
wch_len = 1;
|
||||||
|
} else {
|
||||||
|
wch_len = static_cast<int>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wch == L'\t') {
|
||||||
std::size_t next_tab = tabw - (render_col % tabw);
|
std::size_t next_tab = tabw - (render_col % tabw);
|
||||||
if (render_col + next_tab <= coloffs) {
|
if (render_col + next_tab <= coloffs) {
|
||||||
render_col += next_tab;
|
render_col += next_tab;
|
||||||
++src_i;
|
src_i += wch_len;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Emit spaces for tab
|
// Emit spaces for tab
|
||||||
@@ -194,7 +214,7 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
std::size_t to_skip = std::min<std::size_t>(
|
std::size_t to_skip = std::min<std::size_t>(
|
||||||
next_tab, coloffs - render_col);
|
next_tab, coloffs - render_col);
|
||||||
render_col += to_skip;
|
render_col += to_skip;
|
||||||
next_tab -= to_skip;
|
next_tab -= to_skip;
|
||||||
}
|
}
|
||||||
// Now render visible spaces
|
// Now render visible spaces
|
||||||
while (next_tab > 0 && written < cols) {
|
while (next_tab > 0 && written < cols) {
|
||||||
@@ -233,23 +253,34 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
++render_col;
|
++render_col;
|
||||||
--next_tab;
|
--next_tab;
|
||||||
}
|
}
|
||||||
++src_i;
|
src_i += wch_len;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
// normal char
|
// normal char
|
||||||
|
disp_w = wcwidth(wch);
|
||||||
|
if (disp_w < 0)
|
||||||
|
disp_w = 1; // non-printable or similar
|
||||||
|
|
||||||
if (render_col < coloffs) {
|
if (render_col < coloffs) {
|
||||||
++render_col;
|
render_col += disp_w;
|
||||||
++src_i;
|
src_i += wch_len;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ch = static_cast<char>(c);
|
|
||||||
from_src = true;
|
from_src = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// beyond EOL, fill spaces
|
// beyond EOL, fill spaces
|
||||||
ch = ' ';
|
wch = L' ';
|
||||||
|
wch_len = 1;
|
||||||
|
disp_w = 1;
|
||||||
from_src = false;
|
from_src = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (written + disp_w > cols) {
|
||||||
|
// would overflow, just break
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
bool in_hl = search_mode && from_src && is_src_in_hl(src_i);
|
bool in_hl = search_mode && from_src && is_src_in_hl(src_i);
|
||||||
bool in_cur =
|
bool in_cur =
|
||||||
has_current && li == cur_my && from_src && src_i >= cur_mx && src_i <
|
has_current && li == cur_my && from_src && src_i >= cur_mx && src_i <
|
||||||
@@ -273,11 +304,20 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
if (!in_hl && from_src) {
|
if (!in_hl && from_src) {
|
||||||
apply_token_attr(token_at(src_i));
|
apply_token_attr(token_at(src_i));
|
||||||
}
|
}
|
||||||
addch(static_cast<unsigned char>(ch));
|
|
||||||
++written;
|
if (from_src) {
|
||||||
++render_col;
|
cchar_t cch;
|
||||||
|
wchar_t warr[2] = {wch, L'\0'};
|
||||||
|
setcchar(&cch, warr, A_NORMAL, 0, nullptr);
|
||||||
|
add_wch(&cch);
|
||||||
|
} else {
|
||||||
|
addch(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
written += disp_w;
|
||||||
|
render_col += disp_w;
|
||||||
if (from_src)
|
if (from_src)
|
||||||
++src_i;
|
src_i += wch_len;
|
||||||
if (src_i >= line.size() && written >= cols)
|
if (src_i >= line.size() && written >= cols)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -297,23 +337,35 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
// Place terminal cursor at logical position accounting for tabs and coloffs.
|
// Place terminal cursor at logical position accounting for tabs and coloffs.
|
||||||
// Recompute the rendered X using the same logic as the drawing loop to avoid
|
// Recompute the rendered X using the same logic as the drawing loop to avoid
|
||||||
// any drift between the command-layer computation and the terminal renderer.
|
// any drift between the command-layer computation and the terminal renderer.
|
||||||
std::size_t cy = buf->Cury();
|
std::size_t cy = buf->Cury();
|
||||||
std::size_t cx = buf->Curx();
|
std::size_t cx = buf->Curx();
|
||||||
int cur_y = static_cast<int>(cy) - static_cast<int>(buf->Rowoffs());
|
int cur_y = static_cast<int>(cy) - static_cast<int>(buf->Rowoffs());
|
||||||
std::size_t rx_recomputed = 0;
|
std::size_t rx_recomputed = 0;
|
||||||
if (cy < lines.size()) {
|
if (cy < lines.size()) {
|
||||||
const std::string line_for_cursor = static_cast<std::string>(lines[cy]);
|
const std::string line_for_cursor = static_cast<std::string>(lines[cy]);
|
||||||
std::size_t src_i_cur = 0;
|
std::size_t src_i_cur = 0;
|
||||||
std::size_t render_col_cur = 0;
|
std::size_t render_col_cur = 0;
|
||||||
while (src_i_cur < line_for_cursor.size() && src_i_cur < cx) {
|
while (src_i_cur < line_for_cursor.size() && src_i_cur < cx) {
|
||||||
unsigned char ccur = static_cast<unsigned char>(line_for_cursor[src_i_cur]);
|
std::mbstate_t state = std::mbstate_t();
|
||||||
if (ccur == '\t') {
|
wchar_t wch;
|
||||||
std::size_t next_tab = tabw - (render_col_cur % tabw);
|
size_t res = std::mbrtowc(
|
||||||
render_col_cur += next_tab;
|
&wch, &line_for_cursor[src_i_cur], line_for_cursor.size() - src_i_cur,
|
||||||
++src_i_cur;
|
&state);
|
||||||
|
|
||||||
|
if (res == (size_t) -1 || res == (size_t) -2) {
|
||||||
|
render_col_cur += 1;
|
||||||
|
src_i_cur += 1;
|
||||||
|
} else if (res == 0) {
|
||||||
|
src_i_cur += 1;
|
||||||
} else {
|
} else {
|
||||||
++render_col_cur;
|
if (wch == L'\t') {
|
||||||
++src_i_cur;
|
std::size_t next_tab = tabw - (render_col_cur % tabw);
|
||||||
|
render_col_cur += next_tab;
|
||||||
|
} else {
|
||||||
|
int dw = wcwidth(wch);
|
||||||
|
render_col_cur += (dw < 0) ? 1 : dw;
|
||||||
|
}
|
||||||
|
src_i_cur += res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rx_recomputed = render_col_cur;
|
rx_recomputed = render_col_cur;
|
||||||
@@ -403,9 +455,9 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
{
|
{
|
||||||
const char *app = "kte";
|
const char *app = "kte";
|
||||||
left.reserve(256);
|
left.reserve(256);
|
||||||
left += app;
|
left += app;
|
||||||
left += " ";
|
left += " ";
|
||||||
left += KTE_VERSION_STR; // already includes leading 'v'
|
left += KTE_VERSION_STR; // already includes leading 'v'
|
||||||
const Buffer *b = buf;
|
const Buffer *b = buf;
|
||||||
std::string fname;
|
std::string fname;
|
||||||
if (b) {
|
if (b) {
|
||||||
@@ -426,11 +478,11 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
std::size_t total = ed.BufferCount();
|
std::size_t total = ed.BufferCount();
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // human-friendly 1-based
|
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // human-friendly 1-based
|
||||||
left += "[";
|
left += "[";
|
||||||
left += std::to_string(static_cast<unsigned long long>(idx1));
|
left += std::to_string(static_cast<unsigned long long>(idx1));
|
||||||
left += "/";
|
left += "/";
|
||||||
left += std::to_string(static_cast<unsigned long long>(total));
|
left += std::to_string(static_cast<unsigned long long>(total));
|
||||||
left += "] ";
|
left += "] ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
left += fname;
|
left += fname;
|
||||||
@@ -442,9 +494,9 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
// Append total line count as "<n>L"
|
// Append total line count as "<n>L"
|
||||||
if (b) {
|
if (b) {
|
||||||
unsigned long lcount = static_cast<unsigned long>(b->Rows().size());
|
unsigned long lcount = static_cast<unsigned long>(b->Rows().size());
|
||||||
left += " ";
|
left += " ";
|
||||||
left += std::to_string(lcount);
|
left += std::to_string(lcount);
|
||||||
left += "L";
|
left += "L";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TestFrontend::Init(Editor &ed)
|
TestFrontend::Init(int &argc, char **argv, Editor &ed)
|
||||||
{
|
{
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
ed.SetDimensions(24, 80);
|
ed.SetDimensions(24, 80);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -30,4 +32,4 @@ TestFrontend::Step(Editor &ed, bool &running)
|
|||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
TestFrontend::Shutdown() {}
|
TestFrontend::Shutdown() {}
|
||||||
@@ -13,7 +13,7 @@ public:
|
|||||||
|
|
||||||
~TestFrontend() override = default;
|
~TestFrontend() override = default;
|
||||||
|
|
||||||
bool Init(Editor &ed) override;
|
bool Init(int &argc, char **argv, Editor &ed) override;
|
||||||
|
|
||||||
void Step(Editor &ed, bool &running) override;
|
void Step(Editor &ed, bool &running) override;
|
||||||
|
|
||||||
|
|||||||
78
cmake/fix_bundle.cmake
Normal file
78
cmake/fix_bundle.cmake
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
# Fix up a macOS .app bundle by copying non-Qt dylibs into
|
||||||
|
# Contents/Frameworks and rewriting install names to use @rpath/@loader_path.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# cmake -DAPP_BUNDLE=/path/to/kge.app -P cmake/fix_bundle.cmake
|
||||||
|
|
||||||
|
if (NOT APP_BUNDLE)
|
||||||
|
message(FATAL_ERROR "APP_BUNDLE not set. Invoke with -DAPP_BUNDLE=/path/to/App.app")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(APP_DIR "${APP_BUNDLE}" ABSOLUTE)
|
||||||
|
set(EXECUTABLE "${APP_DIR}/Contents/MacOS/kge")
|
||||||
|
|
||||||
|
if (NOT EXISTS "${EXECUTABLE}")
|
||||||
|
message(FATAL_ERROR "Executable not found at: ${EXECUTABLE}")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
include(BundleUtilities)
|
||||||
|
|
||||||
|
# Directories to search when resolving prerequisites. We include Homebrew so that
|
||||||
|
# if any deps are currently resolved from there, fixup_bundle will copy them into
|
||||||
|
# the bundle and rewrite install names to be self-contained.
|
||||||
|
set(DIRS
|
||||||
|
"/usr/local/lib"
|
||||||
|
"/opt/homebrew/lib"
|
||||||
|
"/opt/homebrew/opt"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Note: We pass empty plugin list so fixup_bundle scans the executable and all
|
||||||
|
# libs it references recursively. Qt frameworks already live in the bundle after
|
||||||
|
# macdeployqt; this step is primarily for non-Qt dylibs (glib, icu, pcre2, zstd,
|
||||||
|
# dbus, etc.).
|
||||||
|
# fixup_bundle often fails if copied libraries are read-only.
|
||||||
|
# We also try to use the system install_name_tool and otool to avoid issues with Anaconda's version.
|
||||||
|
# Note: BundleUtilities uses find_program(gp_otool "otool") internally, so we might need to set it differently.
|
||||||
|
set(gp_otool "/usr/bin/otool")
|
||||||
|
set(CMAKE_INSTALL_NAME_TOOL "/usr/bin/install_name_tool")
|
||||||
|
set(CMAKE_OTOOL "/usr/bin/otool")
|
||||||
|
set(ENV{PATH} "/usr/bin:/bin:/usr/sbin:/sbin")
|
||||||
|
|
||||||
|
execute_process(COMMAND chmod -R u+w "${APP_DIR}/Contents/Frameworks")
|
||||||
|
|
||||||
|
fixup_bundle("${APP_DIR}" "" "${DIRS}")
|
||||||
|
|
||||||
|
# On Apple Silicon (and modern macOS in general), modifications by fixup_bundle
|
||||||
|
# invalidate code signatures. We must re-sign the bundle (at least ad-hoc)
|
||||||
|
# for it to be allowed to run.
|
||||||
|
# We sign deep, but sometimes explicit signing of components is more reliable.
|
||||||
|
message(STATUS "Re-signing ${APP_DIR} after fixup...")
|
||||||
|
|
||||||
|
# 1. Sign dylibs in Frameworks
|
||||||
|
file(GLOB_RECURSE DYLIBS "${APP_DIR}/Contents/Frameworks/*.dylib")
|
||||||
|
foreach (DYLIB ${DYLIBS})
|
||||||
|
message(STATUS "Signing ${DYLIB}...")
|
||||||
|
execute_process(COMMAND /usr/bin/codesign --force --sign - "${DYLIB}")
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
# 2. Sign nested executables
|
||||||
|
message(STATUS "Signing nested kte...")
|
||||||
|
execute_process(COMMAND /usr/bin/codesign --force --sign - "${APP_DIR}/Contents/MacOS/kte")
|
||||||
|
|
||||||
|
# 3. Sign the main executable explicitly
|
||||||
|
message(STATUS "Signing main kge...")
|
||||||
|
execute_process(COMMAND /usr/bin/codesign --force --sign - "${APP_DIR}/Contents/MacOS/kge")
|
||||||
|
|
||||||
|
# 4. Sign the main bundle
|
||||||
|
execute_process(
|
||||||
|
COMMAND /usr/bin/codesign --force --deep --sign - "${APP_DIR}"
|
||||||
|
RESULT_VARIABLE CODESIGN_RESULT
|
||||||
|
)
|
||||||
|
|
||||||
|
if (NOT CODESIGN_RESULT EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Codesign failed with error: ${CODESIGN_RESULT}")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
message(STATUS "fix_bundle.cmake completed for ${APP_DIR}")
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "Font.h"
|
#include "Font.h"
|
||||||
|
#include "IosevkaExtended.h"
|
||||||
|
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
|
||||||
@@ -8,16 +9,32 @@ Font::Load(const float size) const
|
|||||||
{
|
{
|
||||||
const ImGuiIO &io = ImGui::GetIO();
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
io.Fonts->Clear();
|
io.Fonts->Clear();
|
||||||
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
|
|
||||||
|
ImFontConfig config;
|
||||||
|
config.MergeMode = false;
|
||||||
|
|
||||||
|
// Load Basic Latin + Latin Supplement
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
this->data_,
|
this->data_,
|
||||||
this->size_,
|
this->size_,
|
||||||
size);
|
size,
|
||||||
|
&config,
|
||||||
|
io.Fonts->GetGlyphRangesDefault());
|
||||||
|
|
||||||
if (!font) {
|
// Merge Greek and Mathematical symbols from IosevkaExtended as fallback
|
||||||
font = io.Fonts->AddFontDefault();
|
config.MergeMode = true;
|
||||||
}
|
static const ImWchar extended_ranges[] = {
|
||||||
|
0x0370, 0x03FF, // Greek and Coptic
|
||||||
|
0x2200, 0x22FF, // Mathematical Operators
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
|
kte::Fonts::IosevkaExtended::DefaultFontRegularCompressedData,
|
||||||
|
kte::Fonts::IosevkaExtended::DefaultFontRegularCompressedSize,
|
||||||
|
size,
|
||||||
|
&config,
|
||||||
|
extended_ranges);
|
||||||
|
|
||||||
(void) font;
|
|
||||||
io.Fonts->Build();
|
io.Fonts->Build();
|
||||||
}
|
}
|
||||||
} // namespace kte::Fonts
|
} // namespace kte::Fonts
|
||||||
9
main.cc
9
main.cc
@@ -1,3 +1,4 @@
|
|||||||
|
#include <clocale>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -111,8 +112,10 @@ RunStressHighlighter(unsigned seconds)
|
|||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, const char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
std::setlocale(LC_ALL, "");
|
||||||
|
|
||||||
Editor editor;
|
Editor editor;
|
||||||
|
|
||||||
// CLI parsing using getopt_long
|
// CLI parsing using getopt_long
|
||||||
@@ -133,7 +136,7 @@ main(int argc, const char *argv[])
|
|||||||
int opt;
|
int opt;
|
||||||
int long_index = 0;
|
int long_index = 0;
|
||||||
unsigned stress_seconds = 0;
|
unsigned stress_seconds = 0;
|
||||||
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "gthV", long_opts, &long_index)) != -1) {
|
while ((opt = getopt_long(argc, argv, "gthV", long_opts, &long_index)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'g':
|
case 'g':
|
||||||
req_gui = true;
|
req_gui = true;
|
||||||
@@ -300,7 +303,7 @@ main(int argc, const char *argv[])
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!fe->Init(editor)) {
|
if (!fe->Init(argc, argv, editor)) {
|
||||||
std::cerr << "kte: failed to initialize frontend" << std::endl;
|
std::cerr << "kte: failed to initialize frontend" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user