diff --git a/changelog.md b/changelog.md index 8f3fc4a3..30f6b557 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,12 @@ This files document the changes done in Lite XL for each release. Add `system.set_window_opacity` function. +Add codepoint replacement API to support natively the "draw whitespaces" option. +It replace the `drawwhitespace` plugin. If can be configured using the +`config.draw_whitespace` boolean variable and enabled and disables using the +commands `draw-whitespace:toggle`, `draw-whitespace:enable`, +`draw-whitespace:disable`. + ### 1.16.5 Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122 diff --git a/data/core/command.lua b/data/core/command.lua index 513e0701..95743061 100644 --- a/data/core/command.lua +++ b/data/core/command.lua @@ -59,7 +59,7 @@ end function command.add_defaults() - local reg = { "core", "root", "command", "doc", "findreplace", "files" } + local reg = { "core", "root", "command", "doc", "findreplace", "files", "drawwhitespace" } for _, name in ipairs(reg) do require("core.commands." .. name) end diff --git a/data/core/commands/drawwhitespace.lua b/data/core/commands/drawwhitespace.lua new file mode 100644 index 00000000..9372c55d --- /dev/null +++ b/data/core/commands/drawwhitespace.lua @@ -0,0 +1,16 @@ +local command = require "core.command" +local config = require "core.config" + +command.add(nil, { + ["draw-whitespace:toggle"] = function() + config.draw_whitespace = not config.draw_whitespace + end, + + ["draw-whitespace:disable"] = function() + config.draw_whitespace = false + end, + + ["draw-whitespace:enable"] = function() + config.draw_whitespace = true + end, +}) diff --git a/data/core/config.lua b/data/core/config.lua index 8cc7d556..5ee5d576 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -21,6 +21,7 @@ config.max_project_files = 2000 config.transitions = true config.animation_rate = 1.0 config.blink_period = 0.8 +config.draw_whitespace = false -- Disable plugin loading setting to false the config entry -- of the same name. diff --git a/data/core/docview.lua b/data/core/docview.lua index 7faa18dd..2c33d0a3 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -314,7 +314,11 @@ function DocView:draw_line_text(idx, x, y) local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset() for _, type, text in self.doc.highlighter:each_token(idx) do local color = style.syntax[type] - tx = renderer.draw_text_subpixel(font, text, tx, ty, color) + if config.draw_whitespace then + tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment) + else + tx = renderer.draw_text_subpixel(font, text, tx, ty, color) + end end end diff --git a/data/core/init.lua b/data/core/init.lua index f8833df6..16a09c31 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -345,6 +345,14 @@ function core.remove_project_directory(path) end +local function whitespace_replacements() + local r = renderer.replacements.new() + r:add(" ", "·") + r:add("\t", "»") + return r +end + + function core.init() command = require "core.command" keymap = require "core.keymap" @@ -416,6 +424,7 @@ function core.init() core.redraw = true core.visited_files = {} core.restart_request = false + core.replacements = whitespace_replacements() core.root_view = RootView() core.command_view = CommandView() diff --git a/src/api/api.h b/src/api/api.h index 3a93ad92..4b0e14f0 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -6,6 +6,7 @@ #include "lualib.h" #define API_TYPE_FONT "Font" +#define API_TYPE_REPLACE "Replace" void api_load_libs(lua_State *L); diff --git a/src/api/cp_replace.c b/src/api/cp_replace.c new file mode 100644 index 00000000..a0fb3ac8 --- /dev/null +++ b/src/api/cp_replace.c @@ -0,0 +1,42 @@ +#include "api.h" +#include "renderer.h" + + +static int f_new(lua_State *L) { + CPReplaceTable *rep_table = lua_newuserdata(L, sizeof(CPReplaceTable)); + luaL_setmetatable(L, API_TYPE_REPLACE); + ren_cp_replace_init(rep_table); + return 1; +} + + +static int f_gc(lua_State *L) { + CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE); + ren_cp_replace_free(rep_table); + return 0; +} + + +static int f_add(lua_State *L) { + CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE); + const char *src = luaL_checkstring(L, 2); + const char *dst = luaL_checkstring(L, 3); + ren_cp_replace_add(rep_table, src, dst); + return 0; +} + + +static const luaL_Reg lib[] = { + { "__gc", f_gc }, + { "new", f_new }, + { "add", f_add }, + { NULL, NULL } +}; + +int luaopen_renderer_replacements(lua_State *L) { + luaL_newmetatable(L, API_TYPE_REPLACE); + luaL_setfuncs(L, lib, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + return 1; +} diff --git a/src/api/renderer.c b/src/api/renderer.c index bba86238..6e4ebf40 100644 --- a/src/api/renderer.c +++ b/src/api/renderer.c @@ -71,28 +71,37 @@ static int f_draw_rect(lua_State *L) { return 0; } - -static int f_draw_text(lua_State *L) { +static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) { RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT); const char *text = luaL_checkstring(L, 2); - int x = luaL_checknumber(L, 3); + /* The coordinate below will be in subpixels iff draw_subpixel is true. + Otherwise it will be in pixels. */ + int x_subpixel = luaL_checknumber(L, 3); int y = luaL_checknumber(L, 4); RenColor color = checkcolor(L, 5, 255); - x = rencache_draw_text(*font, text, x, y, color, false); - lua_pushnumber(L, x); + + CPReplaceTable *rep_table; + RenColor replace_color; + if (lua_gettop(L) >= 7) { + rep_table = luaL_checkudata(L, 6, API_TYPE_REPLACE); + replace_color = checkcolor(L, 7, 255); + } else { + rep_table = NULL; + replace_color = (RenColor) {0}; + } + + x_subpixel = rencache_draw_text(*font, text, x_subpixel, y, color, draw_subpixel, rep_table, replace_color); + lua_pushnumber(L, x_subpixel); return 1; } +static int f_draw_text(lua_State *L) { + return draw_text_subpixel_impl(L, false); +} + static int f_draw_text_subpixel(lua_State *L) { - RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT); - const char *text = luaL_checkstring(L, 2); - int x_subpixel = luaL_checknumber(L, 3); - int y = luaL_checknumber(L, 4); - RenColor color = checkcolor(L, 5, 255); - x_subpixel = rencache_draw_text(*font, text, x_subpixel, y, color, true); - lua_pushnumber(L, x_subpixel); - return 1; + return draw_text_subpixel_impl(L, true); } @@ -110,10 +119,13 @@ static const luaL_Reg lib[] = { int luaopen_renderer_font(lua_State *L); +int luaopen_renderer_replacements(lua_State *L); int luaopen_renderer(lua_State *L) { luaL_newlib(L, lib); luaopen_renderer_font(L); lua_setfield(L, -2, "font"); + luaopen_renderer_replacements(L); + lua_setfield(L, -2, "replacements"); return 1; } diff --git a/src/meson.build b/src/meson.build index e6e50cc8..d760b165 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,6 @@ lite_sources = [ 'api/api.c', + 'api/cp_replace.c', 'api/renderer.c', 'api/renderer_font.c', 'api/system.c', diff --git a/src/rencache.c b/src/rencache.c index 98e80009..e9796f81 100644 --- a/src/rencache.c +++ b/src/rencache.c @@ -1,3 +1,4 @@ +#include #include #include "rencache.h" @@ -14,13 +15,16 @@ enum { FREE_FONT, SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL }; typedef struct { - int type, size; + int8_t type; + int8_t tab_size; + int8_t subpixel_scale; + int8_t x_subpixel_offset; + int32_t size; RenRect rect; RenColor color; RenFont *font; - short int subpixel_scale; - short int x_subpixel_offset; - int tab_size; + CPReplaceTable *replacements; + RenColor replace_color; char text[0]; } Command; @@ -130,7 +134,10 @@ void rencache_draw_rect(RenRect rect, RenColor color) { } } -int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, bool draw_subpixel) { +int rencache_draw_text(RenFont *font, + const char *text, int x, int y, RenColor color, bool draw_subpixel, + CPReplaceTable *replacements, RenColor replace_color) +{ int subpixel_scale; int w_subpixel = ren_get_font_width(font, text, &subpixel_scale); RenRect rect; @@ -150,6 +157,8 @@ int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor c cmd->subpixel_scale = (draw_subpixel ? subpixel_scale : 1); cmd->x_subpixel_offset = x - subpixel_scale * rect.x; cmd->tab_size = ren_get_font_tab_size(font); + cmd->replacements = replacements; + cmd->replace_color = replace_color; } } @@ -262,11 +271,14 @@ void rencache_end_frame(void) { break; case DRAW_TEXT: ren_set_font_tab_size(cmd->font, cmd->tab_size); - ren_draw_text(cmd->font, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color); + ren_draw_text(cmd->font, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color, + cmd->replacements, cmd->replace_color); break; case DRAW_TEXT_SUBPIXEL: ren_set_font_tab_size(cmd->font, cmd->tab_size); - ren_draw_text_subpixel(cmd->font, cmd->text, cmd->subpixel_scale * cmd->rect.x + cmd->x_subpixel_offset, cmd->rect.y, cmd->color); + ren_draw_text_subpixel(cmd->font, cmd->text, + cmd->subpixel_scale * cmd->rect.x + cmd->x_subpixel_offset, cmd->rect.y, cmd->color, + cmd->replacements, cmd->replace_color); break; } } diff --git a/src/rencache.h b/src/rencache.h index eb988229..a532c9b5 100644 --- a/src/rencache.h +++ b/src/rencache.h @@ -8,7 +8,7 @@ void rencache_show_debug(bool enable); void rencache_free_font(RenFont *font); void rencache_set_clip_rect(RenRect rect); void rencache_draw_rect(RenRect rect, RenColor color); -int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, bool draw_subpixel); +int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, bool draw_subpixel, CPReplaceTable *replacements, RenColor replace_color); void rencache_invalidate(void); void rencache_begin_frame(void); void rencache_end_frame(void); diff --git a/src/renderer.c b/src/renderer.c index 562dc0e8..36a76604 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -6,6 +6,7 @@ #include "font_renderer.h" #define MAX_GLYPHSET 256 +#define REPLACEMENT_CHUNK_SIZE 8 struct RenImage { RenColor *pixels; @@ -60,6 +61,37 @@ static const char* utf8_to_codepoint(const char *p, unsigned *dst) { } +void ren_cp_replace_init(CPReplaceTable *rep_table) { + rep_table->size = 0; + rep_table->replacements = NULL; +} + + +void ren_cp_replace_free(CPReplaceTable *rep_table) { + free(rep_table->replacements); +} + + +void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst) { + int table_size = rep_table->size; + if (table_size % REPLACEMENT_CHUNK_SIZE == 0) { + CPReplace *old_replacements = rep_table->replacements; + const int new_size = (table_size / REPLACEMENT_CHUNK_SIZE + 1) * REPLACEMENT_CHUNK_SIZE; + rep_table->replacements = malloc(new_size * sizeof(CPReplace)); + if (!rep_table->replacements) { + rep_table->replacements = old_replacements; + return; + } + memcpy(rep_table->replacements, old_replacements, table_size * sizeof(CPReplace)); + free(old_replacements); + } + CPReplace *rep = &rep_table->replacements[table_size]; + utf8_to_codepoint(src, &rep->codepoint_src); + utf8_to_codepoint(dst, &rep->codepoint_dst); + rep_table->size = table_size + 1; +} + + void ren_init(SDL_Window *win) { assert(win); window = win; @@ -291,31 +323,55 @@ void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color) } } +static int codepoint_replace(CPReplaceTable *rep_table, unsigned *codepoint) { + for (int i = 0; i < rep_table->size; i++) { + const CPReplace *rep = &rep_table->replacements[i]; + if (*codepoint == rep->codepoint_src) { + *codepoint = rep->codepoint_dst; + return 1; + } + } + return 0; +} -void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color) { + +void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color, + CPReplaceTable *replacements, RenColor replace_color) +{ const char *p = text; unsigned codepoint; SDL_Surface *surf = SDL_GetWindowSurface(window); - FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b }; + const FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b }; while (*p) { + FR_Color color_rep; p = utf8_to_codepoint(p, &codepoint); GlyphSet *set = get_glyphset(font, codepoint); FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff]; + const int xadvance_original_cp = g->xadvance; + const int replaced = replacements ? codepoint_replace(replacements, &codepoint) : 0; + if (replaced) { + set = get_glyphset(font, codepoint); + g = &set->glyphs[codepoint & 0xff]; + color_rep = (FR_Color) { .r = replace_color.r, .g = replace_color.g, .b = replace_color.b}; + } else { + color_rep = color_fr; + } if (color.a != 0) { FR_Blend_Glyph(font->renderer, &clip, - x_subpixel, y, (uint8_t *) surf->pixels, surf->w, set->image, g, color_fr); + x_subpixel, y, (uint8_t *) surf->pixels, surf->w, set->image, g, color_rep); } - x_subpixel += g->xadvance; + x_subpixel += xadvance_original_cp; } } - -void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) { +void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, + CPReplaceTable *replacements, RenColor replace_color) +{ const int subpixel_scale = FR_Subpixel_Scale(font->renderer); - ren_draw_text_subpixel(font, text, subpixel_scale * x, y, color); + ren_draw_text_subpixel(font, text, subpixel_scale * x, y, color, replacements, replace_color); } - +// Could be declared as static inline int ren_font_subpixel_round(int width, int subpixel_scale, int orientation) { int w_mult; if (orientation < 0) { @@ -332,3 +388,4 @@ int ren_font_subpixel_round(int width, int subpixel_scale, int orientation) { int ren_get_font_subpixel_scale(RenFont *font) { return FR_Subpixel_Scale(font->renderer); } + diff --git a/src/renderer.h b/src/renderer.h index cc1e4b23..2b1f29d3 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -21,6 +21,19 @@ enum { typedef struct { uint8_t b, g, r, a; } RenColor; typedef struct { int x, y, width, height; } RenRect; +struct CPReplace { + unsigned codepoint_src; + unsigned codepoint_dst; +}; +typedef struct CPReplace CPReplace; + + +struct CPReplaceTable { + int size; + CPReplace *replacements; +}; +typedef struct CPReplaceTable CPReplaceTable; + void ren_init(SDL_Window *win); void ren_update_rects(RenRect *rects, int count); @@ -41,7 +54,12 @@ int ren_font_subpixel_round(int width, int subpixel_scale, int orientation); void ren_draw_rect(RenRect rect, RenColor color); void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color); -void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color); -void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color); +void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color); +void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color); + +void ren_cp_replace_init(CPReplaceTable *rep_table); +void ren_cp_replace_free(CPReplaceTable *rep_table); +void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst); +void ren_cp_replace_clear(CPReplaceTable *rep_table); #endif