From dea44561eec40c161cee3aae4d3d70a7ab72c38e Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Mon, 31 Mar 2025 15:36:31 +0900 Subject: [PATCH] Restyle whole buffer on event to fix style breaks TODO: Make this more precise... some day --- src/: | 198 +++++++++++++++++++++++++++++++++++++++ src/gd_sophia_editor.erl | 46 ++++----- src/gd_v_devman.erl | 3 +- 3 files changed, 219 insertions(+), 28 deletions(-) create mode 100644 src/: diff --git a/src/: b/src/: new file mode 100644 index 0000000..bbd59d2 --- /dev/null +++ b/src/: @@ -0,0 +1,198 @@ +-module(gd_sophia_editor). +-export([new/1, update/2, + get_text/1, set_text/2]). + +-include("$zx_include/zx_logger.hrl"). +-include_lib("wx/include/wx.hrl"). + + +%%% Formatting Constants + +%% Style labels +-define(DEFAULT, 0). +-define(KEYWORD, 1). +-define(IDENTIFIER, 2). +-define(COMMENT, 3). +-define(STRING, 4). +-define(NUMBER, 5). +-define(OPERATOR, 6). + + + +%% Color palette + +% Intensities: +-define(H, 255). % High +-define(M, 192). % Medium +-define(L, 128). % Low +-define(X, 32). % X-Low +-define(Z, 0). % Zilch + + +% RGB values +% R G B +-define(black, {?Z, ?Z, ?Z}). +-define(light_red, {?H, ?Z, ?Z}). +-define(light_green, {?Z, ?H, ?Z}). +-define(light_blue, {?Z, ?Z, ?H}). +-define(yellow, {?H, ?H, ?Z}). +-define(light_magenta, {?H, ?Z, ?H}). +-define(light_cyan, {?Z, ?H, ?H}). +-define(high_white, {?H, ?H, ?H}). +-define(red, {?L, ?Z, ?Z}). +-define(green, {?Z, ?L, ?Z}). +-define(blue, {?Z, ?Z, ?L}). +-define(brown, {?L, ?L, ?Z}). +-define(magenta, {?L, ?Z, ?L}). +-define(cyan, {?Z, ?L, ?L}). +-define(not_black, {?X, ?X, ?X}). +-define(grey, {?L, ?L, ?L}). +-define(white, {?M, ?M, ?M}). + +styles() -> + [?DEFAULT, + ?KEYWORD, + ?IDENTIFIER, + ?COMMENT, + ?STRING, + ?NUMBER, + ?OPERATOR]. + +palette(light) -> + #{?DEFAULT => ?black, + ?KEYWORD => ?blue, + ?IDENTIFIER => ?cyan, + ?COMMENT => ?grey, + ?STRING => ?red, + ?NUMBER => ?magenta, + ?OPERATOR => ?brown, + bg => ?high_white}; +palette(dark) -> + #{?DEFAULT => ?white, + ?KEYWORD => ?light_cyan, + ?IDENTIFIER => ?green, + ?COMMENT => ?grey, + ?STRING => ?light_red, + ?NUMBER => ?light_magenta, + ?OPERATOR => ?yellow, + bg => ?not_black}. + +color_mode() -> + {R, G, B, _} = wxSystemSettings:getColour(?wxSYS_COLOUR_WINDOW), + case (lists:sum([R, G, B]) div 3) > 128 of + true -> light; + false -> dark + end. + + +new(Parent) -> + STC = wxStyledTextCtrl:new(Parent), + ok = wxStyledTextCtrl:setLexer(STC, ?wxSTC_LEX_CONTAINER), + FontSize = 13, + Mono = wxFont:new(FontSize, + ?wxFONTFAMILY_TELETYPE, + ?wxFONTSTYLE_NORMAL, + ?wxFONTWEIGHT_NORMAL, + [{face, "Monospace"}]), + SetMonospace = fun(Style) -> wxStyledTextCtrl:styleSetFont(STC, Style, Mono) end, + ok = lists:foreach(SetMonospace, styles()), + ok = wxStyledTextCtrl:styleSetFont(STC, ?wxSTC_STYLE_DEFAULT, Mono), + ok = set_colors(STC), + STC. + +get_text(STC) -> + wxStyledTextCtrl:getText(STC). + +set_text(STC, Text) -> + ok = wxStyledTextCtrl:setText(STC, Text), + %% Force Scintilla to request styling for the entire text + wxStyledTextCtrl:colourise(STC, 0, -1). + +%% ====================================================================== + +set_colors(STC) -> + ok = wxStyledTextCtrl:styleClearAll(STC), + Palette = #{bg := BGC} = palette(color_mode()), + Colorize = + fun(Style) -> + Color = maps:get(Style, Palette), + ok = wxStyledTextCtrl:styleSetForeground(STC, Style, Color), + ok = wxStyledTextCtrl:styleSetBackground(STC, Style, BGC) + end, + ok = wxStyledTextCtrl:styleSetBackground(STC, ?wxSTC_STYLE_DEFAULT, BGC), + lists:foreach(Colorize, styles()). + + +update(_Event, STC) -> + try + StartPos = wxStyledTextCtrl:getEndStyled(STC), + EndPos = wxStyledTextCtrl:getLength(STC), + Text = wxStyledTextCtrl:getTextRange(STC, StartPos, EndPos), + case so_scan:scan(Text) of + {ok, Tokens} -> + wxStyledTextCtrl:startStyling(STC, StartPos), + apply_styles(STC, Tokens); + {error, _Reason} -> + wxStyledTextCtrl:startStyling(STC, StartPos), + wxStyledTextCtrl:setStyling(STC, EndPos - StartPos, 0) + end + catch + error:E:T -> + dbg("CAUGHT error:~p / ~p~n", [E, T]) + end. + +apply_styles(STC, Tokens) -> + lists:foreach(fun(Token) -> style_token(STC, Token) end, Tokens). + +style_token(STC, Token) -> + {Type, Value} = type_and_value(Token), + {Line, Col} = element(2, Token), + Len = byte_size(to_binary(Value)), + Style = classify_style(Type, Value), + % Get exact position from Line and Column + Start = wxStyledTextCtrl:positionFromLine(STC, Line - 1) + (Col - 1), + wxStyledTextCtrl:startStyling(STC, Start), + wxStyledTextCtrl:setStyling(STC, Len, Style). + +to_binary(S) when is_list(S) -> + unicode:characters_to_binary(S); +to_binary(S) when is_binary(S) -> + S; +to_binary(I) when is_integer(I) -> + integer_to_binary(I). + +classify_style(Type, Value) -> + case Type of + symbol -> + case lists:member(Value, keywords()) of + true -> ?KEYWORD; + false -> ?OPERATOR + end; + id -> ?IDENTIFIER; + qid -> ?IDENTIFIER; + con -> ?IDENTIFIER; + qcon -> ?IDENTIFIER; + tvar -> ?IDENTIFIER; + string -> ?STRING; + char -> ?STRING; + int -> ?NUMBER; + hex -> ?NUMBER; + bytes -> ?NUMBER; + skip -> ?COMMENT; + _Other -> ?DEFAULT + end. + +type_and_value({Type,_Line,Value}) -> {Type, Value}; +type_and_value({Type, _}) -> {Type, atom_to_list(Type)}. + +keywords() -> + ["contract", "include", "let", "switch", "type", "record", "datatype", "if", + "elif", "else", "function", "stateful", "payable", "true", "false", "mod", + "public", "entrypoint", "private", "indexed", "namespace", "interface", + "main", "using", "as", "for", "hiding", "band", "bor", "bxor", "bnot"]. + +%% dbg(Fmt) -> +%% dbg(Fmt, []). + +dbg(Fmt, Args) -> + io:format("~p: " ++ Fmt, [self()|Args]). diff --git a/src/gd_sophia_editor.erl b/src/gd_sophia_editor.erl index 30217c9..33e8987 100644 --- a/src/gd_sophia_editor.erl +++ b/src/gd_sophia_editor.erl @@ -94,7 +94,6 @@ new(Parent) -> ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL, [{face, "Monospace"}]), - SetMonospace = fun(Style) -> wxStyledTextCtrl:styleSetFont(STC, Style, Mono) end, ok = lists:foreach(SetMonospace, styles()), ok = wxStyledTextCtrl:styleSetFont(STC, ?wxSTC_STYLE_DEFAULT, Mono), @@ -109,7 +108,6 @@ set_text(STC, Text) -> %% Force Scintilla to request styling for the entire text wxStyledTextCtrl:colourise(STC, 0, -1). -%% ====================================================================== set_colors(STC) -> ok = wxStyledTextCtrl:styleClearAll(STC), @@ -125,35 +123,34 @@ set_colors(STC) -> update(_Event, STC) -> - try - StartPos = wxStyledTextCtrl:getEndStyled(STC), - EndPos = wxStyledTextCtrl:getLength(STC), - Text = wxStyledTextCtrl:getTextRange(STC, StartPos, EndPos), + Text = wxStyledTextCtrl:getText(STC), case so_scan:scan(Text) of {ok, Tokens} -> - wxStyledTextCtrl:startStyling(STC, StartPos), + ok = wxStyledTextCtrl:startStyling(STC, 0), apply_styles(STC, Tokens); {error, _Reason} -> - wxStyledTextCtrl:startStyling(STC, StartPos), - wxStyledTextCtrl:setStyling(STC, EndPos - StartPos, 0) - end - catch - error:E:T -> - dbg("CAUGHT error:~p / ~p~n", [E, T]) + ok end. + apply_styles(STC, Tokens) -> lists:foreach(fun(Token) -> style_token(STC, Token) end, Tokens). + +% FIXME: 'qid' is not properly handled. If there are multi-dot qids, they will break style_token(STC, Token) -> {Type, Value} = type_and_value(Token), + {StartOffset, LengthOffset} = offset(Type), {Line, Col} = element(2, Token), - Len = byte_size(to_binary(Value)), + Length = byte_size(to_binary(Value)) + LengthOffset, Style = classify_style(Type, Value), - % Get exact position from Line and Column - Start = wxStyledTextCtrl:positionFromLine(STC, Line - 1) + (Col - 1), + Start = wxStyledTextCtrl:positionFromLine(STC, Line - 1) + Col + StartOffset, wxStyledTextCtrl:startStyling(STC, Start), - wxStyledTextCtrl:setStyling(STC, Len, Style). + wxStyledTextCtrl:setStyling(STC, Length, Style). + +offset(string) -> { 0, 0}; +offset(qid) -> {-1, 1}; +offset(_) -> {-1, 0}. to_binary(S) when is_list(S) -> unicode:characters_to_binary(S); @@ -162,6 +159,7 @@ to_binary(S) when is_binary(S) -> to_binary(I) when is_integer(I) -> integer_to_binary(I). + classify_style(Type, Value) -> case Type of symbol -> @@ -180,20 +178,16 @@ classify_style(Type, Value) -> hex -> ?NUMBER; bytes -> ?NUMBER; skip -> ?COMMENT; - _Other -> ?DEFAULT + _ -> ?DEFAULT end. -type_and_value({Type,_Line,Value}) -> {Type, Value}; -type_and_value({Type, _}) -> {Type, atom_to_list(Type)}. + +type_and_value({Type, _Line, Value}) -> {Type, Value}; +type_and_value({Type, _}) -> {Type, atom_to_list(Type)}. + keywords() -> ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", "stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace", "interface", "main", "using", "as", "for", "hiding", "band", "bor", "bxor", "bnot"]. - -%% dbg(Fmt) -> -%% dbg(Fmt, []). - -dbg(Fmt, Args) -> - io:format("~p: " ++ Fmt, [self()|Args]). diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 675eaca..def3093 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -296,10 +296,9 @@ terminate(Reason, State) -> %%% Doers -style(#s{code = {Codebook, Pages}}, Win, Event) -> +style(#s{code = {_, Pages}}, Win, Event) -> case lists:keyfind(Win, #p.win, Pages) of #p{code = STC} -> - tell("Received style event.~nWin: ~p~nEvent: ~p", [Win, Event]), gd_sophia_editor:update(Event, STC); false -> tell("Received bogus style event.~nWin: ~p~nEvent: ~p", [Win, Event])