From 27b78f8623b29f90744a7cb3f2b29bad302de676 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Sun, 30 Mar 2025 21:16:21 +0200 Subject: [PATCH] First stab at supporting syntax highlighting via wxStyledTextCtrl --- src/gd_sophia_editor.erl | 113 +++++++++++++++++++++++++++++++++++++++ src/gd_v_devman.erl | 35 +++++++++--- 2 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 src/gd_sophia_editor.erl diff --git a/src/gd_sophia_editor.erl b/src/gd_sophia_editor.erl new file mode 100644 index 0000000..dff5e3e --- /dev/null +++ b/src/gd_sophia_editor.erl @@ -0,0 +1,113 @@ +-module(gd_sophia_editor). +-export([new/3, get_text/1, set_text/2]). + +-include_lib("wx/include/wx.hrl"). + + +new(Parent, Id, Opts) -> + STC = wxStyledTextCtrl:new(Parent, [{id, Id} | Opts]), + + %% Set up the container lexer + wxStyledTextCtrl:setLexer(STC, ?wxSTC_LEX_CONTAINER), + + %% Define styles + set_colors(STC), + %% Connect the styling event + wxStyledTextCtrl:connect( + STC, 'stc_styleneeded', + [{callback, fun(Event, _) -> style_needed(Event, STC) end}]), + STC. + +get_text(STC) -> + wxStyledTextCtrl:getText(STC). + +set_text(STC, Text) -> + wxStyledTextCtrl:setText(STC, Text), + %% Force Scintilla to request styling for the entire text + wxStyledTextCtrl:colourise(STC, 0, -1). + +%% ====================================================================== + +set_colors(STC) -> + wxStyledTextCtrl:styleClearAll(STC), + wxStyledTextCtrl:styleSetForeground(STC, 0, {0, 0, 0}), % Default: Black + wxStyledTextCtrl:styleSetForeground(STC, 1, {0, 0, 192}), % Keywords: Deep Blue + wxStyledTextCtrl:styleSetForeground(STC, 2, {0, 0, 0}), % Identifiers: Black + wxStyledTextCtrl:styleSetForeground(STC, 3, {128, 128, 128}), % Comments: Gray + wxStyledTextCtrl:styleSetForeground(STC, 4, {163, 21, 21}), % Strings/Chars: Reddish + wxStyledTextCtrl:styleSetForeground(STC, 5, {128, 0, 128}), % Numbers: Purple + wxStyledTextCtrl:styleSetForeground(STC, 6, {0, 0, 0}). % Operators: Black + +style_needed(_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 -> 1; + false -> 6 + end; + id -> 2; + qid -> 2; + con -> 2; + qcon -> 2; + tvar -> 2; + string -> 4; + char -> 4; + int -> 5; + hex -> 5; + bytes -> 5; + skip -> 3; + _Other -> 1 + 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_v_devman.erl b/src/gd_v_devman.erl index 4184ff7..434abcf 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -15,6 +15,8 @@ -include("$zx_include/zx_logger.hrl"). -include("gd.hrl"). +-define(editorMode, sophia). + % Widgets -record(w, {name = none :: atom() | {FunName :: binary(), call | dryr}, @@ -514,12 +516,20 @@ add_code_page(State = #s{tabs = TopBook, code = {Codebook, Pages}}, Location, Co PageSz = wxBoxSizer:new(?wxHORIZONTAL), CodeTxStyle = {style, ?wxTE_MULTILINE bor ?wxTE_PROCESS_TAB bor ?wxTE_DONTWRAP}, - CodeTx = wxTextCtrl:new(Window, ?wxID_ANY, [CodeTxStyle]), + CodeTx = case ?editorMode of + plain -> wxTextCtrl:new(Window, ?wxID_ANY, [CodeTxStyle]); + sophia -> gd_sophia_editor:new(Window, ?wxID_ANY, [CodeTxStyle]) + end, TextAt = wxTextAttr:new(), Mono = wxFont:new(10, ?wxMODERN, ?wxNORMAL, ?wxNORMAL, [{face, "Monospace"}]), ok = wxTextAttr:setFont(TextAt, Mono), - true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt), - ok = wxTextCtrl:setValue(CodeTx, Code), + case ?editorMode of + plain -> + true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt), + ok = wxTextCtrl:setValue(CodeTx, Code); + sophia -> + gd_sophia_editor:set_text(CodeTx, Code) + end, _ = wxSizer:add(PageSz, CodeTx, zxw:flags(wide)), @@ -910,7 +920,7 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) _ -> Name ++ ".aes" end, Path = filename:join(Dir, File), - Source = wxTextCtrl:getValue(Widget), + Source = get_source(Widget), case filelib:ensure_dir(Path) of ok -> case file:write_file(Path, Source) of @@ -938,6 +948,19 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) end end. +get_source(Widget) -> + case ?editorMode of + plain -> wxTextCtrl:getValue(Widget); + sophia -> gd_sophia_editor:get_text(Widget) + end. + +set_source(Widget, Src) -> + case ?editorMode of + plain -> wxTextCtrl:setValue(Widget, Src); + sophia -> gd_sophia_editor:set_text(Widget, Src) + end. + + % TODO: Break this down -- tons of things in here recur. rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) -> case wxNotebook:getSelection(Codebook) of @@ -968,7 +991,7 @@ rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}} _ -> Name ++ ".aes" end, NewPath = filename:join(Dir, File), - Source = wxTextCtrl:getValue(Widget), + Source = get_source(Widget), case filelib:ensure_dir(NewPath) of ok -> case file:write_file(NewPath, Source) of @@ -1084,7 +1107,7 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j TextAt = wxTextAttr:new(), ok = wxTextAttr:setFont(TextAt, Mono), true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt), - ok = wxTextCtrl:setValue(CodeTx, Source), + ok = set_source(CodeTx, Source), _ = wxSizer:add(CodeSz, CodeTx, zxw:flags(wide)), ScrollWin = wxScrolledWindow:new(Window), FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Function Interfaces")}]),