summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2023-07-31 23:51:02 -0400
committerpommicket <pommicket@gmail.com>2023-08-01 12:54:24 -0400
commit7c5ca21fdd5fb3448376e9491528487ddb5db076 (patch)
tree4ff33aa87182f48a0e001bf1532799191b518d53
parentf8b5606c42ba3ae35baf823c12865cc7aa68d260 (diff)
fix inconsistencies btwn xoff_to_index & rendering
-rw-r--r--buffer.c148
-rw-r--r--development.md23
-rw-r--r--ted.h2
-rw-r--r--text.c5
-rw-r--r--text.h6
5 files changed, 80 insertions, 104 deletions
diff --git a/buffer.c b/buffer.c
index e2d32ea..2bc0efe 100644
--- a/buffer.c
+++ b/buffer.c
@@ -297,7 +297,9 @@ Settings *buffer_settings(TextBuffer *buffer) {
}
u8 buffer_tab_width(TextBuffer *buffer) {
- return buffer_settings(buffer)->tab_width;
+ if (!buffer->tab_width)
+ buffer->tab_width = buffer_settings(buffer)->tab_width;
+ return buffer->tab_width;
}
bool buffer_indent_with_spaces(TextBuffer *buffer) {
@@ -780,6 +782,29 @@ static void buffer_print(TextBuffer const *buffer) {
fflush(stdout);
}
+static void buffer_render_char(TextBuffer *buffer, Font *font, TextRenderState *state, char32_t c) {
+ switch (c) {
+ case '\n': assert(0); break;
+ case '\r': break; // for CRLF line endings
+ case '\t': {
+ u16 tab_width = buffer_tab_width(buffer);
+ double x = state->x;
+ double space_size = text_font_char_width(font, ' ');
+ double tab_width_px = tab_width * space_size;
+ double tabs = x / tab_width_px;
+ double tab_stop = (1.0 + floor(tabs)) * tab_width_px;
+ if (tab_stop - x < space_size * 0.5) {
+ // tab shouldn't be less than half a space
+ tab_stop += tab_width_px;
+ }
+ state->x = tab_stop;
+ } break;
+ default:
+ text_char_with_state(font, state, c);
+ break;
+ }
+}
+
// convert line character index to offset in pixels
static double buffer_index_to_xoff(TextBuffer *buffer, u32 line_number, u32 index) {
if (line_number >= buffer->nlines) {
@@ -788,41 +813,15 @@ static double buffer_index_to_xoff(TextBuffer *buffer, u32 line_number, u32 inde
}
Line *line = &buffer->lines[line_number];
char32_t *str = line->str;
- const Settings *settings = buffer_settings(buffer);
- Font *font = buffer_font(buffer);
- u8 tab_width = settings->tab_width;
if (index > line->len)
index = line->len;
-
- if (settings->force_monospace) {
- u32 col = 0;
- for (u32 i = 0; i < index; ++i) {
- switch (str[i]) {
- case '\t': {
- do
- ++col;
- while (col % tab_width);
- } break;
- default:
- ++col;
- break;
- }
- }
- return col * (double)text_font_char_width(font, ' ');
- } else {
- double x = 0;
- for (u32 i = 0; i < index; ++i) {
- switch (str[i]) {
- case '\t': {
- x += tab_width * text_font_char_width(font, ' ');
- } break;
- default:
- x += text_font_char_width(font, str[i]);
- break;
- }
- }
- return x;
+ Font *font = buffer_font(buffer);
+ TextRenderState state = text_render_state_default;
+ state.render = false;
+ for (u32 i = 0; i < index; ++i) {
+ buffer_render_char(buffer, font, &state, str[i]);
}
+ return state.x;
}
// convert line x offset in pixels to character index
@@ -836,52 +835,21 @@ static u32 buffer_xoff_to_index(TextBuffer *buffer, u32 line_number, double xoff
}
Line *line = &buffer->lines[line_number];
char32_t *str = line->str;
- const Settings *settings = buffer_settings(buffer);
Font *font = buffer_font(buffer);
- u8 tab_width = settings->tab_width;
-
- if (settings->force_monospace) {
- u32 target_col = (u32)(xoff / text_font_char_width(font, ' '));
- u32 col = 0;
- for (u32 i = 0; i < line->len; ++i) {
- switch (str[i]) {
- case '\t': {
- do {
- if (col == target_col)
- return i;
- ++col;
- } while (col % tab_width);
- } break;
- default:
- if (col == target_col)
- return i;
- ++col;
- break;
- }
- }
- return line->len;
- } else {
- double x = 0;
- for (u32 i = 0; i < line->len; ++i) {
- double x_prev = x;
- switch (str[i]) {
- case '\t': {
- x += tab_width * text_font_char_width(font, ' ');
- } break;
- default:
- x += text_font_char_width(font, str[i]);
- break;
- }
- if (x > xoff) {
- if (xoff - x_prev < x - xoff) {
- return i;
- } else {
- return i + 1;
- }
- }
+ TextRenderState state = text_render_state_default;
+ state.render = false;
+ for (u32 i = 0; i < line->len; ++i) {
+ double x0 = state.x;
+ buffer_render_char(buffer, font, &state, str[i]);
+ double x1 = state.x;
+ if (x1 > xoff) {
+ if (x1 - xoff > xoff - x0)
+ return i;
+ else
+ return i + 1;
}
- return line->len;
}
+ return line->len;
}
@@ -2956,7 +2924,9 @@ bool buffer_cursor_move_to_matching_bracket(TextBuffer *buffer) {
// Render the text buffer in the given rectangle
void buffer_render(TextBuffer *buffer, Rect r) {
+ const Settings *settings = buffer_settings(buffer);
+ buffer->tab_width = settings->tab_width;
buffer_lsp(buffer); // this will send didOpen/didClose if the buffer's LSP changed
if (r.size.x < 1 || r.size.y < 1) {
@@ -2978,7 +2948,6 @@ void buffer_render(TextBuffer *buffer, Rect r) {
float char_height = text_font_char_height(font);
Ted *ted = buffer->ted;
- const Settings *settings = buffer_settings(buffer);
const u32 *colors = settings->colors;
const float padding = settings->padding;
const float border_thickness = settings->border_thickness;
@@ -3081,7 +3050,6 @@ void buffer_render(TextBuffer *buffer, Rect r) {
// what x coordinate to start rendering the text from
double render_start_x = x1 - (double)buffer->scroll_x * text_font_char_width(font, ' ');
- u32 column = 0;
if (buffer->selection) { // draw selection
BufferPos sel_start = {0}, sel_end = {0};
@@ -3155,7 +3123,10 @@ void buffer_render(TextBuffer *buffer, Rect r) {
TextRenderState text_state = text_render_state_default;
- text_state.x = render_start_x;
+ text_state.x = 0;
+ // we need to start at x = 0 to be consistent with
+ // buffer_index_to_xoff and the like (because tabs are difficult).
+ text_state.x_render_offset = (float)render_start_x;
text_state.y = render_start_y;
text_state.min_x = x1;
text_state.min_y = y1;
@@ -3182,33 +3153,18 @@ void buffer_render(TextBuffer *buffer, Rect r) {
ColorSetting color = syntax_char_type_to_color_setting(type);
rgba_u32_to_floats(colors[color], text_state.color);
}
- switch (c) {
- case '\n': assert(0); break;
- case '\r': break; // for CRLF line endings
- case '\t': {
- uint tab_width = buffer_tab_width(buffer);
- do {
- text_char_with_state(font, &text_state, ' ');
- ++column;
- } while (column % tab_width);
- } break;
- default:
- text_char_with_state(font, &text_state, c);
- ++column;
- break;
- }
+ buffer_render_char(buffer, font, &text_state, c);
}
// next line
text_state_break_kerning(&text_state);
- text_state.x = render_start_x;
+ text_state.x = 0;
if (text_state.y > text_state.max_y) {
buffer->last_line_on_screen = line_idx;
// made it to the bottom of the buffer view.
break;
}
text_state.y += text_font_char_height(font);
- column = 0;
}
if (buffer->last_line_on_screen == 0) buffer->last_line_on_screen = nlines - 1;
diff --git a/development.md b/development.md
index 7ad5b7d..c633885 100644
--- a/development.md
+++ b/development.md
@@ -5,21 +5,32 @@
To build the debug version of ted, run `make` (or `make.bat` on Windows).
By default we use the `ninja` build system (`sudo apt install ninja` or similar).
+## Architecture
+
+The big `Ted` struct has basically all of the data for ted.
+
+In general, fixed-size types such as `u32` should be preferred to `unsigned int` & co.
+
+
## Files
Most function declarations should go in `ted.h`.
The exceptions are only for self-contained files, like `text.c`,
which gets its own header file `text.h`.
-As much as possible, OS-dependent functions should be put in `os.h/os-*.c`.
-(But "pure" functions like `qsort_with_context`, which could
-in theory be implemented on any platform with just plain C, should be put
-in `util.c` even if they use OS-specific library functions.)
+Everything includes `base.h`, which has useful type definitions and stuff.
+This includes shorter names for fixed-size types (e.g. `i8` for `int8_t`).
+
+`util.h`/`util.c` has "utility" functions which aren't specific to ted.
+
+`os.h`/`os-*.c` has OS-dependent functions such as file system
+stuff which is (annoyingly) lacking from standard C.
## Unicode
-ted stores text as UTF-32. We assume that code points are characters.
-This is not correct for combining diacritics and will hopefully be fixed at some point.
+ted stores text as UTF-32. Left/right move by code points,
+which isn't correct for e.g. combining diacritics. Hopefully this will
+be fixed at some point.
All paths are stored as UTF-8, and annoyingly have to be converted to UTF-16 for Windows
functions (why hasn't Windows made UTF-8 versions of all their API functions yet to save
diff --git a/ted.h b/ted.h
index 956ccef..f6c0e78 100644
--- a/ted.h
+++ b/ted.h
@@ -412,6 +412,8 @@ typedef struct {
u32 nlines;
/// capacity of \ref lines
u32 lines_capacity;
+ /// cached tab width, updated every frame
+ u8 tab_width;
/// which LSP this document is open in
LSPID lsp_opened_in;
diff --git a/text.c b/text.c
index 8b2acc3..de5438c 100644
--- a/text.c
+++ b/text.c
@@ -63,6 +63,7 @@ const TextRenderState text_render_state_default = {
.color = {1, 0, 1, 1},
.x_largest = -FLT_MAX, .y_largest = -FLT_MAX,
.prev_glyph = 0,
+ .x_render_offset = 0,
};
static char text_err[200];
@@ -411,8 +412,8 @@ top:;
float s0 = q.s0, t0 = q.t0;
float s1 = q.s1, t1 = q.t1;
- float x0 = roundf(q.x0), y0 = roundf(q.y0);
- float x1 = roundf(q.x1), y1 = roundf(q.y1);
+ float x0 = roundf(q.x0 + state->x_render_offset), y0 = roundf(q.y0);
+ float x1 = roundf(q.x1 + state->x_render_offset), y1 = roundf(q.y1);
const float min_x = state->min_x, max_x = state->max_x;
const float min_y = state->min_y, max_y = state->max_y;
diff --git a/text.h b/text.h
index 8b2f133..3fd603c 100644
--- a/text.h
+++ b/text.h
@@ -44,6 +44,12 @@ typedef struct {
/// index of previous glyph rendered, or 0 if this is the first
int prev_glyph;
+ /// added to x for rendering
+ /// this exists for complicated reasons
+ /// basically we want a way of consistently getting the size
+ /// of text without error from floating point imprecision
+ float x_render_offset;
+
/// used for forwards-compatibility
char _reserved[64];
} TextRenderState;