From 43d6e5486f01222ee71f937db931696083d0b065 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:08:32 +0100 Subject: [PATCH 01/27] added 3 new colour schemes --- config/catppuccin-mocha.lua | 83 +++++++++++ config/ox-transparent.lua | 268 ++++++++++++++++++++++++++++++++++++ config/tropical.lua | 85 ++++++++++++ 3 files changed, 436 insertions(+) create mode 100644 config/catppuccin-mocha.lua create mode 100644 config/ox-transparent.lua create mode 100644 config/tropical.lua diff --git a/config/catppuccin-mocha.lua b/config/catppuccin-mocha.lua new file mode 100644 index 00000000..87afdeaa --- /dev/null +++ b/config/catppuccin-mocha.lua @@ -0,0 +1,83 @@ +-- Define user-defined commands +commands = { + ["reload"] = function(arguments) + editor:reload_config() + editor:display_info("Configuration file and plugins reloaded") + end, +} + +-- Pallette -- +black = '#1e1e2e' +darkgrey = '#24273a' +lightgrey = '#303446' +verylightgrey = '#7f849c' +white = '#cdd6f4' +brown = '#f2cdcd' +red = '#f38ba8' +orange = '#fab387' +yellow = '#f9e2af' +green = '#a6e3a1' +lightblue = '#89dceb' +darkblue = '#89b4fa' +purple = '#cba6f7' +pink = '#f5c2e7' + +-- Configure Colours -- +colors.editor_bg = black +colors.editor_fg = white +colors.line_number_fg = lightgrey +colors.line_number_bg = black + +colors.status_bg = darkgrey +colors.status_fg = purple + +colors.highlight = purple + +colors.tab_inactive_bg = darkgrey +colors.tab_inactive_fg = white +colors.tab_active_bg = lightgrey +colors.tab_active_fg = purple + +colors.info_bg = black +colors.info_fg = lightblue +colors.warning_bg = black +colors.warning_fg = yellow +colors.error_bg = black +colors.error_fg = red + +colors.selection_bg = darkgrey +colors.selection_fg = lightblue + +-- Configure Syntax Highlighting Colours -- +syntax:set("string", green) -- Strings, bright green +syntax:set("comment", verylightgrey) -- Comments, light purple/gray +syntax:set("digit", red) -- Digits, cyan +syntax:set("keyword", purple) -- Keywords, vibrant pink +syntax:set("attribute", lightblue) -- Attributes, cyan +syntax:set("character", darkblue) -- Characters, cyan +syntax:set("type", yellow) -- Types, light purple +syntax:set("function", darkblue) -- Function names, light purple +syntax:set("header", lightblue) -- Headers, cyan +syntax:set("macro", red) -- Macros, red +syntax:set("namespace", darkblue) -- Namespaces, light purple +syntax:set("struct", pink) -- Structs, classes, and enums, light purple +syntax:set("operator", verylightgrey) -- Operators, light purple/gray +syntax:set("boolean", green) -- Booleans, bright green +syntax:set("table", purple) -- Tables, light purple +syntax:set("reference", pink) -- References, vibrant pink +syntax:set("tag", darkblue) -- Tags (e.g. HTML tags), cyan +syntax:set("heading", purple) -- Headings, light purple +syntax:set("link", pink) -- Links, vibrant pink +syntax:set("key", pink) -- Keys, vibrant pink +syntax:set("quote", verylightgrey) -- Quotes, light purple/gray +syntax:set("bold", red) -- Bold text, cyan +syntax:set("italic", orange) -- Italic text, cyan +syntax:set("block", lightblue) -- Code blocks, cyan +syntax:set("image", lightblue) -- Images in markup languages, cyan +syntax:set("list", green) -- Lists, bright green +syntax:set("insertion", green) -- Insertions (e.g. diff highlight), bright green +syntax:set("deletion", red) -- Deletions (e.g. diff highlight), red + +-- Import plugins (must be at the bottom of this file) +load_plugin("pairs.lua") +load_plugin("autoindent.lua") diff --git a/config/ox-transparent.lua b/config/ox-transparent.lua new file mode 100644 index 00000000..21991a50 --- /dev/null +++ b/config/ox-transparent.lua @@ -0,0 +1,268 @@ +-- Configure Events -- +event_mapping = { + -- Cursor movement + ["up"] = function() + editor:move_up() + end, + ["down"] = function() + editor:move_down() + end, + ["left"] = function() + editor:move_left() + end, + ["right"] = function() + editor:move_right() + end, + ["shift_up"] = function() + editor:select_up() + end, + ["shift_down"] = function() + editor:select_down() + end, + ["shift_left"] = function() + editor:select_left() + end, + ["shift_right"] = function() + editor:select_right() + end, + ["ctrl_up"] = function() + editor:move_top() + end, + ["ctrl_down"] = function() + editor:move_bottom() + end, + ["ctrl_left"] = function() + editor:move_previous_word() + end, + ["ctrl_right"] = function() + editor:move_next_word() + end, + ["home"] = function() + editor:move_home() + end, + ["end"] = function() + editor:move_end() + end, + ["pageup"] = function() + editor:move_page_up() + end, + ["pagedown"] = function() + editor:move_page_down() + end, + ["ctrl_g"] = function() + local line = editor:prompt("Go to line") + editor:move_to(0, tonumber(line)) + end, + -- Searching & Replacing + ["ctrl_f"] = function() + editor:search() + end, + ["ctrl_r"] = function() + editor:replace() + end, + -- Document Management + ["ctrl_n"] = function() + editor:new() + end, + ["ctrl_o"] = function() + editor:open() + end, + ["ctrl_s"] = function() + editor:save() + end, + ["alt_s"] = function() + editor:save_as() + end, + ["alt_a"] = function() + editor:save_all() + end, + ["ctrl_q"] = function() + editor:quit() + end, + ["alt_left"] = function() + editor:previous_tab() + end, + ["alt_right"] = function() + editor:next_tab() + end, + -- Clipboard Interaction + ["ctrl_a"] = function() + editor:select_all() + end, + ["ctrl_x"] = function() + editor:cut() + end, + ["ctrl_c"] = function() + editor:copy() + end, + ["ctrl_v"] = function() + editor:display_info("Use ctrl+shift+v for paste or set your terminal emulator to do paste on ctrl+v") + end, + -- Undo & Redo + ["ctrl_z"] = function() + editor:undo() + end, + ["ctrl_y"] = function() + editor:redo() + end, + -- Miscellaneous + ["ctrl_h"] = function() + help_message.enabled = not help_message.enabled + end, + ["ctrl_d"] = function() + editor:remove_line() + end, + ["ctrl_k"] = function() + editor:open_command_line() + end, + ["alt_up"] = function() + -- current line information + line = editor:get_line() + y = editor.cursor.y + -- insert a new line + editor:insert_line_at(line, y - 1) + -- delete old copy and reposition cursor + editor:remove_line_at(y + 1) + editor:move_up() + -- correct indentation level + autoindent:fix_indent() + end, + ["alt_down"] = function() + -- current line information + line = editor:get_line() + y = editor.cursor.y + -- insert a new line + editor:insert_line_at(line, y + 2) + -- delete old copy and reposition cursor + editor:remove_line_at(y) + editor:move_down() + -- correct indentation level + autoindent:fix_indent() + end, + ["ctrl_w"] = function() + y = editor.cursor.y + x = editor.cursor.x + if editor:get_character() == " " then + start = 0 + else + start = 1 + end + editor:move_previous_word() + new_x = editor.cursor.x + diff = x - new_x + if editor.cursor.y == y then + -- Cursor on the same line + for i = start, diff do + editor:remove_at(new_x, y) + end + else + -- Cursor has passed up onto the previous line + end + end, +} + +-- Define user-defined commands +commands = { + ["readonly"] = function(arguments) + arg = arguments[1] + if arg == "true" then + editor:set_read_only(true) + elseif arg == "false" then + editor:set_read_only(false) + end + end, + ["filetype"] = function(arguments) + local file_type_name = table.concat(arguments, " ") + editor:set_file_type(file_type_name) + end, + ["reload"] = function(arguments) + editor:reload_config() + editor:reload_plugins() + editor:display_info("Configuration file and plugins reloaded") + end, +} + +-- Configure Documents -- +document.tab_width = 4 +document.indentation = "spaces" +document.undo_period = 10 +document.wrap_cursor = true + +-- Configure Colours -- +colors.editor_bg = 'transparent' +colors.editor_fg = {255, 255, 255} +colors.line_number_fg = {65, 65, 98} +colors.line_number_bg = 'transparent' + +colors.status_bg = {59, 59, 84} +colors.status_fg = {35, 240, 144} + +colors.highlight = {35, 240, 144} + +colors.tab_inactive_fg = {255, 255, 255} +colors.tab_inactive_bg = {41, 41, 61} +colors.tab_active_fg = {35, 240, 144} +colors.tab_active_bg = {59, 59, 84} + +colors.info_fg = {99, 162, 255} +colors.info_bg = 'transparent' +colors.warning_fg = {255, 182, 99} +colors.warning_bg = 'transparent' +colors.error_fg = {255, 100, 100} +colors.error_bg = 'transparent' + +colors.selection_fg = {255, 255, 255} +colors.selection_bg = {59, 59, 130} + +-- Configure Line Numbers -- +line_numbers.enabled = true +line_numbers.padding_left = 1 +line_numbers.padding_right = 1 + +-- Configure Mouse Behaviour -- +terminal.mouse_enabled = true + +-- Configure Tab Line -- +tab_line.enabled = true +tab_line.format = " {file_name}{modified} " + +-- Configure Status Line -- +status_line:add_part(" {file_name}{modified} │ {file_type} │") -- The left side of the status line +status_line:add_part("│ {cursor_y} / {line_count} {cursor_x} ") -- The right side of the status line + +status_line.alignment = "between" -- This will put a space between the left and right sides + +-- Configure Greeting and Help Messages -- +greeting_message.enabled = true +help_message.enabled = false + +-- Configure Syntax Highlighting Colours -- +syntax:set("string", {39, 222, 145}) -- Strings in various programming languages +syntax:set("comment", {113, 113, 169}) -- Comments in various programming languages +syntax:set("digit", {40, 198, 232}) -- Digits in various programming languages +syntax:set("keyword", {134, 76, 232}) -- Keywords in various programming languages +syntax:set("attribute", {40, 198, 232}) -- Attributes in various programming languages +syntax:set("character", {40, 198, 232}) -- Characters in various programming languages +syntax:set("type", {47, 141, 252}) -- Types in various programming languages +syntax:set("function", {47, 141, 252}) -- Function names in various programming languages +syntax:set("header", {40, 198, 232}) -- Headers in various programming language +syntax:set("macro", {223, 52, 249}) -- Macro names in various programming languages +syntax:set("namespace", {47, 141, 252}) -- Namespaces in various programming languages +syntax:set("struct", {47, 141, 252}) -- The names of structs, classes, enums in various programming languages +syntax:set("operator", {113, 113, 169}) -- Operators in various programming languages e.g. +, -, * etc +syntax:set("boolean", {86, 217, 178}) -- Booleans in various programming langauges e.g. true / false +syntax:set("table", {47, 141, 252}) -- Tables in various programming languages +syntax:set("reference", {134, 76, 232}) -- References in various programming languages +syntax:set("tag", {40, 198, 232}) -- Tags in various markup langauges e.g. HTML

tags +syntax:set("heading", {47, 141, 252}) -- Headings in various markup languages e.g. # in markdown +syntax:set("link", {223, 52, 249}) -- Links in various markup languages e.g. URLs +syntax:set("key", {223, 52, 249}) -- Keys in various markup languages +syntax:set("quote", {113, 113, 169}) -- Quotes in various markup languages e.g. > in markdown +syntax:set("bold", {40, 198, 232}) -- Quotes in various markup languages e.g. * in markdown +syntax:set("italic", {40, 198, 232}) -- Quotes in various markup languages e.g. _ in markdown +syntax:set("block", {40, 198, 232}) -- Quotes in various markup languages e.g. _ in markdown +syntax:set("list", {86, 217, 178}) -- Quotes in various markup languages e.g. _ in markdown + +-- Import plugins (must be at the bottom of this file) +load_plugin("pairs.lua") +load_plugin("autoindent.lua") diff --git a/config/tropical.lua b/config/tropical.lua new file mode 100644 index 00000000..3e4eabae --- /dev/null +++ b/config/tropical.lua @@ -0,0 +1,85 @@ +-- Define user-defined commands +commands = { + ["reload"] = function(arguments) + editor:reload_config() + editor:display_info("Configuration file and plugins reloaded") + end, +} + +-- Pallette -- +black = '#232336' +darkgrey = '#353552' +lightgrey = '#484863' +verylightgrey = '#A1A7C7' +white = '#cdd6f4' +brown = '#dd7878' +red = '#ed8796' +orange = '#f5a97f' +yellow = '#eed49f' +green = '#a6da95' +lightblue = '#7dc4e4' +darkblue = '#8aadf4' +purple = '#c6a0f6' +pink = '#f5bde6' + +-- PRIORITISE - ORANGE, RED, YELLOW, DARKBLUE, BROWN, GREEN, PINK + +-- Configure Colours -- +colors.editor_bg = black +colors.editor_fg = white +colors.line_number_fg = lightgrey +colors.line_number_bg = black + +colors.status_bg = darkgrey +colors.status_fg = orange + +colors.highlight = orange + +colors.tab_inactive_bg = darkgrey +colors.tab_inactive_fg = white +colors.tab_active_bg = lightgrey +colors.tab_active_fg = orange + +colors.info_bg = black +colors.info_fg = lightblue +colors.warning_bg = black +colors.warning_fg = yellow +colors.error_bg = black +colors.error_fg = red + +colors.selection_bg = darkgrey +colors.selection_fg = lightblue + +-- Configure Syntax Highlighting Colours -- +syntax:set("string", lightblue) -- Strings, bright green +syntax:set("comment", verylightgrey) -- Comments, light purple/gray +syntax:set("digit", lightblue) -- Digits, cyan +syntax:set("keyword", orange) -- Keywords, vibrant pink +syntax:set("attribute", darkblue) -- Attributes, cyan +syntax:set("character", orange) -- Characters, cyan +syntax:set("type", pink) -- Types, light purple +syntax:set("function", red) -- Function names, light purple +syntax:set("header", darkblue) -- Headers, cyan +syntax:set("macro", darkblue) -- Macros, red +syntax:set("namespace", pink) -- Namespaces, light purple +syntax:set("struct", yellow) -- Structs, classes, and enums, light purple +syntax:set("operator", darkblue) -- Operators, light purple/gray +syntax:set("boolean", pink) -- Booleans, bright green +syntax:set("table", yellow) -- Tables, light purple +syntax:set("reference", yellow) -- References, vibrant pink +syntax:set("tag", orange) -- Tags (e.g. HTML tags), cyan +syntax:set("heading", red) -- Headings, light purple +syntax:set("link", darkblue) -- Links, vibrant pink +syntax:set("key", yellow) -- Keys, vibrant pink +syntax:set("quote", verylightgrey) -- Quotes, light purple/gray +syntax:set("bold", red) -- Bold text, cyan +syntax:set("italic", orange) -- Italic text, cyan +syntax:set("block", red) -- Code blocks, cyan +syntax:set("image", red) -- Images in markup languages, cyan +syntax:set("list", red) -- Lists, bright green +syntax:set("insertion", green) -- Insertions (e.g. diff highlight), bright green +syntax:set("deletion", red) -- Deletions (e.g. diff highlight), red + +-- Import plugins (must be at the bottom of this file) +load_plugin("pairs.lua") +load_plugin("autoindent.lua") From 3460f3ecfcb9561a59f55ca46298de7aad08b539 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:41:49 +0100 Subject: [PATCH 02/27] version bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 03ac7a40..b118568f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ exclude = ["cactus"] [package] name = "ox" -version = "0.6.7" +version = "0.6.8" edition = "2021" authors = ["Curlpipe <11898833+curlpipe@users.noreply.github.com>"] description = "A Rust powered text editor." From 556064850cb9f1d5d6c3197e3d5d831fc4676c62 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Fri, 18 Oct 2024 23:10:28 +0100 Subject: [PATCH 03/27] Added configuration assistant --- Cargo.lock | 2 +- config/.oxrc | 11 +- config/ox-transparent.lua | 268 ------- src/cli.rs | 6 +- src/config/assistant.rs | 689 ++++++++++++++++++ src/config/colors.rs | 38 +- src/config/highlighting.rs | 40 +- src/config/interface.rs | 23 +- src/config/mod.rs | 33 +- src/editor/mod.rs | 2 +- src/main.rs | 13 +- .../themes/galaxy.lua | 33 +- src/themes/transparent.lua | 8 + {config => src/themes}/tropical.lua | 35 +- 14 files changed, 833 insertions(+), 368 deletions(-) delete mode 100644 config/ox-transparent.lua create mode 100644 src/config/assistant.rs rename config/catppuccin-mocha.lua => src/themes/galaxy.lua (73%) create mode 100644 src/themes/transparent.lua rename {config => src/themes}/tropical.lua (73%) diff --git a/Cargo.lock b/Cargo.lock index 0232a3b2..c4e11b78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,7 +306,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ox" -version = "0.6.7" +version = "0.6.8" dependencies = [ "alinio", "base64", diff --git a/config/.oxrc b/config/.oxrc index 1d25c0b8..015d7e11 100644 --- a/config/.oxrc +++ b/config/.oxrc @@ -232,17 +232,18 @@ line_numbers.padding_right = 1 -- Configure Mouse Behaviour -- terminal.mouse_enabled = true -terminal.scroll_amount = 1 +terminal.scroll_amount = 2 -- Configure Tab Line -- tab_line.enabled = true tab_line.format = " {file_name}{modified} " -- Configure Status Line -- -status_line:add_part(" {file_name}{modified} │ {file_type} │") -- The left side of the status line -status_line:add_part("│ {cursor_y} / {line_count} {cursor_x} ") -- The right side of the status line - -status_line.alignment = "between" -- This will put a space between the left and right sides +status_line.parts = { + " {file_name}{modified} │ {file_type} │", -- The left side of the status line + "│ {cursor_y} / {line_count} {cursor_x} ", -- The right side of the status line +} +status_line.alignment = "between" -- This will put a space between the parts (left and right sides) -- Configure Greeting Message -- greeting_message.enabled = true diff --git a/config/ox-transparent.lua b/config/ox-transparent.lua deleted file mode 100644 index 21991a50..00000000 --- a/config/ox-transparent.lua +++ /dev/null @@ -1,268 +0,0 @@ --- Configure Events -- -event_mapping = { - -- Cursor movement - ["up"] = function() - editor:move_up() - end, - ["down"] = function() - editor:move_down() - end, - ["left"] = function() - editor:move_left() - end, - ["right"] = function() - editor:move_right() - end, - ["shift_up"] = function() - editor:select_up() - end, - ["shift_down"] = function() - editor:select_down() - end, - ["shift_left"] = function() - editor:select_left() - end, - ["shift_right"] = function() - editor:select_right() - end, - ["ctrl_up"] = function() - editor:move_top() - end, - ["ctrl_down"] = function() - editor:move_bottom() - end, - ["ctrl_left"] = function() - editor:move_previous_word() - end, - ["ctrl_right"] = function() - editor:move_next_word() - end, - ["home"] = function() - editor:move_home() - end, - ["end"] = function() - editor:move_end() - end, - ["pageup"] = function() - editor:move_page_up() - end, - ["pagedown"] = function() - editor:move_page_down() - end, - ["ctrl_g"] = function() - local line = editor:prompt("Go to line") - editor:move_to(0, tonumber(line)) - end, - -- Searching & Replacing - ["ctrl_f"] = function() - editor:search() - end, - ["ctrl_r"] = function() - editor:replace() - end, - -- Document Management - ["ctrl_n"] = function() - editor:new() - end, - ["ctrl_o"] = function() - editor:open() - end, - ["ctrl_s"] = function() - editor:save() - end, - ["alt_s"] = function() - editor:save_as() - end, - ["alt_a"] = function() - editor:save_all() - end, - ["ctrl_q"] = function() - editor:quit() - end, - ["alt_left"] = function() - editor:previous_tab() - end, - ["alt_right"] = function() - editor:next_tab() - end, - -- Clipboard Interaction - ["ctrl_a"] = function() - editor:select_all() - end, - ["ctrl_x"] = function() - editor:cut() - end, - ["ctrl_c"] = function() - editor:copy() - end, - ["ctrl_v"] = function() - editor:display_info("Use ctrl+shift+v for paste or set your terminal emulator to do paste on ctrl+v") - end, - -- Undo & Redo - ["ctrl_z"] = function() - editor:undo() - end, - ["ctrl_y"] = function() - editor:redo() - end, - -- Miscellaneous - ["ctrl_h"] = function() - help_message.enabled = not help_message.enabled - end, - ["ctrl_d"] = function() - editor:remove_line() - end, - ["ctrl_k"] = function() - editor:open_command_line() - end, - ["alt_up"] = function() - -- current line information - line = editor:get_line() - y = editor.cursor.y - -- insert a new line - editor:insert_line_at(line, y - 1) - -- delete old copy and reposition cursor - editor:remove_line_at(y + 1) - editor:move_up() - -- correct indentation level - autoindent:fix_indent() - end, - ["alt_down"] = function() - -- current line information - line = editor:get_line() - y = editor.cursor.y - -- insert a new line - editor:insert_line_at(line, y + 2) - -- delete old copy and reposition cursor - editor:remove_line_at(y) - editor:move_down() - -- correct indentation level - autoindent:fix_indent() - end, - ["ctrl_w"] = function() - y = editor.cursor.y - x = editor.cursor.x - if editor:get_character() == " " then - start = 0 - else - start = 1 - end - editor:move_previous_word() - new_x = editor.cursor.x - diff = x - new_x - if editor.cursor.y == y then - -- Cursor on the same line - for i = start, diff do - editor:remove_at(new_x, y) - end - else - -- Cursor has passed up onto the previous line - end - end, -} - --- Define user-defined commands -commands = { - ["readonly"] = function(arguments) - arg = arguments[1] - if arg == "true" then - editor:set_read_only(true) - elseif arg == "false" then - editor:set_read_only(false) - end - end, - ["filetype"] = function(arguments) - local file_type_name = table.concat(arguments, " ") - editor:set_file_type(file_type_name) - end, - ["reload"] = function(arguments) - editor:reload_config() - editor:reload_plugins() - editor:display_info("Configuration file and plugins reloaded") - end, -} - --- Configure Documents -- -document.tab_width = 4 -document.indentation = "spaces" -document.undo_period = 10 -document.wrap_cursor = true - --- Configure Colours -- -colors.editor_bg = 'transparent' -colors.editor_fg = {255, 255, 255} -colors.line_number_fg = {65, 65, 98} -colors.line_number_bg = 'transparent' - -colors.status_bg = {59, 59, 84} -colors.status_fg = {35, 240, 144} - -colors.highlight = {35, 240, 144} - -colors.tab_inactive_fg = {255, 255, 255} -colors.tab_inactive_bg = {41, 41, 61} -colors.tab_active_fg = {35, 240, 144} -colors.tab_active_bg = {59, 59, 84} - -colors.info_fg = {99, 162, 255} -colors.info_bg = 'transparent' -colors.warning_fg = {255, 182, 99} -colors.warning_bg = 'transparent' -colors.error_fg = {255, 100, 100} -colors.error_bg = 'transparent' - -colors.selection_fg = {255, 255, 255} -colors.selection_bg = {59, 59, 130} - --- Configure Line Numbers -- -line_numbers.enabled = true -line_numbers.padding_left = 1 -line_numbers.padding_right = 1 - --- Configure Mouse Behaviour -- -terminal.mouse_enabled = true - --- Configure Tab Line -- -tab_line.enabled = true -tab_line.format = " {file_name}{modified} " - --- Configure Status Line -- -status_line:add_part(" {file_name}{modified} │ {file_type} │") -- The left side of the status line -status_line:add_part("│ {cursor_y} / {line_count} {cursor_x} ") -- The right side of the status line - -status_line.alignment = "between" -- This will put a space between the left and right sides - --- Configure Greeting and Help Messages -- -greeting_message.enabled = true -help_message.enabled = false - --- Configure Syntax Highlighting Colours -- -syntax:set("string", {39, 222, 145}) -- Strings in various programming languages -syntax:set("comment", {113, 113, 169}) -- Comments in various programming languages -syntax:set("digit", {40, 198, 232}) -- Digits in various programming languages -syntax:set("keyword", {134, 76, 232}) -- Keywords in various programming languages -syntax:set("attribute", {40, 198, 232}) -- Attributes in various programming languages -syntax:set("character", {40, 198, 232}) -- Characters in various programming languages -syntax:set("type", {47, 141, 252}) -- Types in various programming languages -syntax:set("function", {47, 141, 252}) -- Function names in various programming languages -syntax:set("header", {40, 198, 232}) -- Headers in various programming language -syntax:set("macro", {223, 52, 249}) -- Macro names in various programming languages -syntax:set("namespace", {47, 141, 252}) -- Namespaces in various programming languages -syntax:set("struct", {47, 141, 252}) -- The names of structs, classes, enums in various programming languages -syntax:set("operator", {113, 113, 169}) -- Operators in various programming languages e.g. +, -, * etc -syntax:set("boolean", {86, 217, 178}) -- Booleans in various programming langauges e.g. true / false -syntax:set("table", {47, 141, 252}) -- Tables in various programming languages -syntax:set("reference", {134, 76, 232}) -- References in various programming languages -syntax:set("tag", {40, 198, 232}) -- Tags in various markup langauges e.g. HTML

tags -syntax:set("heading", {47, 141, 252}) -- Headings in various markup languages e.g. # in markdown -syntax:set("link", {223, 52, 249}) -- Links in various markup languages e.g. URLs -syntax:set("key", {223, 52, 249}) -- Keys in various markup languages -syntax:set("quote", {113, 113, 169}) -- Quotes in various markup languages e.g. > in markdown -syntax:set("bold", {40, 198, 232}) -- Quotes in various markup languages e.g. * in markdown -syntax:set("italic", {40, 198, 232}) -- Quotes in various markup languages e.g. _ in markdown -syntax:set("block", {40, 198, 232}) -- Quotes in various markup languages e.g. _ in markdown -syntax:set("list", {86, 217, 178}) -- Quotes in various markup languages e.g. _ in markdown - --- Import plugins (must be at the bottom of this file) -load_plugin("pairs.lua") -load_plugin("autoindent.lua") diff --git a/src/cli.rs b/src/cli.rs index a7403f39..2d589d5d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -19,6 +19,7 @@ OPTIONS: --readonly, -r : Prevent opened files from writing --filetype [name], -f [name] : Set the file type of files opened --stdin : Reads file from the stdin + --config-assist : Activate the configuration assistant EXAMPLES: ox @@ -27,7 +28,8 @@ EXAMPLES: ox /home/user/docs/test.txt ox -c config.lua test.txt ox -r -c ~/.config/.oxrc -f Lua my_file.lua - tree | ox -r --stdin\ + tree | ox -r --stdin + ox --config-assist\ "; /// Read from the standard input @@ -44,6 +46,7 @@ pub struct CommandLineInterfaceFlags { pub version: bool, pub read_only: bool, pub stdin: bool, + pub config_assist: bool, } /// Struct to help with starting ox @@ -70,6 +73,7 @@ impl CommandLineInterface { version: j.contains(["-v", "--version"]), read_only: j.contains(["-r", "--readonly"]), stdin: j.contains("--stdin"), + config_assist: j.contains("--config-assist"), }, file_type: j.option_arg::(filetype.clone()), config_path: j diff --git a/src/config/assistant.rs b/src/config/assistant.rs new file mode 100644 index 00000000..173bb50e --- /dev/null +++ b/src/config/assistant.rs @@ -0,0 +1,689 @@ +use crate::cli::VERSION; +/// Code for the configuration set-up assistant +use crate::config::{Colors, Indentation, SyntaxHighlighting}; +use crate::error::Result; +use crossterm::style::{SetBackgroundColor as Bg, SetForegroundColor as Fg}; +use mlua::prelude::*; +use std::cell::RefCell; +use std::io::Write; +use std::rc::Rc; +//use crate::config::Color; +//use std::collections::HashMap; + +pub const TROPICAL: &str = include_str!("../themes/tropical.lua"); +pub const GALAXY: &str = include_str!("../themes/galaxy.lua"); +pub const TRANSPARENT: &str = include_str!("../themes/transparent.lua"); + +#[macro_export] +macro_rules! gets { + () => {{ + let mut s = std::string::String::new(); + std::io::stdin().read_line(&mut s).unwrap(); + s.trim_end_matches(&['\n', '\r'][..]).to_owned() + }}; + ( $($args:tt)* ) => {{ + use std::io::Write; + print!("{}", format_args!($($args)*)); + std::io::stdout().flush().unwrap(); + $crate::gets!() + }}; +} + +const TITLE: &str = r" + ___ ____ __ _ _ _ _ _ + / _ \__ __ / ___|___ _ __ / _(_) __ _ / \ ___ ___(_)___| |_ __ _ _ __ | |_ +| | | \ \/ / | | / _ \| '_ \| |_| |/ _` | / _ \ / __/ __| / __| __/ _` | '_ \| __| +| |_| |> < | |__| (_) | | | | _| | (_| | / ___ \\__ \__ \ \__ \ || (_| | | | | |_ + \___//_/\_\ \____\___/|_| |_|_| |_|\__, | /_/ \_\___/___/_|___/\__\__,_|_| |_|\__| + |___/ +"; + +const NO_CONFIG_MESSAGE: &str = r" +Thank you for installing Ox +We noticed you don't have a configuration file. +This set-up process will help you customise and configure Ox. +This way, you'll have a better user experience out of the box. +"; + +const INTRODUCTION: &str = r" +Welcome to the configuration assistant for the Ox Editor. +This is a tool that will help get Ox set up for you. +It will take no more than 5 minutes and the config assistant will not show again after set-up. +You can always re-access this tool using `ox --config-assist` + +"; + +const PLUGIN_LIST: &str = r" +Ox has an ecosystem of plug-ins that you can make use of, they are as follows: + +──────────────────── Code Helpers ──────────────────── +AutoIndent - A plug-in that will insert and remove code indentation automatically +Pairs - A plug-in that will insert end brackets and end quotes automatically + +─────────────────── Web Development ────────────────── +Emmet - A neat language to help you write HTML quickly - requires python and the py-emmet module +Live HTML - Start a web server that previews your HTML site as you write the code - requires python and the Flask module + +────────── Integration with Existing Tools ─────────── +DiscordRPC - Have Ox interact with Discord and show your programming activity - requires python and the discord-rpc module +Git - View and manage your git repository - requires git to be installed + +─────────────────── Miscellaneous ──────────────────── +Pomodoro - A timer that helps you track your periods of work and breaks +Todo - Makes .todo files interactive todo lists +Typing Speed - Shows the rough speed that you're typing in the status line +Update Notification - Warns you if there is a new version of Ox - requires curl to be installed on unix based systems\n +"; + +#[derive(PartialEq)] +pub enum Theme { + Tropical, + Galaxy, + Transparent, + Default, + //Custom(HashMap), +} + +impl Theme { + pub fn to_config(&self) -> String { + match self { + Self::Tropical => TROPICAL, + Self::Galaxy => GALAXY, + Self::Transparent => TRANSPARENT, + Self::Default => "", + } + .to_string() + } +} + +#[derive(PartialEq, Debug)] +pub enum Plugin { + AutoIndent, + Pairs, + DiscordRPC, + Emmet, + Git, + LiveHTML, + Pomodoro, + Todo, + TypingSpeed, + UpdateNotification, +} + +impl Plugin { + pub fn to_config(&self) -> String { + format!( + "load_plugin(\"{}\")\n", + match self { + Self::AutoIndent => "autoindent.lua", + Self::Pairs => "pairs.lua", + Self::DiscordRPC => "discord_rpc.lua", + Self::Emmet => "emmet.lua", + Self::Git => "git.lua", + Self::LiveHTML => "live_html.lua", + Self::Pomodoro => "pomodoro.lua", + Self::Todo => "todo.lua", + Self::TypingSpeed => "typing_speed.lua", + Self::UpdateNotification => "update_notification.lua", + } + ) + } +} + +#[allow(clippy::struct_excessive_bools)] +pub struct Assistant { + pub theme: Theme, + pub indentation: Indentation, + pub tab_width: usize, + pub mouse: bool, + pub scroll_sensitivity: usize, + pub cursor_wrap: bool, + pub line_numbers: bool, + pub line_number_padding: (usize, usize), + pub icons: bool, + pub tab_line: bool, + pub greeting_message: bool, + pub plugins: Vec, +} + +impl Default for Assistant { + fn default() -> Self { + Self { + // Colours and theme + theme: Theme::Default, + // Document + indentation: Indentation::Tabs, + tab_width: 4, + // Line Numbers + line_numbers: true, + line_number_padding: (1, 1), + // Tab Line + tab_line: true, + // Greeting Message + greeting_message: true, + // Mouse and Cursor Behaviour + mouse: true, + scroll_sensitivity: 2, + cursor_wrap: true, + // Plug-ins + plugins: vec![Plugin::AutoIndent, Plugin::Pairs], + // Misc + icons: false, + } + } +} + +impl Assistant { + /// Run the configuration assistant + pub fn run(because_no_config: bool) -> Result<()> { + println!("{TITLE}"); + if because_no_config { + println!("{NO_CONFIG_MESSAGE}"); + } + println!("{INTRODUCTION}"); + if Self::confirmation("Do you wish to set-up the editor?", true) { + let mut result = Self::default(); + // Theme + println!("Let's begin with what theme you'd like to use\n\n"); + // Prepare demonstration + Self::demonstrate_themes()?; + let choice = Self::options( + "Please choose which theme you'd like", + &["default", "tropical", "galaxy", "transparent"], + "default", + ); + result.theme = match choice.as_str() { + "default" => Theme::Default, + "tropical" => Theme::Tropical, + "galaxy" => Theme::Galaxy, + "transparent" => Theme::Transparent, + _ => unreachable!(), + }; + // Document + println!("Great choice, now let's move onto indentation\n"); + result.indentation = Self::options( + "Please choose how you'd like to represent indentation", + &["spaces", "tabs"], + "tabs", + ) + .into(); + result.tab_width = if result.indentation == Indentation::Tabs { + Self::integer("How wide should tabs be rendered as", 4) + } else { + Self::integer("How many spaces should make up 1 indent", 4) + }; + // Line Numbers + println!("Great, now for deciding which parts of the editor should be visible\n"); + result.line_numbers = + Self::confirmation("Would you like line numbers to be visible", true); + if result.line_numbers { + result.line_number_padding = ( + Self::integer( + "How much space should there be on the left hand side of the line numbers", + 1, + ), + Self::integer( + "How much space should there be on the right hand side of the line numbers", + 1, + ), + ); + } + // Tab line + result.tab_line = Self::confirmation("Would you like the tab line to be visible", true); + // Greeting message + result.greeting_message = Self::confirmation( + "Would you like the greeting message to be visible on start-up", + true, + ); + // Mouse and Cursor + println!("Now for the mouse and cursor behaviour\n"); + result.mouse = Self::confirmation( + "Would you like to use your mouse cursor in the editor", + true, + ); + result.scroll_sensitivity = Self::integer( + "How sensitive should scrolling be, 1 = least sensitive, 5 = very sensitive", + 2, + ); + result.cursor_wrap = Self::confirmation( + "Would you like the cursor to wrap around when at the edge of a line", + true, + ); + // Icons + println!("Ox has support for icons, which can enhance the UI, if you choose to enable them, ensure you install nerd fonts\n"); + result.icons = + Self::confirmation("Would you like to enable icons, yes is recommended", false); + // Plug-Ins + Self::ask_plugins(&mut result); + // Create the configuration file (and print it) + println!("\nSet-up is complete!"); + if !because_no_config { + println!("WARNING: config file already exists, it will be backed-up to ~/.oxrc-backup if you write"); + } + let result = result.to_config(); + if Self::confirmation( + "Would you like to write the configuration file?", + because_no_config, + ) { + let config_path = format!("{}/.oxrc", shellexpand::tilde("~")); + let backup_path = format!("{}/.oxrc-backup", shellexpand::tilde("~")); + if !because_no_config { + let _ = std::fs::rename(config_path.clone(), backup_path); + } + let mut file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(config_path)?; + file.write_all(result.as_bytes())?; + } else { + println!("Below is your newly generated configuration file:\n\n"); + println!("{result}"); + } + } + Ok(()) + } + + pub fn confirmation(msg: &str, default: bool) -> bool { + let mut response = "#####################".to_string(); + while response != "y" && response != "n" && !response.is_empty() { + response = gets!( + "{msg}, default is {} (y/n)\n> ", + if default { "yes" } else { "no" } + ) + .to_lowercase(); + } + println!(); + if response.is_empty() { + default + } else { + response == "y" + } + } + + pub fn options(msg: &str, options: &[&str], default: &str) -> String { + let options: Vec = options + .iter() + .map(std::string::ToString::to_string) + .collect(); + let mut response = "#####################".to_string(); + while !options.contains(&response) && !response.is_empty() { + response = + gets!("{msg}, default is {default} ({})\n> ", options.join("/")).to_lowercase(); + } + println!(); + if response.is_empty() { + default.to_string() + } else { + response + } + } + + pub fn integer(msg: &str, default: usize) -> usize { + let mut response = "#####################".to_string(); + while response.parse::().is_err() && !response.is_empty() { + response = gets!("{msg} (enter a number, default: {default})\n> ").to_lowercase(); + } + println!(); + if response.is_empty() { + default + } else { + response.parse::().unwrap() + } + } + + pub fn ask_plugins(result: &mut Self) { + println!("{PLUGIN_LIST}"); + let mut adding = String::new(); + while adding != "exit" { + println!("Enabled plug-ins: {:?}\n", result.plugins); + adding = Self::options( + "Enter the name of a plug-in you'd like to enable / disable", + &[ + "autoindent", + "pairs", + "emmet", + "live_html", + "discord_rpc", + "git", + "pomodoro", + "todo", + "typing_speed", + "update_notification", + "exit", + ], + "exit", + ); + let plugin = match adding.as_str() { + "autoindent" => Plugin::AutoIndent, + "pairs" => Plugin::Pairs, + "emmet" => Plugin::Emmet, + "live_html" => Plugin::LiveHTML, + "discord_rpc" => Plugin::DiscordRPC, + "git" => Plugin::Git, + "pomodoro" => Plugin::Pomodoro, + "todo" => Plugin::Todo, + "typing_speed" => Plugin::TypingSpeed, + "update_notification" => Plugin::UpdateNotification, + _ => continue, + }; + if result.plugins.contains(&plugin) { + result.plugins.retain(|p| *p != plugin); + } else { + result.plugins.push(plugin); + } + } + } + + pub fn demonstrate_themes() -> Result<()> { + println!( + "{}", + Self::demonstrate_theme_row(&["default", "transparent"])? + ); + println!("{}", Self::demonstrate_theme_row(&["tropical", "galaxy"])?); + Ok(()) + } + + pub fn demonstrate_theme_row(include: &[&str]) -> Result { + // Gather the list of theme previews + let mut themes: Vec> = vec![]; + for name in include { + let code = match *name { + "default" => "", + "tropical" => TROPICAL, + "galaxy" => GALAXY, + "transparent" => TRANSPARENT, + _ => unreachable!(), + }; + let theme = Self::demonstrate_theme(name, code)? + .split('\n') + .map(std::string::ToString::to_string) + .collect(); + themes.push(theme); + } + // Put into row format + let mut result = String::new(); + let mut at = 0; + while at < 13 { + for theme in &themes { + result += &format!("{} ", theme[at]); + } + result += "\n"; + at += 1; + } + // Return the result + Ok(result) + } + + pub fn demonstrate_theme(name: &str, code: &str) -> Result { + // Create an environment to capture all the values + let lua = Lua::new(); + let colors = Rc::new(RefCell::new(Colors::default())); + let syntax_highlighting = Rc::new(RefCell::new(SyntaxHighlighting::default())); + lua.globals().set("syntax", syntax_highlighting.clone())?; + lua.globals().set("colors", colors.clone())?; + // Access all the values + lua.load(code).exec()?; + // Gather the editor colours + let col = colors.borrow(); + let editor = format!( + "{}{}", + Fg(col.editor_fg.to_color()?), + Bg(col.editor_bg.to_color()?) + ); + let reset = format!( + "{}{}", + Fg(crossterm::style::Color::Reset), + Bg(crossterm::style::Color::Reset) + ); + let active_tab = format!( + "{}{}", + Fg(col.tab_active_fg.to_color()?), + Bg(col.tab_active_bg.to_color()?) + ); + let inactive_tab = format!( + "{}{}", + Fg(col.tab_inactive_fg.to_color()?), + Bg(col.tab_inactive_bg.to_color()?) + ); + let line_number = format!( + "{}{}", + Fg(col.line_number_fg.to_color()?), + Bg(col.line_number_bg.to_color()?) + ); + let status_line = format!( + "{}{}", + Fg(col.status_fg.to_color()?), + Bg(col.status_bg.to_color()?) + ); + let error = format!( + "{}{}", + Fg(col.error_fg.to_color()?), + Bg(col.error_bg.to_color()?) + ); + let warning = format!( + "{}{}", + Fg(col.warning_fg.to_color()?), + Bg(col.warning_bg.to_color()?) + ); + let info = format!( + "{}{}", + Fg(col.info_fg.to_color()?), + Bg(col.info_bg.to_color()?) + ); + // Gather syntax highlighting colours + let string = Fg(syntax_highlighting.borrow().get_theme("string")?); + let comment = Fg(syntax_highlighting.borrow().get_theme("comment")?); + let digit = Fg(syntax_highlighting.borrow().get_theme("digit")?); + let keyword = Fg(syntax_highlighting.borrow().get_theme("keyword")?); + let character = Fg(syntax_highlighting.borrow().get_theme("character")?); + let type_syn = Fg(syntax_highlighting.borrow().get_theme("type")?); + let function = Fg(syntax_highlighting.borrow().get_theme("function")?); + let macro_syn = Fg(syntax_highlighting.borrow().get_theme("macro")?); + let block = Fg(syntax_highlighting.borrow().get_theme("block")?); + let namespace = Fg(syntax_highlighting.borrow().get_theme("namespace")?); + let header = Fg(syntax_highlighting.borrow().get_theme("header")?); + let struct_syn = Fg(syntax_highlighting.borrow().get_theme("struct")?); + let operator = Fg(syntax_highlighting.borrow().get_theme("operator")?); + let boolean = Fg(syntax_highlighting.borrow().get_theme("boolean")?); + let reference = Fg(syntax_highlighting.borrow().get_theme("reference")?); + let tag = Fg(syntax_highlighting.borrow().get_theme("tag")?); + let heading = Fg(syntax_highlighting.borrow().get_theme("heading")?); + let link = Fg(syntax_highlighting.borrow().get_theme("link")?); + let bold = Fg(syntax_highlighting.borrow().get_theme("bold")?); + let italic = Fg(syntax_highlighting.borrow().get_theme("italic")?); + let insertion = Fg(syntax_highlighting.borrow().get_theme("insertion")?); + let deletion = Fg(syntax_highlighting.borrow().get_theme("deletion")?); + // Render the preview + let name = format!(" {name} "); + Ok(format!("{name:─^47} +{editor}┌─────────────────────────────────────────────┐{reset} +{editor}│{inactive_tab} Inactive Tab |{active_tab} Active Tab {inactive_tab}| {editor}│{reset} +{editor}│{line_number} 1 │{editor}{function}print{editor}({string}\"hello\" {operator}+ {digit}3 {operator}+ {boolean}true {operator}+ {character}'c'{editor}); │{reset} +{editor}│{line_number} 2 │{editor}{keyword}let {editor}var{operator}: {type_syn}Type {operator}= {reference}&{struct_syn}Object{editor}({namespace}name::space{editor}); │{reset} +{editor}│{line_number} 3 │{editor}{tag} {comment}// Comment{editor} │{reset} +{editor}│{line_number} 4 │{editor}{header}import {editor}random; │{reset} +{editor}│{line_number} 5 │{editor}{macro_syn}macro!{editor}(); {insertion}+ insertion {deletion}- deletion {editor}│{reset} +{editor}│{line_number} 6 │{editor}{heading}# Title {italic}*italic* {bold}**bold** {block}`code`{editor} │{reset} +{editor}│{line_number} 7 │{editor}{link}[link](example.com){editor} │{reset} +{editor}│{status_line} Status Line {editor}│{reset} +{editor}│{error} Error {warning} Warning {info} Information {editor}│{reset} +{editor}└─────────────────────────────────────────────┘{reset}")) + } + + /// Turn the configuration assistant details into a lua file + pub fn to_config(&self) -> String { + let mut result = String::new(); + let (sections, fields) = self.diff(); + // Comment at the top + result += &format!( + "-- Configuration generated for Ox {VERSION} by the configuration assistant --\n" + ); + // Configuration of colours and theme + if sections.contains(&"theme") { + result += &self.theme.to_config(); + } + // Configuration of document + if sections.contains(&"document") { + result += "\n-- Document Configuration --\n"; + if fields.contains(&"indentation") { + result += &format!("document.indentation = '{}'\n", self.indentation); + } + if fields.contains(&"tab_width") { + result += &format!("document.tab_width = {}\n", self.tab_width); + } + } + // Configuration of line numbers + if sections.contains(&"line_number") { + result += "\n-- Line Number Configuration --\n"; + if fields.contains(&"line_numbers") { + result += &format!("line_numbers.enabled = {}\n", self.line_numbers); + } + if fields.contains(&"line_number_padding") { + result += &format!( + "line_numbers.padding_left = {}\n", + self.line_number_padding.0 + ); + result += &format!( + "line_numbers.padding_right = {}\n", + self.line_number_padding.1 + ); + } + } + // Configuration of tab line + if sections.contains(&"tab_line") { + result += "\n-- Tab Line Configuration --\n"; + result += &format!("tab_line.enabled = {}\n", self.tab_line); + let mut format = " {file_name}{modified} ".to_string(); + let mut format_changed = false; + if self.icons { + format = format.replace("{file_name}", "{icon} {file_name}"); + format_changed = true; + } + if self.plugins.contains(&Plugin::Git) { + format = format.replace("{modified}", "{modified} {git_status}"); + format_changed = true; + } + if format_changed { + result += &format!("tab_line.format = '{format}'\n"); + } + } + // Configuration of status line + if sections.contains(&"status_line") { + result += "\n-- Status Line Configuration --\n"; + let mut left = " {file_name}{modified} │ {file_type} │".to_string(); + let mut right = "│ {cursor_y} / {line_count} {cursor_x} ".to_string(); + // Handle file type icons + if self.icons { + left = left.replace("{file_type}", "{icon} {file_type}"); + } + // Handle git plug-in + if self.plugins.contains(&Plugin::Git) && self.icons { + right = format!("│  {{git_branch}} {right}"); + } else if self.plugins.contains(&Plugin::Git) && !self.icons { + right = format!("│ {{git_branch}} {right}"); + } + // Handle typing speed plug-in + if self.plugins.contains(&Plugin::TypingSpeed) { + right = format!("│ {{typing_speed_show}} {right}"); + } + // Handle pomodoro plug-in + if self.plugins.contains(&Plugin::Pomodoro) { + left = format!("{left} {{pomodoro_show}} │"); + } + result += &format!("status_line.parts = {{\n\t'{left}',\n\t'{right}',\n}}\n"); + } + // Configuration of greeting message + if sections.contains(&"greeting_message") { + result += "\n-- Greeting Message Configuration --\n"; + result += &format!("greeting_message.enabled = {}\n", self.greeting_message); + } + // Configuration of mouse and cursor behaviour + if sections.contains(&"cursors") { + result += "\n-- Cursor Configuration --\n"; + if fields.contains(&"mouse") { + result += &format!("terminal.mouse_enabled = {}\n", self.mouse); + } + if fields.contains(&"scroll_sensitivity") { + result += &format!("terminal.scroll_amount = {}\n", self.scroll_sensitivity); + } + if fields.contains(&"cursor_wrap") { + result += &format!("document.wrap_cursor = {}\n", self.cursor_wrap); + } + } + // Configuration of plug-ins + result += "\n-- Load Plug-Ins --\n"; + for plugin in &self.plugins { + result += &plugin.to_config(); + } + // Ready to go + result + } + + /// Find the difference between the default configuration and this one + pub fn diff(&self) -> (Vec<&str>, Vec<&str>) { + let def = Self::default(); + let fields = vec![ + ("theme", self.theme != def.theme), + ("indentation", self.indentation != def.indentation), + ("line_numbers", self.line_numbers != def.line_numbers), + ("tab_line", self.tab_line != def.tab_line), + ( + "greeting_message", + self.greeting_message != def.greeting_message, + ), + ("mouse", self.mouse != def.mouse), + ("cursor_wrap", self.cursor_wrap != def.cursor_wrap), + ("icons", self.icons != def.icons), + ( + "line_number_padding", + self.line_number_padding != def.line_number_padding, + ), + ( + "scroll_sensitivity", + self.scroll_sensitivity != def.scroll_sensitivity, + ), + ("tab_width", self.tab_width != def.tab_width), + ]; + let fields = fields + .iter() + .filter_map(|(name, differs)| if *differs { Some(*name) } else { None }) + .collect::>(); + let sections = [ + ("theme", fields.contains(&"theme")), + ( + "document", + fields.contains(&"indentation") || fields.contains(&"tab_width"), + ), + ( + "line_numbers", + fields.contains(&"line_numbers") || fields.contains(&"line_number_padding"), + ), + ( + "tab_line", + fields.contains(&"tab_line") + || fields.contains(&"icons") + || self.plugins.contains(&Plugin::Git), + ), + ( + "status_line", + fields.contains(&"icons") + || self.plugins.contains(&Plugin::Git) + || self.plugins.contains(&Plugin::Pomodoro) + || self.plugins.contains(&Plugin::TypingSpeed), + ), + ("greeting_message", fields.contains(&"greeting_message")), + ( + "cursors", + fields.contains(&"mouse") + || fields.contains(&"scroll_sensitivity") + || fields.contains(&"cursor_wrap"), + ), + ]; + let sections = sections + .iter() + .filter_map(|(name, differs)| if *differs { Some(*name) } else { None }) + .collect::>(); + (sections, fields) + } +} diff --git a/src/config/colors.rs b/src/config/colors.rs index 053b6dc3..333883f1 100644 --- a/src/config/colors.rs +++ b/src/config/colors.rs @@ -37,31 +37,31 @@ pub struct Colors { impl Default for Colors { fn default() -> Self { Self { - editor_bg: Color::Black, - editor_fg: Color::Black, + editor_bg: Color::Rgb(41, 41, 61), + editor_fg: Color::Rgb(255, 255, 255), - status_bg: Color::Black, - status_fg: Color::Black, + status_bg: Color::Rgb(59, 59, 84), + status_fg: Color::Rgb(35, 240, 144), - highlight: Color::Black, + highlight: Color::Rgb(35, 240, 144), - line_number_fg: Color::Black, - line_number_bg: Color::Black, + line_number_fg: Color::Rgb(65, 65, 98), + line_number_bg: Color::Rgb(41, 41, 61), - tab_active_fg: Color::Black, - tab_active_bg: Color::Black, - tab_inactive_fg: Color::Black, - tab_inactive_bg: Color::Black, + tab_active_fg: Color::Rgb(255, 255, 255), + tab_active_bg: Color::Rgb(41, 41, 61), + tab_inactive_fg: Color::Rgb(255, 255, 255), + tab_inactive_bg: Color::Rgb(59, 59, 84), - info_bg: Color::Black, - info_fg: Color::Black, - warning_bg: Color::Black, - warning_fg: Color::Black, - error_bg: Color::Black, - error_fg: Color::Black, + info_bg: Color::Rgb(41, 41, 61), + info_fg: Color::Rgb(99, 162, 255), + warning_bg: Color::Rgb(41, 41, 61), + warning_fg: Color::Rgb(255, 182, 99), + error_bg: Color::Rgb(41, 41, 61), + error_fg: Color::Rgb(255, 100, 100), - selection_fg: Color::White, - selection_bg: Color::Blue, + selection_fg: Color::Rgb(41, 41, 61), + selection_bg: Color::Rgb(41, 41, 61), } } } diff --git a/src/config/highlighting.rs b/src/config/highlighting.rs index d462b958..e4c7e426 100644 --- a/src/config/highlighting.rs +++ b/src/config/highlighting.rs @@ -10,13 +10,51 @@ use super::Color; type BoundedInterpArgs = (String, String, String, String, String, bool); /// For storing configuration information related to syntax highlighting -#[derive(Debug, Default)] +#[derive(Debug)] #[allow(clippy::module_name_repetitions)] pub struct SyntaxHighlighting { pub theme: HashMap, pub user_rules: HashMap, } +impl Default for SyntaxHighlighting { + fn default() -> Self { + let mut theme = HashMap::default(); + theme.insert("string".to_string(), Color::Rgb(39, 222, 145)); + theme.insert("comment".to_string(), Color::Rgb(113, 113, 169)); + theme.insert("digit".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("keyword".to_string(), Color::Rgb(134, 76, 232)); + theme.insert("attribute".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("character".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("type".to_string(), Color::Rgb(47, 141, 252)); + theme.insert("function".to_string(), Color::Rgb(47, 141, 252)); + theme.insert("header".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("macro".to_string(), Color::Rgb(223, 52, 249)); + theme.insert("namespace".to_string(), Color::Rgb(47, 141, 252)); + theme.insert("struct".to_string(), Color::Rgb(47, 141, 252)); + theme.insert("operator".to_string(), Color::Rgb(113, 113, 169)); + theme.insert("boolean".to_string(), Color::Rgb(86, 217, 178)); + theme.insert("table".to_string(), Color::Rgb(47, 141, 252)); + theme.insert("reference".to_string(), Color::Rgb(134, 76, 232)); + theme.insert("tag".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("heading".to_string(), Color::Rgb(47, 141, 252)); + theme.insert("link".to_string(), Color::Rgb(223, 52, 249)); + theme.insert("key".to_string(), Color::Rgb(223, 52, 249)); + theme.insert("quote".to_string(), Color::Rgb(113, 113, 169)); + theme.insert("bold".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("italic".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("block".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("image".to_string(), Color::Rgb(40, 198, 232)); + theme.insert("list".to_string(), Color::Rgb(86, 217, 178)); + theme.insert("insertion".to_string(), Color::Rgb(39, 222, 145)); + theme.insert("deletion".to_string(), Color::Rgb(255, 100, 100)); + Self { + theme, + user_rules: HashMap::default(), + } + } +} + impl SyntaxHighlighting { /// Get a colour from the theme pub fn get_theme(&self, name: &str) -> Result { diff --git a/src/config/interface.rs b/src/config/interface.rs index 064d4f81..df876da0 100644 --- a/src/config/interface.rs +++ b/src/config/interface.rs @@ -386,18 +386,23 @@ impl StatusLine { } impl LuaUserData for StatusLine { - fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method_mut("clear", |_, status_line, ()| { - status_line.parts.clear(); - Ok(()) + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("parts", |lua, this| { + let parts = lua.create_table()?; + for (i, part) in this.parts.iter().enumerate() { + parts.set(i + 1, part.clone())?; + } + Ok(parts) }); - methods.add_method_mut("add_part", |_, status_line, part| { - status_line.parts.push(part); + fields.add_field_method_set("parts", |_, this, value: LuaTable| { + let mut result = vec![]; + for item in value.pairs::() { + let (_, part) = item?; + result.push(part); + } + this.parts = result; Ok(()) }); - } - - fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_method_get("alignment", |_, this| { let alignment: String = this.alignment.clone().into(); Ok(alignment) diff --git a/src/config/mod.rs b/src/config/mod.rs index aa334db4..3578f22a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -10,6 +10,7 @@ use std::{ rc::Rc, }; +mod assistant; mod colors; mod editor; mod highlighting; @@ -17,6 +18,7 @@ mod interface; mod keys; mod tasks; +pub use assistant::Assistant; pub use colors::{Color, Colors}; pub use highlighting::SyntaxHighlighting; pub use interface::{GreetingMessage, HelpMessage, LineNumbers, StatusLine, TabLine, Terminal}; @@ -145,29 +147,20 @@ impl Config { } /// Actually take the configuration file, open it and interpret it - pub fn read(&mut self, path: &str, lua: &Lua) -> Result<()> { + pub fn read(path: &str, lua: &Lua) -> Result<()> { // Load the default config to start with lua.load(DEFAULT_CONFIG).exec()?; // Reset plugin status based on built-in configuration file lua.load("plugins = {}").exec()?; lua.load("builtins = {}").exec()?; - // Judge pre-user config state - let status_parts = self.status_line.borrow().parts.len(); - // Attempt to read config file from home directory + let user_provided = Self::get_user_provided_config(path); let mut user_provided_config = false; - if let Ok(path) = shellexpand::full(&path) { - if let Ok(config) = std::fs::read_to_string(path.to_string()) { - // Update configuration with user-defined values - lua.load(config).exec()?; - user_provided_config = true; - } - } - - // Remove any default values if necessary - if self.status_line.borrow().parts.len() > status_parts { - self.status_line.borrow_mut().parts.drain(0..status_parts); + if let Some(config) = user_provided { + // Load in user-defined configuration file + lua.load(config).exec()?; + user_provided_config = true; } // Determine whether or not to load built-in plugins @@ -188,6 +181,16 @@ impl Config { } } + /// Read the user-provided config + pub fn get_user_provided_config(path: &str) -> Option { + if let Ok(path) = shellexpand::full(&path) { + if let Ok(config) = std::fs::read_to_string(path.to_string()) { + return Some(config); + } + } + None + } + /// Decide whether to load a built-in plugin pub fn load_bi(name: &str, user_provided_config: bool, lua: &Lua) -> bool { if user_provided_config { diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 46b47fb4..f1f778c8 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -313,7 +313,7 @@ impl Editor { /// Load the configuration values pub fn load_config(&mut self, path: &str, lua: &Lua) -> Option { self.config_path = path.to_string(); - let result = self.config.read(path, lua); + let result = Config::read(path, lua); // Display any warnings if the user configuration couldn't be found match result { Ok(()) => (), diff --git a/src/main.rs b/src/main.rs index e2a1ca4c..cd93af7e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ mod ui; use cli::CommandLineInterface; use config::{ - key_to_string, run_key, run_key_before, PLUGIN_BOOTSTRAP, PLUGIN_MANAGER, PLUGIN_NETWORKING, - PLUGIN_RUN, + key_to_string, run_key, run_key_before, Assistant, Config, PLUGIN_BOOTSTRAP, PLUGIN_MANAGER, + PLUGIN_NETWORKING, PLUGIN_RUN, }; use crossterm::event::Event as CEvent; use editor::{Editor, FileTypes}; @@ -32,6 +32,15 @@ fn main() { // Handle help and version options cli.basic_options(); + // Activate configuration assistant if applicable + let no_config = Config::get_user_provided_config(&cli.config_path).is_none(); + if no_config || cli.flags.config_assist { + if let Err(err) = Assistant::run(no_config) { + panic!("{err:?}"); + } + } + + // Run the editor let result = run(&cli); if let Err(err) = result { panic!("{err:?}"); diff --git a/config/catppuccin-mocha.lua b/src/themes/galaxy.lua similarity index 73% rename from config/catppuccin-mocha.lua rename to src/themes/galaxy.lua index 87afdeaa..40f39ea3 100644 --- a/config/catppuccin-mocha.lua +++ b/src/themes/galaxy.lua @@ -1,16 +1,9 @@ --- Define user-defined commands -commands = { - ["reload"] = function(arguments) - editor:reload_config() - editor:display_info("Configuration file and plugins reloaded") - end, -} -- Pallette -- black = '#1e1e2e' -darkgrey = '#24273a' -lightgrey = '#303446' -verylightgrey = '#7f849c' +grey1 = '#24273a' +grey2 = '#303446' +grey3 = '#7f849c' white = '#cdd6f4' brown = '#f2cdcd' red = '#f38ba8' @@ -25,17 +18,17 @@ pink = '#f5c2e7' -- Configure Colours -- colors.editor_bg = black colors.editor_fg = white -colors.line_number_fg = lightgrey +colors.line_number_fg = grey2 colors.line_number_bg = black -colors.status_bg = darkgrey +colors.status_bg = grey1 colors.status_fg = purple colors.highlight = purple -colors.tab_inactive_bg = darkgrey +colors.tab_inactive_bg = grey1 colors.tab_inactive_fg = white -colors.tab_active_bg = lightgrey +colors.tab_active_bg = grey2 colors.tab_active_fg = purple colors.info_bg = black @@ -45,12 +38,12 @@ colors.warning_fg = yellow colors.error_bg = black colors.error_fg = red -colors.selection_bg = darkgrey +colors.selection_bg = grey1 colors.selection_fg = lightblue -- Configure Syntax Highlighting Colours -- syntax:set("string", green) -- Strings, bright green -syntax:set("comment", verylightgrey) -- Comments, light purple/gray +syntax:set("comment", grey3) -- Comments, light purple/gray syntax:set("digit", red) -- Digits, cyan syntax:set("keyword", purple) -- Keywords, vibrant pink syntax:set("attribute", lightblue) -- Attributes, cyan @@ -61,7 +54,7 @@ syntax:set("header", lightblue) -- Headers, cyan syntax:set("macro", red) -- Macros, red syntax:set("namespace", darkblue) -- Namespaces, light purple syntax:set("struct", pink) -- Structs, classes, and enums, light purple -syntax:set("operator", verylightgrey) -- Operators, light purple/gray +syntax:set("operator", grey3) -- Operators, light purple/gray syntax:set("boolean", green) -- Booleans, bright green syntax:set("table", purple) -- Tables, light purple syntax:set("reference", pink) -- References, vibrant pink @@ -69,7 +62,7 @@ syntax:set("tag", darkblue) -- Tags (e.g. HTML tags), cyan syntax:set("heading", purple) -- Headings, light purple syntax:set("link", pink) -- Links, vibrant pink syntax:set("key", pink) -- Keys, vibrant pink -syntax:set("quote", verylightgrey) -- Quotes, light purple/gray +syntax:set("quote", grey3) -- Quotes, light purple/gray syntax:set("bold", red) -- Bold text, cyan syntax:set("italic", orange) -- Italic text, cyan syntax:set("block", lightblue) -- Code blocks, cyan @@ -77,7 +70,3 @@ syntax:set("image", lightblue) -- Images in markup languages, cyan syntax:set("list", green) -- Lists, bright green syntax:set("insertion", green) -- Insertions (e.g. diff highlight), bright green syntax:set("deletion", red) -- Deletions (e.g. diff highlight), red - --- Import plugins (must be at the bottom of this file) -load_plugin("pairs.lua") -load_plugin("autoindent.lua") diff --git a/src/themes/transparent.lua b/src/themes/transparent.lua new file mode 100644 index 00000000..8a22e97f --- /dev/null +++ b/src/themes/transparent.lua @@ -0,0 +1,8 @@ + +-- Configure Colours -- +colors.editor_bg = 'transparent' +colors.line_number_bg = 'transparent' + +colors.info_bg = 'transparent' +colors.warning_bg = 'transparent' +colors.error_bg = 'transparent' diff --git a/config/tropical.lua b/src/themes/tropical.lua similarity index 73% rename from config/tropical.lua rename to src/themes/tropical.lua index 3e4eabae..fd604832 100644 --- a/config/tropical.lua +++ b/src/themes/tropical.lua @@ -1,16 +1,9 @@ --- Define user-defined commands -commands = { - ["reload"] = function(arguments) - editor:reload_config() - editor:display_info("Configuration file and plugins reloaded") - end, -} -- Pallette -- black = '#232336' -darkgrey = '#353552' -lightgrey = '#484863' -verylightgrey = '#A1A7C7' +grey1 = '#353552' +grey2 = '#484863' +grey3 = '#A1A7C7' white = '#cdd6f4' brown = '#dd7878' red = '#ed8796' @@ -22,37 +15,35 @@ darkblue = '#8aadf4' purple = '#c6a0f6' pink = '#f5bde6' --- PRIORITISE - ORANGE, RED, YELLOW, DARKBLUE, BROWN, GREEN, PINK - -- Configure Colours -- colors.editor_bg = black colors.editor_fg = white -colors.line_number_fg = lightgrey +colors.line_number_fg = grey2 colors.line_number_bg = black -colors.status_bg = darkgrey +colors.status_bg = grey1 colors.status_fg = orange colors.highlight = orange -colors.tab_inactive_bg = darkgrey +colors.tab_inactive_bg = grey1 colors.tab_inactive_fg = white -colors.tab_active_bg = lightgrey +colors.tab_active_bg = grey2 colors.tab_active_fg = orange colors.info_bg = black -colors.info_fg = lightblue +colors.info_fg = darkblue colors.warning_bg = black colors.warning_fg = yellow colors.error_bg = black colors.error_fg = red -colors.selection_bg = darkgrey +colors.selection_bg = grey1 colors.selection_fg = lightblue -- Configure Syntax Highlighting Colours -- syntax:set("string", lightblue) -- Strings, bright green -syntax:set("comment", verylightgrey) -- Comments, light purple/gray +syntax:set("comment", grey3) -- Comments, light purple/gray syntax:set("digit", lightblue) -- Digits, cyan syntax:set("keyword", orange) -- Keywords, vibrant pink syntax:set("attribute", darkblue) -- Attributes, cyan @@ -71,7 +62,7 @@ syntax:set("tag", orange) -- Tags (e.g. HTML tags), cyan syntax:set("heading", red) -- Headings, light purple syntax:set("link", darkblue) -- Links, vibrant pink syntax:set("key", yellow) -- Keys, vibrant pink -syntax:set("quote", verylightgrey) -- Quotes, light purple/gray +syntax:set("quote", grey3) -- Quotes, light purple/gray syntax:set("bold", red) -- Bold text, cyan syntax:set("italic", orange) -- Italic text, cyan syntax:set("block", red) -- Code blocks, cyan @@ -79,7 +70,3 @@ syntax:set("image", red) -- Images in markup languages, cyan syntax:set("list", red) -- Lists, bright green syntax:set("insertion", green) -- Insertions (e.g. diff highlight), bright green syntax:set("deletion", red) -- Deletions (e.g. diff highlight), red - --- Import plugins (must be at the bottom of this file) -load_plugin("pairs.lua") -load_plugin("autoindent.lua") From 208d52201229bc4b6ab995af6eea33b09732364f Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Fri, 18 Oct 2024 23:27:33 +0100 Subject: [PATCH 04/27] Modernised missing plug-in message and added final message to config assistant --- src/config/assistant.rs | 17 +++++++++++++++++ src/plugin/run.lua | 3 +-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/config/assistant.rs b/src/config/assistant.rs index 173bb50e..7690038f 100644 --- a/src/config/assistant.rs +++ b/src/config/assistant.rs @@ -75,6 +75,21 @@ Typing Speed - Shows the rough speed that you're typing in the status line Update Notification - Warns you if there is a new version of Ox - requires curl to be installed on unix based systems\n "; +const FINAL_WORDS: &str = r" +Configuration file was successfully written. + +Just a few things before you go: + +See the documentation here: https://github.com/curlpipe/ox/wiki +Report any bugs or request new features here: https://github.com/curlpipe/ox/issues/new/choose + +Remember: You can press Ctrl + H when you are in the editor to reveal a help message to get started + +I hope you enjoy your Ox experience + +Ready? Press the enter key to start Ox +"; + #[derive(PartialEq)] pub enum Theme { Tropical, @@ -280,6 +295,8 @@ impl Assistant { println!("Below is your newly generated configuration file:\n\n"); println!("{result}"); } + println!("{FINAL_WORDS}"); + let _ = gets!(); } Ok(()) } diff --git a/src/plugin/run.lua b/src/plugin/run.lua index 2bb0cf2f..50c40841 100644 --- a/src/plugin/run.lua +++ b/src/plugin/run.lua @@ -51,8 +51,7 @@ remap_keys("before:ctrl_alt_space", "before:ctrl_alt_ ") -- Show warning if any plugins weren't able to be loaded if plugin_issues then print("Various plug-ins failed to load") - print("You may download these plug-ins from the ox git repository (in the plugins folder)") - print("https://github.com/curlpipe/ox") + print("You may download these plug-ins by running the command `plugin install [plugin_name]`") print("") print("Alternatively, you may silence these warnings\nby removing the load_plugin() lines in your configuration file\nfor the missing plug-ins that are listed above") end From 0b03ebc76d5b30cac8932cb7a55372ea0b9faa2e Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 01:12:53 +0100 Subject: [PATCH 05/27] Added illustrations and clears the screen for different sections --- src/config/assistant.rs | 278 ++++++++++++++++++++++++++++------------ 1 file changed, 193 insertions(+), 85 deletions(-) diff --git a/src/config/assistant.rs b/src/config/assistant.rs index 7690038f..52afadd6 100644 --- a/src/config/assistant.rs +++ b/src/config/assistant.rs @@ -1,13 +1,15 @@ use crate::cli::VERSION; /// Code for the configuration set-up assistant -use crate::config::{Colors, Indentation, SyntaxHighlighting}; +use crate::config::{Color, Colors, Indentation, SyntaxHighlighting}; use crate::error::Result; +use crossterm::cursor::MoveTo; +use crossterm::execute; use crossterm::style::{SetBackgroundColor as Bg, SetForegroundColor as Fg}; +use crossterm::terminal::{Clear, ClearType}; use mlua::prelude::*; use std::cell::RefCell; -use std::io::Write; +use std::io::{stdout, Write}; use std::rc::Rc; -//use crate::config::Color; //use std::collections::HashMap; pub const TROPICAL: &str = include_str!("../themes/tropical.lua"); @@ -48,7 +50,7 @@ This way, you'll have a better user experience out of the box. const INTRODUCTION: &str = r" Welcome to the configuration assistant for the Ox Editor. This is a tool that will help get Ox set up for you. -It will take no more than 5 minutes and the config assistant will not show again after set-up. +It will take no more than 3 minutes and the config assistant will not show again after set-up. You can always re-access this tool using `ox --config-assist` "; @@ -191,7 +193,7 @@ impl Default for Assistant { impl Assistant { /// Run the configuration assistant pub fn run(because_no_config: bool) -> Result<()> { - println!("{TITLE}"); + Self::reset()?; if because_no_config { println!("{NO_CONFIG_MESSAGE}"); } @@ -199,98 +201,33 @@ impl Assistant { if Self::confirmation("Do you wish to set-up the editor?", true) { let mut result = Self::default(); // Theme - println!("Let's begin with what theme you'd like to use\n\n"); - // Prepare demonstration - Self::demonstrate_themes()?; - let choice = Self::options( - "Please choose which theme you'd like", - &["default", "tropical", "galaxy", "transparent"], - "default", - ); - result.theme = match choice.as_str() { - "default" => Theme::Default, - "tropical" => Theme::Tropical, - "galaxy" => Theme::Galaxy, - "transparent" => Theme::Transparent, - _ => unreachable!(), - }; + Self::ask_theme(&mut result)?; // Document - println!("Great choice, now let's move onto indentation\n"); - result.indentation = Self::options( - "Please choose how you'd like to represent indentation", - &["spaces", "tabs"], - "tabs", - ) - .into(); - result.tab_width = if result.indentation == Indentation::Tabs { - Self::integer("How wide should tabs be rendered as", 4) - } else { - Self::integer("How many spaces should make up 1 indent", 4) - }; + Self::ask_document(&mut result)?; // Line Numbers - println!("Great, now for deciding which parts of the editor should be visible\n"); - result.line_numbers = - Self::confirmation("Would you like line numbers to be visible", true); - if result.line_numbers { - result.line_number_padding = ( - Self::integer( - "How much space should there be on the left hand side of the line numbers", - 1, - ), - Self::integer( - "How much space should there be on the right hand side of the line numbers", - 1, - ), - ); - } + Self::ask_line_numbers(&mut result)?; // Tab line - result.tab_line = Self::confirmation("Would you like the tab line to be visible", true); - // Greeting message - result.greeting_message = Self::confirmation( - "Would you like the greeting message to be visible on start-up", - true, - ); + Self::ask_tab_line(&mut result)?; // Mouse and Cursor - println!("Now for the mouse and cursor behaviour\n"); - result.mouse = Self::confirmation( - "Would you like to use your mouse cursor in the editor", - true, - ); - result.scroll_sensitivity = Self::integer( - "How sensitive should scrolling be, 1 = least sensitive, 5 = very sensitive", - 2, - ); - result.cursor_wrap = Self::confirmation( - "Would you like the cursor to wrap around when at the edge of a line", - true, - ); + Self::ask_mouse_cursor(&mut result)?; // Icons - println!("Ox has support for icons, which can enhance the UI, if you choose to enable them, ensure you install nerd fonts\n"); - result.icons = - Self::confirmation("Would you like to enable icons, yes is recommended", false); + Self::ask_icons(&mut result)?; // Plug-Ins - Self::ask_plugins(&mut result); + Self::ask_plugins(&mut result)?; // Create the configuration file (and print it) + Self::reset()?; println!("\nSet-up is complete!"); if !because_no_config { - println!("WARNING: config file already exists, it will be backed-up to ~/.oxrc-backup if you write"); + let yellow = Fg(Color::Ansi(220).to_color()?); + let reset = Fg(Color::Transparent.to_color()?); + println!("{yellow}WARNING{reset}: config file already exists, it will be backed-up to ~/.oxrc-backup if you write"); } let result = result.to_config(); if Self::confirmation( "Would you like to write the configuration file?", because_no_config, ) { - let config_path = format!("{}/.oxrc", shellexpand::tilde("~")); - let backup_path = format!("{}/.oxrc-backup", shellexpand::tilde("~")); - if !because_no_config { - let _ = std::fs::rename(config_path.clone(), backup_path); - } - let mut file = std::fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(config_path)?; - file.write_all(result.as_bytes())?; + Self::write_config(&result, because_no_config)?; } else { println!("Below is your newly generated configuration file:\n\n"); println!("{result}"); @@ -301,6 +238,27 @@ impl Assistant { Ok(()) } + pub fn reset() -> Result<()> { + execute!(stdout(), Clear(ClearType::All), MoveTo(0, 0))?; + println!("{TITLE}"); + Ok(()) + } + + pub fn write_config(result: &str, because_no_config: bool) -> Result<()> { + let config_path = format!("{}/.oxrc", shellexpand::tilde("~")); + let backup_path = format!("{}/.oxrc-backup", shellexpand::tilde("~")); + if !because_no_config { + let _ = std::fs::rename(config_path.clone(), backup_path); + } + let mut file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(config_path)?; + file.write_all(result.as_bytes())?; + Ok(()) + } + pub fn confirmation(msg: &str, default: bool) -> bool { let mut response = "#####################".to_string(); while response != "y" && response != "n" && !response.is_empty() { @@ -348,12 +306,161 @@ impl Assistant { response.parse::().unwrap() } } - - pub fn ask_plugins(result: &mut Self) { + + pub fn ask_theme(result: &mut Self) -> Result<()> { + Self::reset()?; + println!("Let's begin with what theme you'd like to use\n\n"); + // Prepare demonstration + Self::demonstrate_themes()?; + let choice = Self::options( + "Please choose which theme you'd like", + &["default", "tropical", "galaxy", "transparent"], + "default", + ); + result.theme = match choice.as_str() { + "default" => Theme::Default, + "tropical" => Theme::Tropical, + "galaxy" => Theme::Galaxy, + "transparent" => Theme::Transparent, + _ => unreachable!(), + }; + Ok(()) + } + + pub fn ask_document(result: &mut Self) -> Result<()> { + let red = Fg(Color::Ansi(196).to_color()?); + let orange = Fg(Color::Ansi(202).to_color()?); + let yellow = Fg(Color::Ansi(220).to_color()?); + let green = Fg(Color::Ansi(34).to_color()?); + let blue = Fg(Color::Ansi(39).to_color()?); + let purple = Fg(Color::Ansi(141).to_color()?); + let pink = Fg(Color::Ansi(213).to_color()?); + let reset = Fg(Color::Transparent.to_color()?); + Self::reset()?; + println!("Great choice, now let's move onto indentation\n"); + println!("{purple}_{blue}_{purple}_{blue}_{reset}spaces"); + println!(" tabs\n{purple}‾‾‾‾{reset}"); + result.indentation = Self::options( + "Please choose how you'd like to represent indentation", + &["spaces", "tabs"], + "tabs", + ) + .into(); + println!("{red}•{reset}1"); + println!("{orange}••{reset}2"); + println!("{yellow}•••{reset}3"); + println!("{green}••••{reset}4"); + println!("{blue}•••••{reset}5"); + println!("{purple}••••••{reset}6"); + println!("{pink}•••••••{reset}7"); + println!("••••••••8\n"); + result.tab_width = if result.indentation == Indentation::Tabs { + Self::integer("How wide should tabs be rendered as", 4) + } else { + Self::integer("How many spaces should make up 1 indent", 4) + }; + Ok(()) + } + + pub fn ask_mouse_cursor(result: &mut Self) -> Result<()> { + let red = Fg(Color::Ansi(196).to_color()?); + let yellow = Fg(Color::Ansi(220).to_color()?); + let green = Fg(Color::Ansi(34).to_color()?); + let blue = Fg(Color::Ansi(39).to_color()?); + let purple = Fg(Color::Ansi(141).to_color()?); + let pink = Fg(Color::Ansi(213).to_color()?); + let reset = Fg(Color::Transparent.to_color()?); + Self::reset()?; + println!("Now for the mouse and cursor behaviour\n"); + println!("{blue}🖰 {reset}Clicking to move cursor, {purple}◅ 🖰 ▻ {reset} Dragging to select text\n"); + result.mouse = Self::confirmation( + "Would you like to use your mouse cursor in the editor", + true, + ); + println!("{red}🖰 ⭥ {reset} {yellow}🖰 ⭥ {reset} {green}🖰 ⭥ {reset}\n"); + result.scroll_sensitivity = Self::integer( + "How sensitive should scrolling be, 1 = least sensitive, 5 = very sensitive", + 2, + ); + println!(" Cursor wraps{pink}|{reset}→ \n ↳ {pink}|{reset}Onto new line\n"); + result.cursor_wrap = Self::confirmation( + "Would you like the cursor to wrap around when at the edge of a line", + true, + ); + Ok(()) + } + + pub fn ask_tab_line(result: &mut Self) -> Result<()> { + let orange = Fg(Color::Ansi(202).to_color()?); + let yellow = Fg(Color::Ansi(220).to_color()?); + let green = Fg(Color::Ansi(34).to_color()?); + let purple = Fg(Color::Ansi(141).to_color()?); + let reset = Fg(Color::Transparent.to_color()?); + println!( + "| {purple}File 1{reset} | {green}File 2{reset} | {orange}File 3{reset} |\n" + ); + result.tab_line = Self::confirmation("Would you like the tab line to be visible", true); + // Greeting message + println!(" {yellow}Welcome to Ox Editor!{reset} \n"); + result.greeting_message = Self::confirmation( + "Would you like the greeting message to be visible on start-up", + true, + ); + Ok(()) + } + + pub fn ask_line_numbers(result: &mut Self) -> Result<()> { + let red = Fg(Color::Ansi(196).to_color()?); + let orange = Fg(Color::Ansi(202).to_color()?); + let yellow = Fg(Color::Ansi(220).to_color()?); + let green = Fg(Color::Ansi(34).to_color()?); + let reset = Fg(Color::Transparent.to_color()?); + Self::reset()?; + println!("Great, now for deciding which parts of the editor should be visible\n"); + println!("{green} 1 {reset}│"); + println!("{yellow} 2 {reset}│"); + println!("{red} 3 {reset}│\n"); + result.line_numbers = Self::confirmation("Would you like line numbers to be visible", true); + if result.line_numbers { + println!("{red}•{reset}1 │"); + println!("{orange}••{reset}2 │"); + println!("{yellow}•••{reset}3 │\n"); + let left_tab = Self::integer( + "How much space should there be on the left hand side of the line numbers", + 1, + ); + println!(" 1{red}•{reset}│"); + println!(" 2{orange}••{reset}│"); + println!(" 3{yellow}•••{reset}│\n"); + let right_tab = Self::integer( + "How much space should there be on the right hand side of the line numbers", + 1, + ); + result.line_number_padding = (left_tab, right_tab); + } + Ok(()) + } + + pub fn ask_icons(result: &mut Self) -> Result<()> { + let yellow = Fg(Color::Ansi(220).to_color()?); + let blue = Fg(Color::Ansi(39).to_color()?); + let reset = Fg(Color::Transparent.to_color()?); + Self::reset()?; + println!("{blue}🖹 {yellow}🖉 {reset}"); + println!("Ox has support for icons, which can enhance the UI, if you choose to enable them, ensure you install nerd fonts\n"); + result.icons = + Self::confirmation("Would you like to enable icons, yes is recommended", false); + Ok(()) + } + + pub fn ask_plugins(result: &mut Self) -> Result<()> { + Self::reset()?; + let green = Fg(Color::Ansi(34).to_color()?); + let reset = Fg(Color::Transparent.to_color()?); println!("{PLUGIN_LIST}"); let mut adding = String::new(); while adding != "exit" { - println!("Enabled plug-ins: {:?}\n", result.plugins); + println!("{green}Enabled plug-ins:{reset} {:?}\n", result.plugins); adding = Self::options( "Enter the name of a plug-in you'd like to enable / disable", &[ @@ -390,6 +497,7 @@ impl Assistant { result.plugins.push(plugin); } } + Ok(()) } pub fn demonstrate_themes() -> Result<()> { From 9556ed18e34dfa9ddfb17d69aea0035e838f108c Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:12:23 +0100 Subject: [PATCH 06/27] Installed plug-ins for user after configuration set-up --- src/config/assistant.rs | 58 +++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/config/assistant.rs b/src/config/assistant.rs index 52afadd6..a4863742 100644 --- a/src/config/assistant.rs +++ b/src/config/assistant.rs @@ -2,6 +2,7 @@ use crate::cli::VERSION; /// Code for the configuration set-up assistant use crate::config::{Color, Colors, Indentation, SyntaxHighlighting}; use crate::error::Result; +use crate::{PLUGIN_BOOTSTRAP, PLUGIN_MANAGER, PLUGIN_NETWORKING}; use crossterm::cursor::MoveTo; use crossterm::execute; use crossterm::style::{SetBackgroundColor as Bg, SetForegroundColor as Fg}; @@ -92,6 +93,13 @@ I hope you enjoy your Ox experience Ready? Press the enter key to start Ox "; +const PLUGIN_INSTALL: &str = r#" +if not plugin_manager:plugin_downloaded('{name}') then + print("Installing " .. '{name}') + plugin_manager:download_plugin('{name}') +end +"#; + #[derive(PartialEq)] pub enum Theme { Tropical, @@ -129,21 +137,23 @@ pub enum Plugin { impl Plugin { pub fn to_config(&self) -> String { - format!( - "load_plugin(\"{}\")\n", - match self { - Self::AutoIndent => "autoindent.lua", - Self::Pairs => "pairs.lua", - Self::DiscordRPC => "discord_rpc.lua", - Self::Emmet => "emmet.lua", - Self::Git => "git.lua", - Self::LiveHTML => "live_html.lua", - Self::Pomodoro => "pomodoro.lua", - Self::Todo => "todo.lua", - Self::TypingSpeed => "typing_speed.lua", - Self::UpdateNotification => "update_notification.lua", - } - ) + let plugin_name = self.name(); + format!("load_plugin(\"{plugin_name}.lua\")\n") + } + + pub fn name(&self) -> &str { + match self { + Self::AutoIndent => "autoindent", + Self::Pairs => "pairs", + Self::DiscordRPC => "discord_rpc", + Self::Emmet => "emmet", + Self::Git => "git", + Self::LiveHTML => "live_html", + Self::Pomodoro => "pomodoro", + Self::Todo => "todo", + Self::TypingSpeed => "typing_speed", + Self::UpdateNotification => "update_notification", + } } } @@ -222,15 +232,15 @@ impl Assistant { let reset = Fg(Color::Transparent.to_color()?); println!("{yellow}WARNING{reset}: config file already exists, it will be backed-up to ~/.oxrc-backup if you write"); } - let result = result.to_config(); + let contents = result.to_config(); if Self::confirmation( "Would you like to write the configuration file?", because_no_config, ) { - Self::write_config(&result, because_no_config)?; + Self::write_config(&result.plugins, &contents, because_no_config)?; } else { println!("Below is your newly generated configuration file:\n\n"); - println!("{result}"); + println!("{contents}"); } println!("{FINAL_WORDS}"); let _ = gets!(); @@ -244,7 +254,7 @@ impl Assistant { Ok(()) } - pub fn write_config(result: &str, because_no_config: bool) -> Result<()> { + pub fn write_config(plugins: &Vec, result: &str, because_no_config: bool) -> Result<()> { let config_path = format!("{}/.oxrc", shellexpand::tilde("~")); let backup_path = format!("{}/.oxrc-backup", shellexpand::tilde("~")); if !because_no_config { @@ -256,6 +266,16 @@ impl Assistant { .truncate(true) .open(config_path)?; file.write_all(result.as_bytes())?; + // Install plug-ins + let lua = Lua::new(); + lua.load("commands = {}").exec()?; + lua.load(PLUGIN_BOOTSTRAP).exec()?; + lua.load(PLUGIN_NETWORKING).exec()?; + lua.load(PLUGIN_MANAGER).exec()?; + for plugin in plugins { + let plugin_name = plugin.name(); + lua.load(PLUGIN_INSTALL.replace("{name}", plugin_name)).exec()?; + } Ok(()) } From 218e3fad0dcfc65d206ef77ede86bdbf618bc5c0 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:12:51 +0100 Subject: [PATCH 07/27] rustfmt --- src/config/assistant.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/config/assistant.rs b/src/config/assistant.rs index a4863742..4dec4b5f 100644 --- a/src/config/assistant.rs +++ b/src/config/assistant.rs @@ -140,7 +140,7 @@ impl Plugin { let plugin_name = self.name(); format!("load_plugin(\"{plugin_name}.lua\")\n") } - + pub fn name(&self) -> &str { match self { Self::AutoIndent => "autoindent", @@ -254,7 +254,11 @@ impl Assistant { Ok(()) } - pub fn write_config(plugins: &Vec, result: &str, because_no_config: bool) -> Result<()> { + pub fn write_config( + plugins: &Vec, + result: &str, + because_no_config: bool, + ) -> Result<()> { let config_path = format!("{}/.oxrc", shellexpand::tilde("~")); let backup_path = format!("{}/.oxrc-backup", shellexpand::tilde("~")); if !because_no_config { @@ -274,7 +278,8 @@ impl Assistant { lua.load(PLUGIN_MANAGER).exec()?; for plugin in plugins { let plugin_name = plugin.name(); - lua.load(PLUGIN_INSTALL.replace("{name}", plugin_name)).exec()?; + lua.load(PLUGIN_INSTALL.replace("{name}", plugin_name)) + .exec()?; } Ok(()) } From bd4444fb38fb5a97ba871f22f09ecfc655e39bc1 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 17:06:39 +0100 Subject: [PATCH 08/27] Fixed minor issues with Rust syntax highlighting --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4e11b78..8f8d42cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,9 +56,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.1.30" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libredox" @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -591,9 +591,9 @@ dependencies = [ [[package]] name = "synoptic" -version = "2.0.8" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1397c98697342b5b76b1dce4ecf2a912dc6407257b7db8c3006ab7d9b69b5b70" +checksum = "58e69f54fedd3b7dc77c792a22844c7917102ecd2bb6f6a7f3347863e0b23540" dependencies = [ "if_chain", "regex", From a7a8f54e9e1e2e18da2a4e87b00aceb5d7edd02c Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 17:15:59 +0100 Subject: [PATCH 09/27] Fixed issue with file path prompt not having a background --- src/editor/interface.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/editor/interface.rs b/src/editor/interface.rs index 50c2019c..98ab09d0 100644 --- a/src/editor/interface.rs +++ b/src/editor/interface.rs @@ -290,6 +290,7 @@ impl Editor { } /// Prompt for selecting a file + #[allow(clippy::similar_names)] pub fn path_prompt(&mut self) -> Result { let mut input = get_cwd().map(|s| s + "/").unwrap_or_default(); let mut offset = 0; @@ -328,12 +329,18 @@ impl Editor { .skip(input.chars().count()) .collect::(); let editor_fg = Fg(self.config.colors.borrow().editor_fg.to_color()?); + let editor_bg = Bg(self.config.colors.borrow().editor_bg.to_color()?); + let tab_width = self.config.document.borrow().tab_width; + let total_width = width(&input, tab_width) + width(&suggestion_text, tab_width); + let padding = " ".repeat(size()?.w.saturating_sub(total_width)); display!( self, + editor_bg, "Path: ", input.clone(), Fg(Color::DarkGrey), suggestion_text, + padding, editor_fg ); let tab_width = self.config.document.borrow_mut().tab_width; From 60f8e3906acbea1c50b38043284ce87f85d78813 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 17:25:08 +0100 Subject: [PATCH 10/27] Fixed particular instances of being able to edit a read only file --- src/editor/editing.rs | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/editor/editing.rs b/src/editor/editing.rs index f86827fe..fa3ef3b8 100644 --- a/src/editor/editing.rs +++ b/src/editor/editing.rs @@ -18,7 +18,7 @@ impl Editor { /// Insert a character into the document, creating a new row if editing /// on the last line of the document pub fn character(&mut self, ch: char) -> Result<()> { - if !self.doc().is_selection_empty() { + if !self.doc().is_selection_empty() && !self.doc().info.read_only { self.doc_mut().remove_selection(); self.reload_highlight(); } @@ -30,7 +30,9 @@ impl Editor { let loc = self.doc().char_loc(); self.exe(Event::Insert(loc, ch.to_string()))?; let file = &mut self.files[self.ptr]; - file.highlighter.edit(loc.y, &file.doc.lines[loc.y]); + if !file.doc.info.read_only { + file.highlighter.edit(loc.y, &file.doc.lines[loc.y]); + } } Ok(()) } @@ -46,17 +48,19 @@ impl Editor { let loc = self.doc().char_loc(); self.exe(Event::SplitDown(loc))?; let file = &mut self.files[self.ptr]; - let line = &file.doc.lines[loc.y + 1]; - file.highlighter.insert_line(loc.y + 1, line); - let line = &file.doc.lines[loc.y]; - file.highlighter.edit(loc.y, line); + if !file.doc.info.read_only { + let line = &file.doc.lines[loc.y + 1]; + file.highlighter.insert_line(loc.y + 1, line); + let line = &file.doc.lines[loc.y]; + file.highlighter.edit(loc.y, line); + } } Ok(()) } /// Handle the backspace key pub fn backspace(&mut self) -> Result<()> { - if !self.doc().is_selection_empty() { + if !self.doc().is_selection_empty() && !self.doc().info.read_only { // Removing a selection is significant and worth an undo commit self.doc_mut().commit(); self.doc_mut().undo_mgmt.set_dirty(); @@ -71,14 +75,19 @@ impl Editor { // Backspace was pressed on the start of the line, move line to the top self.new_row()?; let mut loc = self.doc().char_loc(); - self.highlighter().remove_line(loc.y); + let file = &self.files[self.ptr]; + if !file.doc.info.read_only { + self.highlighter().remove_line(loc.y); + } loc.y = loc.y.saturating_sub(1); let file = &mut self.files[self.ptr]; loc.x = file.doc.line(loc.y).unwrap().chars().count(); self.exe(Event::SpliceUp(loc))?; let file = &mut self.files[self.ptr]; let line = &file.doc.lines[loc.y]; - file.highlighter.edit(loc.y, line); + if !file.doc.info.read_only { + file.highlighter.edit(loc.y, line); + } } else if !(c == 0 && on_first_line) { // Backspace was pressed in the middle of the line, delete the character c = c.saturating_sub(1); @@ -90,7 +99,9 @@ impl Editor { }; self.exe(Event::Delete(loc, ch.to_string()))?; let file = &mut self.files[self.ptr]; - file.highlighter.edit(loc.y, &file.doc.lines[loc.y]); + if !file.doc.info.read_only { + file.highlighter.edit(loc.y, &file.doc.lines[loc.y]); + } } } } @@ -108,7 +119,9 @@ impl Editor { }; self.exe(Event::Delete(loc, ch.to_string()))?; let file = &mut self.files[self.ptr]; - file.highlighter.edit(loc.y, &file.doc.lines[loc.y]); + if !file.doc.info.read_only { + file.highlighter.edit(loc.y, &file.doc.lines[loc.y]); + } } } Ok(()) @@ -118,7 +131,9 @@ impl Editor { fn new_row(&mut self) -> Result<()> { if self.doc().loc().y == self.doc().len_lines() { self.exe(Event::InsertLine(self.doc().loc().y, String::new()))?; - self.highlighter().append(&String::new()); + if !self.doc().info.read_only { + self.highlighter().append(&String::new()); + } } Ok(()) } @@ -130,7 +145,9 @@ impl Editor { let y = self.doc().loc().y; let line = self.doc().line(y).unwrap(); self.exe(Event::DeleteLine(y, line))?; - self.highlighter().remove_line(y); + if !self.doc().info.read_only { + self.highlighter().remove_line(y); + } } Ok(()) } From e93ed72e2f825830d1ac8457351a27dbde7bdd7b Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:50:28 +0100 Subject: [PATCH 11/27] Changed rendering code to prevent artefacts on long lines --- Cargo.lock | 10 +++++----- src/editor/interface.rs | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f8d42cc..099fcd30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -580,9 +580,9 @@ checksum = "cc0db74f9ee706e039d031a560bd7d110c7022f016051b3d33eeff9583e3e67a" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "e6e185e337f816bc8da115b8afcb3324006ccc82eeaddf35113888d3bd8e44ac" dependencies = [ "proc-macro2", "quote", @@ -591,13 +591,13 @@ dependencies = [ [[package]] name = "synoptic" -version = "2.0.9" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e69f54fedd3b7dc77c792a22844c7917102ecd2bb6f6a7f3347863e0b23540" +checksum = "97419cc9b1e79552ca0622b66093fc5217d9b0048415f2c8680a52be2fcd41f7" dependencies = [ "if_chain", "regex", - "unicode-width 0.1.14", + "unicode-width 0.2.0", ] [[package]] diff --git a/src/editor/interface.rs b/src/editor/interface.rs index 98ab09d0..21401121 100644 --- a/src/editor/interface.rs +++ b/src/editor/interface.rs @@ -77,7 +77,15 @@ impl Editor { .collect::>(); let start = u16::try_from(h / 4).unwrap_or(u16::MAX); let end = start + u16::try_from(message.len()).unwrap_or(u16::MAX); + // Render each line of the document for y in 0..u16::try_from(h).unwrap_or(0) { + // Work out how long the line should be (accounting for help message if necessary) + let required_width = if self.config.help_message.borrow().enabled && (start..=end).contains(&y) { + w.saturating_sub(self.dent()).saturating_sub(max_width) + } else { + w.saturating_sub(self.dent()) + }; + // Go to the right location self.terminal.goto(0, y as usize + self.push_down)?; // Start colours let editor_bg = Bg(self.config.colors.borrow().editor_bg.to_color()?); @@ -106,10 +114,9 @@ impl Editor { } // Render line if it exists let idx = y as usize + self.doc().offset.y; - let pad_amount; if let Some(line) = self.doc().line(idx) { let tokens = self.highlighter().line(idx, &line); - let tokens = trim(&tokens, self.doc().offset.x); + let tokens = trim(&tokens, self.doc().offset.x, required_width, tab_width); let mut x_pos = self.doc().offset.x; for token in tokens { // Find out the text (and colour of that text) @@ -131,7 +138,7 @@ impl Editor { // Highlighted text TokOpt::None(text) => (text, editor_fg), }; - // Do the rendering + // Do the rendering (including selection where applicable) for c in text.chars() { let at_x = self.doc().character_idx(&Loc { y: idx, x: x_pos }); let is_selected = self.doc().is_loc_selected(Loc { y: idx, x: at_x }); @@ -144,22 +151,15 @@ impl Editor { x_pos += 1; } } - // Pad out the line (to remove any junk left over from previous render) display!(self, editor_fg, editor_bg); - let tab_width = self.config.document.borrow().tab_width; - let line_width = width(&line, tab_width); - pad_amount = w.saturating_sub(self.dent()).saturating_sub(line_width) + 1; } else { - // Render empty line - pad_amount = w.saturating_sub(self.dent()) + 1; + // Empty line, just pad out with spaces to prevent artefacts + display!(self, " ".repeat(required_width)); } // Render help message if applicable (otherwise, just output padding to clear buffer) if self.config.help_message.borrow().enabled && (start..=end).contains(&y) { let idx = y.saturating_sub(start); - display!(self, " ".repeat(pad_amount.saturating_sub(max_width))); display!(self, message.get(idx as usize).unwrap_or(&String::new())); - } else { - display!(self, " ".repeat(pad_amount)); } } Ok(()) From 0e866048d04a812312912e172276bb455dac68bb Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 23:14:26 +0100 Subject: [PATCH 12/27] Fixed line loading bug and commit to event stack when pasting --- src/editor/interface.rs | 11 ++++++----- src/editor/mod.rs | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/editor/interface.rs b/src/editor/interface.rs index 21401121..b881c19c 100644 --- a/src/editor/interface.rs +++ b/src/editor/interface.rs @@ -80,11 +80,12 @@ impl Editor { // Render each line of the document for y in 0..u16::try_from(h).unwrap_or(0) { // Work out how long the line should be (accounting for help message if necessary) - let required_width = if self.config.help_message.borrow().enabled && (start..=end).contains(&y) { - w.saturating_sub(self.dent()).saturating_sub(max_width) - } else { - w.saturating_sub(self.dent()) - }; + let required_width = + if self.config.help_message.borrow().enabled && (start..=end).contains(&y) { + w.saturating_sub(self.dent()).saturating_sub(max_width) + } else { + w.saturating_sub(self.dent()) + }; // Go to the right location self.terminal.goto(0, y as usize + self.push_down)?; // Start colours diff --git a/src/editor/mod.rs b/src/editor/mod.rs index f1f778c8..93da0305 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -378,7 +378,7 @@ impl Editor { let max = self.dent(); self.doc_mut().size.w = w.saturating_sub(u16::try_from(max).unwrap_or(u16::MAX)) as usize; self.doc_mut().size.h = h.saturating_sub(3) as usize; - let max = self.doc().offset.x + self.doc().size.h; + let max = self.doc().offset.y + self.doc().size.h; self.doc_mut().load_to(max + 1); } @@ -387,6 +387,8 @@ impl Editor { for ch in text.chars() { self.character(ch)?; } + // Paste warrants a commit here really + self.doc_mut().commit(); Ok(()) } From 335f96913fae0fc95a2b80ca536e1b640d2c0375 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sat, 19 Oct 2024 23:31:03 +0100 Subject: [PATCH 13/27] Tabs now open in a much better order --- src/editor/mod.rs | 22 ++++++++++++++++------ src/main.rs | 4 ++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 93da0305..c9c1f2e6 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -101,18 +101,23 @@ impl Editor { // Mark as not saved on disk doc.info.modified = true; // Add document to documents - self.files.push(FileContainer { + let file = FileContainer { highlighter, file_type: Some(FileType::default()), doc, - }); + }; + if self.ptr + 1 >= self.files.len() { + self.files.push(file); + } else { + self.files.insert(self.ptr + 1, file); + } Ok(()) } /// Create a new document and move to it pub fn new_document(&mut self) -> Result<()> { self.blank()?; - self.ptr = self.files.len().saturating_sub(1); + self.next(); Ok(()) } @@ -144,11 +149,16 @@ impl Editor { }); highlighter.run(&doc.lines); // Add in the file - self.files.push(FileContainer { + let file = FileContainer { doc, highlighter, file_type, - }); + }; + if self.ptr + 1 >= self.files.len() { + self.files.push(file); + } else { + self.files.insert(self.ptr + 1, file); + } Ok(()) } @@ -156,7 +166,7 @@ impl Editor { pub fn open_document(&mut self) -> Result<()> { let path = self.path_prompt()?; self.open(&path)?; - self.ptr = self.files.len().saturating_sub(1); + self.next(); self.update_cwd(); Ok(()) } diff --git a/src/main.rs b/src/main.rs index cd93af7e..57bdea63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,7 +111,11 @@ fn run(cli: &CommandLineInterface) -> Result<()> { file.highlighter = highlighter; file.file_type = Some(file_type); } + // Move the pointer to the file we just created + editor.borrow_mut().next(); } + // Reset the pointer back to the first document + editor.borrow_mut().ptr = 0; // Handle stdin if applicable if cli.flags.stdin { From 0f991019a9aa2c3572b236d6d121958db096f575 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:02:21 +0100 Subject: [PATCH 14/27] Improved word traversal --- kaolinite/src/document.rs | 12 ++++++++---- src/config/assistant.rs | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/kaolinite/src/document.rs b/kaolinite/src/document.rs index f64d00a4..48d7fc8a 100644 --- a/kaolinite/src/document.rs +++ b/kaolinite/src/document.rs @@ -665,11 +665,12 @@ impl Document { /// Moves to the previous word in the document pub fn move_prev_word(&mut self) -> Status { let Loc { x, y } = self.char_loc(); + // Handle case where we're at the beginning of the line if x == 0 && y != 0 { return Status::StartOfLine; } - let re = format!("(\t| {{{}}}|^|\\W|$| )", self.tab_width); - let mut searcher = Searcher::new(&re); + // Find where all the words are + let mut searcher = Searcher::new(r"(^|\s{2,}|[A-Za-z0-9_]+|\.)"); let line = self .line(y) .unwrap_or_default() @@ -677,6 +678,8 @@ impl Document { .take(x) .collect::(); let mut matches = searcher.rfinds(&line); + matches.iter_mut().for_each(|m| m.loc.x += m.text.chars().count()); + // Find the most appropriate one to move to given the cursor position if let Some(mtch) = matches.first() { if mtch.loc.x == x { matches.remove(0); @@ -694,11 +697,12 @@ impl Document { pub fn move_next_word(&mut self) -> Status { let Loc { x, y } = self.char_loc(); let line = self.line(y).unwrap_or_default(); + // Handle case where we're at the end of the line if x == line.chars().count() && y != self.len_lines() { return Status::EndOfLine; } - let re = format!("(\t| {{{}}}|\\W|$|^ +| )", self.tab_width); - if let Some(mut mtch) = self.next_match(&re, 0) { + // Find and move to the next word + if let Some(mut mtch) = self.next_match(r"(\s{2,}|[A-Za-z0-9_]+|$|\.)", 0) { mtch.loc.x += mtch.text.chars().count(); self.move_to(&mtch.loc); } diff --git a/src/config/assistant.rs b/src/config/assistant.rs index 4dec4b5f..0c64b2bd 100644 --- a/src/config/assistant.rs +++ b/src/config/assistant.rs @@ -1,5 +1,5 @@ -use crate::cli::VERSION; /// Code for the configuration set-up assistant +use crate::cli::VERSION; use crate::config::{Color, Colors, Indentation, SyntaxHighlighting}; use crate::error::Result; use crate::{PLUGIN_BOOTSTRAP, PLUGIN_MANAGER, PLUGIN_NETWORKING}; From 6aec15d8fffb4435684bafd51ebdecebdcda378c Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:13:46 +0100 Subject: [PATCH 15/27] Shift tab to dedent lines --- kaolinite/src/document.rs | 4 +++- plugins/autoindent.lua | 8 +++++++- src/plugin/run.lua | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/kaolinite/src/document.rs b/kaolinite/src/document.rs index 48d7fc8a..757851a5 100644 --- a/kaolinite/src/document.rs +++ b/kaolinite/src/document.rs @@ -678,7 +678,9 @@ impl Document { .take(x) .collect::(); let mut matches = searcher.rfinds(&line); - matches.iter_mut().for_each(|m| m.loc.x += m.text.chars().count()); + matches + .iter_mut() + .for_each(|m| m.loc.x += m.text.chars().count()); // Find the most appropriate one to move to given the cursor position if let Some(mtch) = matches.first() { if mtch.loc.x == x { diff --git a/plugins/autoindent.lua b/plugins/autoindent.lua index 9790d076..ba61ae9a 100644 --- a/plugins/autoindent.lua +++ b/plugins/autoindent.lua @@ -1,5 +1,5 @@ --[[ -Auto Indent v0.9 +Auto Indent v0.10 Helps you when programming by guessing where indentation should go and then automatically applying these guesses as you program @@ -191,3 +191,9 @@ for i = 32, 126 do end end end + +-- Shortcut to dedent a line +event_mapping["shift_tab"] = function() + local level = autoindent:get_indent(editor.cursor.y) + autoindent:set_indent(editor.cursor.y, level - 1) +end diff --git a/src/plugin/run.lua b/src/plugin/run.lua index 50c40841..1955bfdc 100644 --- a/src/plugin/run.lua +++ b/src/plugin/run.lua @@ -43,10 +43,12 @@ remap_keys("space", " ") remap_keys("ctrl_space", "ctrl_ ") remap_keys("alt_space", "alt_ ") remap_keys("ctrl_alt_space", "ctrl_alt_ ") +remap_keys("shift_tab", "shift_backtab") remap_keys("before:space", "before: ") remap_keys("before:ctrl_space", "before:ctrl_ ") remap_keys("before:alt_space", "before:alt_ ") remap_keys("before:ctrl_alt_space", "before:ctrl_alt_ ") +remap_keys("before:shift_tab", "before:shift_backtab") -- Show warning if any plugins weren't able to be loaded if plugin_issues then From bfd883ed298c50372444e82be33fb19643cb7248 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:21:20 +0100 Subject: [PATCH 16/27] Fixed cursor position when moving lines up and down --- config/.oxrc | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/config/.oxrc b/config/.oxrc index 015d7e11..82ea911d 100644 --- a/config/.oxrc +++ b/config/.oxrc @@ -117,25 +117,27 @@ event_mapping = { end, ["alt_up"] = function() -- current line information - line = editor:get_line() - y = editor.cursor.y + local line = editor:get_line() + local cursor = editor.cursor -- insert a new line - editor:insert_line_at(line, y - 1) + editor:insert_line_at(line, cursor.y - 1) -- delete old copy and reposition cursor - editor:remove_line_at(y + 1) - editor:move_up() + editor:remove_line_at(cursor.y + 1) + -- restore cursor position + editor:move_to(cursor.x, cursor.y - 1) -- correct indentation level autoindent:fix_indent() end, ["alt_down"] = function() -- current line information - line = editor:get_line() - y = editor.cursor.y + local line = editor:get_line() + local cursor = editor.cursor -- insert a new line - editor:insert_line_at(line, y + 2) + editor:insert_line_at(line, cursor.y + 2) -- delete old copy and reposition cursor - editor:remove_line_at(y) - editor:move_down() + editor:remove_line_at(cursor.y) + -- restore cursor position + editor:move_to(cursor.x, cursor.y + 1) -- correct indentation level autoindent:fix_indent() end, From 47ad3db33cb82ebc2de4e9169fec392eb37480ba Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:47:08 +0100 Subject: [PATCH 17/27] updated tests --- kaolinite/tests/test.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/kaolinite/tests/test.rs b/kaolinite/tests/test.rs index 970870d1..230db8e5 100644 --- a/kaolinite/tests/test.rs +++ b/kaolinite/tests/test.rs @@ -545,27 +545,33 @@ fn document_moving() { doc.exe(Event::InsertLine(10, st!("these are words this.is.code()"))); doc.move_to(&Loc { x: 0, y: 10 }); doc.move_next_word(); - assert_eq!(doc.loc(), Loc { x: 6, y: 10 }); + assert_eq!(doc.loc(), Loc { x: 5, y: 10 }); doc.move_next_word(); - assert_eq!(doc.loc(), Loc { x: 10, y: 10 }); + assert_eq!(doc.loc(), Loc { x: 9, y: 10 }); doc.move_next_word(); - assert_eq!(doc.loc(), Loc { x: 16, y: 10 }); + assert_eq!(doc.loc(), Loc { x: 15, y: 10 }); + doc.move_next_word(); + assert_eq!(doc.loc(), Loc { x: 20, y: 10 }); doc.move_next_word(); assert_eq!(doc.loc(), Loc { x: 21, y: 10 }); doc.move_next_word(); + assert_eq!(doc.loc(), Loc { x: 23, y: 10 }); + doc.move_next_word(); assert_eq!(doc.loc(), Loc { x: 24, y: 10 }); doc.move_next_word(); - assert_eq!(doc.loc(), Loc { x: 29, y: 10 }); + assert_eq!(doc.loc(), Loc { x: 28, y: 10 }); doc.move_next_word(); assert_eq!(doc.loc(), Loc { x: 30, y: 10 }); assert_eq!(doc.move_next_word(), Status::EndOfLine); doc.move_prev_word(); - assert_eq!(doc.loc(), Loc { x: 29, y: 10 }); - doc.move_prev_word(); assert_eq!(doc.loc(), Loc { x: 28, y: 10 }); doc.move_prev_word(); + assert_eq!(doc.loc(), Loc { x: 24, y: 10 }); + doc.move_prev_word(); assert_eq!(doc.loc(), Loc { x: 23, y: 10 }); doc.move_prev_word(); + assert_eq!(doc.loc(), Loc { x: 21, y: 10 }); + doc.move_prev_word(); assert_eq!(doc.loc(), Loc { x: 20, y: 10 }); doc.move_prev_word(); assert_eq!(doc.loc(), Loc { x: 15, y: 10 }); From fcf69800f40b0896d35f837558f5edcfe1c8c08a Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sun, 20 Oct 2024 13:57:01 +0100 Subject: [PATCH 18/27] Complete reimplementation of word deletion and moving --- config/.oxrc | 19 +--- kaolinite/src/document.rs | 190 ++++++++++++++++++++++++++++++++----- kaolinite/src/searching.rs | 2 +- src/config/editor.rs | 4 + 4 files changed, 172 insertions(+), 43 deletions(-) diff --git a/config/.oxrc b/config/.oxrc index 82ea911d..4cc188d8 100644 --- a/config/.oxrc +++ b/config/.oxrc @@ -142,24 +142,7 @@ event_mapping = { autoindent:fix_indent() end, ["ctrl_w"] = function() - y = editor.cursor.y - x = editor.cursor.x - if editor:get_character() == " " then - start = 0 - else - start = 1 - end - editor:move_previous_word() - new_x = editor.cursor.x - diff = x - new_x - if editor.cursor.y == y then - -- Cursor on the same line - for i = start, diff do - editor:remove_at(new_x, y) - end - else - -- Cursor has passed up onto the previous line - end + editor:remove_word() end, } diff --git a/kaolinite/src/document.rs b/kaolinite/src/document.rs index 757851a5..3a32b27f 100644 --- a/kaolinite/src/document.rs +++ b/kaolinite/src/document.rs @@ -662,6 +662,38 @@ impl Document { self.cancel_selection(); } + /// Find the word boundaries + pub fn word_boundaries(&mut self, line: &str) -> Vec<(usize, usize)> { + let re = r"(\s{2,}|[A-Za-z0-9_]+|\.)"; + let mut searcher = Searcher::new(re); + let starts: Vec = searcher.lfinds(line); + let mut ends: Vec = starts.clone(); + ends.iter_mut() + .for_each(|m| m.loc.x += m.text.chars().count()); + let starts: Vec = starts.iter().map(|m| m.loc.x).collect(); + let ends: Vec = ends.iter().map(|m| m.loc.x).collect(); + starts.into_iter().zip(ends).collect() + } + + /// Find the current state of the cursor in relation to words + pub fn cursor_word_state(&mut self, words: &[(usize, usize)], x: usize) -> WordState { + let in_word = words + .iter() + .position(|(start, end)| *start <= x && x <= *end); + if let Some(idx) = in_word { + let (word_start, word_end) = words[idx]; + if x == word_end { + WordState::AtEnd(idx) + } else if x == word_start { + WordState::AtStart(idx) + } else { + WordState::InCenter(idx) + } + } else { + WordState::Out + } + } + /// Moves to the previous word in the document pub fn move_prev_word(&mut self) -> Status { let Loc { x, y } = self.char_loc(); @@ -670,27 +702,49 @@ impl Document { return Status::StartOfLine; } // Find where all the words are - let mut searcher = Searcher::new(r"(^|\s{2,}|[A-Za-z0-9_]+|\.)"); - let line = self - .line(y) - .unwrap_or_default() - .chars() - .take(x) - .collect::(); - let mut matches = searcher.rfinds(&line); - matches - .iter_mut() - .for_each(|m| m.loc.x += m.text.chars().count()); - // Find the most appropriate one to move to given the cursor position - if let Some(mtch) = matches.first() { - if mtch.loc.x == x { - matches.remove(0); + let line = self.line(y).unwrap_or_default(); + let words = self.word_boundaries(&line); + let state = self.cursor_word_state(&words, x); + // Work out where to move to + let new_x = match state { + // Go to start of line if at beginning + WordState::AtEnd(0) | WordState::InCenter(0) | WordState::AtStart(0) => 0, + // Cursor is at the middle / end of a word, move to previous end + WordState::AtEnd(idx) | WordState::InCenter(idx) => { + if let Some(word) = words.get(idx.saturating_sub(1)) { + word.1 + } else { + // No previous word exists, just go to start of line + 0 + } } - } - if let Some(mtch) = matches.first_mut() { - mtch.loc.y = self.loc().y; - self.move_to(&mtch.loc); - } + WordState::AtStart(idx) => { + // Cursor is at the start of a word, move to previous start + if let Some(word) = words.get(idx.saturating_sub(1)) { + word.0 + } else { + // No previous word exists, just go to start of line + 0 + } + } + WordState::Out => { + // Cursor is not touching any words, find previous end + let mut shift_back = x; + while let WordState::Out = self.cursor_word_state(&words, shift_back) { + shift_back = shift_back.saturating_sub(1); + if shift_back == 0 { + break; + } + } + match self.cursor_word_state(&words, shift_back) { + WordState::AtEnd(idx) => words[idx].1, + _ => 0, + } + } + }; + // Perform the move + self.move_to_x(new_x); + // Clean up self.old_cursor = self.loc().x; Status::None } @@ -704,14 +758,94 @@ impl Document { return Status::EndOfLine; } // Find and move to the next word - if let Some(mut mtch) = self.next_match(r"(\s{2,}|[A-Za-z0-9_]+|$|\.)", 0) { - mtch.loc.x += mtch.text.chars().count(); - self.move_to(&mtch.loc); - } + let line = self.line(y).unwrap_or_default(); + let words = self.word_boundaries(&line); + let state = self.cursor_word_state(&words, x); + // Work out where to move to + let new_x = match state { + // Cursor is at the middle / end of a word, move to next end + WordState::AtEnd(idx) | WordState::InCenter(idx) => { + if idx == words.len() { + line.chars().count() + } else if let Some(word) = words.get(idx + 1) { + word.1 + } else { + // No next word exists, just go to end of line + line.chars().count() + } + } + WordState::AtStart(idx) => { + // Cursor is at the start of a word, move to next start + if idx == words.len() { + line.chars().count() + } else if let Some(word) = words.get(idx + 1) { + word.0 + } else { + // No next word exists, just go to end of line + line.chars().count() + } + } + WordState::Out => { + // Cursor is not touching any words, find next start + let mut shift_forward = x; + while let WordState::Out = self.cursor_word_state(&words, shift_forward) { + shift_forward += 1; + if shift_forward == line.chars().count() { + break; + } + } + match self.cursor_word_state(&words, shift_forward) { + WordState::AtStart(idx) => words[idx].0, + _ => line.chars().count(), + } + } + }; + // Perform the move + self.move_to_x(new_x); + // Clean up self.old_cursor = self.loc().x; Status::None } + /// Function to delete a word at a certain location + /// # Errors + /// Errors if out of range + pub fn delete_word(&mut self) -> Result<()> { + let Loc { x, y } = self.char_loc(); + let line = self.line(y).unwrap_or_default(); + let words = self.word_boundaries(&line); + let state = self.cursor_word_state(&words, x); + let delete_upto = match state { + WordState::InCenter(idx) | WordState::AtEnd(idx) => { + // Delete back to start of this word + words[idx].0 + } + WordState::AtStart(idx) => { + // Delete back to end of the previous word + if let Some(word) = words.get(idx.saturating_sub(1)) { + word.1 + } else { + 0 + } + } + WordState::Out => { + // Delete back to the end of the previous word + let mut shift_back = x; + while let WordState::Out = self.cursor_word_state(&words, shift_back) { + shift_back = shift_back.saturating_sub(1); + if shift_back == 0 { + break; + } + } + match self.cursor_word_state(&words, shift_back) { + WordState::AtEnd(idx) => words[idx].1, + _ => 0, + } + } + }; + self.delete(delete_upto..=x, y) + } + /// Function to search the document to find the next occurance of a regex pub fn next_match(&mut self, regex: &str, inc: usize) -> Option { // Prepare @@ -1210,3 +1344,11 @@ pub struct Cursor { pub loc: Loc, pub selection_end: Loc, } + +/// State of a word +pub enum WordState { + AtStart(usize), + AtEnd(usize), + InCenter(usize), + Out, +} diff --git a/kaolinite/src/searching.rs b/kaolinite/src/searching.rs index 7df55d42..cd99a81a 100644 --- a/kaolinite/src/searching.rs +++ b/kaolinite/src/searching.rs @@ -4,7 +4,7 @@ use crate::utils::Loc; use regex::Regex; /// Stores information about a match in a document -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Match { pub loc: Loc, pub text: String, diff --git a/src/config/editor.rs b/src/config/editor.rs index 9eff3edf..6cf4c72e 100644 --- a/src/config/editor.rs +++ b/src/config/editor.rs @@ -135,6 +135,10 @@ impl LuaUserData for Editor { editor.plugin_active = false; Ok(()) }); + methods.add_method_mut("remove_word", |_, editor, ()| { + let _ = editor.doc_mut().delete_word(); + Ok(()) + }); // Cursor moving methods.add_method_mut("move_to", |_, editor, (x, y): (usize, usize)| { let y = y.saturating_sub(1); From 696c27ea821c5256bb137727e04a904036496bb0 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sun, 20 Oct 2024 15:28:13 +0100 Subject: [PATCH 19/27] Updated tests and removed redundant code --- kaolinite/src/document.rs | 32 ++++++--------------------- kaolinite/tests/test.rs | 46 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/kaolinite/src/document.rs b/kaolinite/src/document.rs index 3a32b27f..ea991ee5 100644 --- a/kaolinite/src/document.rs +++ b/kaolinite/src/document.rs @@ -711,21 +711,10 @@ impl Document { WordState::AtEnd(0) | WordState::InCenter(0) | WordState::AtStart(0) => 0, // Cursor is at the middle / end of a word, move to previous end WordState::AtEnd(idx) | WordState::InCenter(idx) => { - if let Some(word) = words.get(idx.saturating_sub(1)) { - word.1 - } else { - // No previous word exists, just go to start of line - 0 - } + words[idx.saturating_sub(1)].1 } WordState::AtStart(idx) => { - // Cursor is at the start of a word, move to previous start - if let Some(word) = words.get(idx.saturating_sub(1)) { - word.0 - } else { - // No previous word exists, just go to start of line - 0 - } + words[idx.saturating_sub(1)].0 } WordState::Out => { // Cursor is not touching any words, find previous end @@ -765,9 +754,7 @@ impl Document { let new_x = match state { // Cursor is at the middle / end of a word, move to next end WordState::AtEnd(idx) | WordState::InCenter(idx) => { - if idx == words.len() { - line.chars().count() - } else if let Some(word) = words.get(idx + 1) { + if let Some(word) = words.get(idx + 1) { word.1 } else { // No next word exists, just go to end of line @@ -776,9 +763,7 @@ impl Document { } WordState::AtStart(idx) => { // Cursor is at the start of a word, move to next start - if idx == words.len() { - line.chars().count() - } else if let Some(word) = words.get(idx + 1) { + if let Some(word) = words.get(idx + 1) { word.0 } else { // No next word exists, just go to end of line @@ -790,7 +775,7 @@ impl Document { let mut shift_forward = x; while let WordState::Out = self.cursor_word_state(&words, shift_forward) { shift_forward += 1; - if shift_forward == line.chars().count() { + if shift_forward >= line.chars().count() { break; } } @@ -820,13 +805,10 @@ impl Document { // Delete back to start of this word words[idx].0 } + WordState::AtStart(0) => 0, WordState::AtStart(idx) => { // Delete back to end of the previous word - if let Some(word) = words.get(idx.saturating_sub(1)) { - word.1 - } else { - 0 - } + words[idx.saturating_sub(1)].1 } WordState::Out => { // Delete back to the end of the previous word diff --git a/kaolinite/tests/test.rs b/kaolinite/tests/test.rs index 230db8e5..b9e2d534 100644 --- a/kaolinite/tests/test.rs +++ b/kaolinite/tests/test.rs @@ -369,6 +369,33 @@ fn document_deletion() { assert_eq!(doc.line(0), Some(st!("好"))); doc.exe(Event::Delete(Loc { x: 10000, y: 0 }, st!(" "))); assert_eq!(doc.line(0), Some(st!("好"))); + // Word deleting + doc.exe(Event::InsertLine(1, st!(" hello -world---"))); + doc.move_to(&Loc { x: 0, y: 1 }); + doc.delete_word(); + assert_eq!(doc.line(1).unwrap(), st!(" hello -world---")); + doc.move_to(&Loc { x: 4, y: 1 }); + doc.delete_word(); + assert_eq!(doc.line(1).unwrap(), st!("hello -world---")); + doc.move_to(&Loc { x: 4, y: 1 }); + doc.delete_word(); + assert_eq!(doc.line(1).unwrap(), st!("o -world---")); + doc.move_to(&Loc { x: 1, y: 1 }); + doc.delete_word(); + assert_eq!(doc.line(1).unwrap(), st!(" -world---")); + doc.move_to(&Loc { x: 8, y: 1 }); + doc.delete_word(); + assert_eq!(doc.line(1).unwrap(), st!(" -world--")); + doc.move_to(&Loc { x: 1, y: 1 }); + doc.delete_word(); + assert_eq!(doc.line(1).unwrap(), st!("-world--")); + doc.move_to(&Loc { x: 1, y: 1 }); + doc.delete_word(); + assert_eq!(doc.line(1).unwrap(), st!("world--")); + doc.exe(Event::InsertLine(1, st!(" hello -world---"))); + doc.move_to(&Loc { x: 11, y: 1 }); + doc.delete_word(); + assert_eq!(doc.line(1).unwrap(), st!(" helloworld---")); } #[test] @@ -545,11 +572,11 @@ fn document_moving() { doc.exe(Event::InsertLine(10, st!("these are words this.is.code()"))); doc.move_to(&Loc { x: 0, y: 10 }); doc.move_next_word(); - assert_eq!(doc.loc(), Loc { x: 5, y: 10 }); + assert_eq!(doc.loc(), Loc { x: 6, y: 10 }); doc.move_next_word(); - assert_eq!(doc.loc(), Loc { x: 9, y: 10 }); + assert_eq!(doc.loc(), Loc { x: 10, y: 10 }); doc.move_next_word(); - assert_eq!(doc.loc(), Loc { x: 15, y: 10 }); + assert_eq!(doc.loc(), Loc { x: 16, y: 10 }); doc.move_next_word(); assert_eq!(doc.loc(), Loc { x: 20, y: 10 }); doc.move_next_word(); @@ -582,6 +609,10 @@ fn document_moving() { doc.move_prev_word(); assert_eq!(doc.loc(), Loc { x: 0, y: 10 }); assert_eq!(doc.move_prev_word(), Status::StartOfLine); + doc.exe(Event::InsertLine(11, st!("----test"))); + doc.move_to(&Loc { x: 7, y: 11 }); + doc.move_next_word(); + assert_eq!(doc.loc(), Loc { x: 8, y: 11 }); } #[test] @@ -793,6 +824,15 @@ fn document_searching() { text: st!(" hello") }) ); + // General searching stuff + let mut searcher = Searcher::new("[0-9]+"); + assert_eq!( + searcher.rfinds("hello098hello765hello"), + vec![ + Match { loc: Loc { x: 13, y: 0 }, text: "765".to_string() }, + Match { loc: Loc { x: 5, y: 0 }, text: "098".to_string() }, + ], + ); } #[test] From acf6b404c8f3c6e2b6d569865a72351f8a6b2421 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sun, 20 Oct 2024 15:28:29 +0100 Subject: [PATCH 20/27] rustfmt --- kaolinite/src/document.rs | 8 ++------ kaolinite/tests/test.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/kaolinite/src/document.rs b/kaolinite/src/document.rs index ea991ee5..286d585b 100644 --- a/kaolinite/src/document.rs +++ b/kaolinite/src/document.rs @@ -710,12 +710,8 @@ impl Document { // Go to start of line if at beginning WordState::AtEnd(0) | WordState::InCenter(0) | WordState::AtStart(0) => 0, // Cursor is at the middle / end of a word, move to previous end - WordState::AtEnd(idx) | WordState::InCenter(idx) => { - words[idx.saturating_sub(1)].1 - } - WordState::AtStart(idx) => { - words[idx.saturating_sub(1)].0 - } + WordState::AtEnd(idx) | WordState::InCenter(idx) => words[idx.saturating_sub(1)].1, + WordState::AtStart(idx) => words[idx.saturating_sub(1)].0, WordState::Out => { // Cursor is not touching any words, find previous end let mut shift_back = x; diff --git a/kaolinite/tests/test.rs b/kaolinite/tests/test.rs index b9e2d534..c55aafd9 100644 --- a/kaolinite/tests/test.rs +++ b/kaolinite/tests/test.rs @@ -829,8 +829,14 @@ fn document_searching() { assert_eq!( searcher.rfinds("hello098hello765hello"), vec![ - Match { loc: Loc { x: 13, y: 0 }, text: "765".to_string() }, - Match { loc: Loc { x: 5, y: 0 }, text: "098".to_string() }, + Match { + loc: Loc { x: 13, y: 0 }, + text: "765".to_string() + }, + Match { + loc: Loc { x: 5, y: 0 }, + text: "098".to_string() + }, ], ); } From e84e98fb53d683cb63a6b9403e22e41451228ea5 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Sun, 20 Oct 2024 23:50:49 +0100 Subject: [PATCH 21/27] updated to fit new synoptic version and config assistant can enable icons in git --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- plugins/git.lua | 4 ++-- src/config/assistant.rs | 3 +++ src/editor/interface.rs | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 099fcd30..c7cabcb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -591,9 +591,9 @@ dependencies = [ [[package]] name = "synoptic" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97419cc9b1e79552ca0622b66093fc5217d9b0048415f2c8680a52be2fcd41f7" +checksum = "43bd6803459770f73e455673c24d25751b808f8ea77b9fa0df0672d0ecdce5dd" dependencies = [ "if_chain", "regex", diff --git a/Cargo.toml b/Cargo.toml index b118568f..3946d33c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,4 +41,4 @@ kaolinite = { path = "./kaolinite" } mlua = { version = "0.9.9", features = ["lua54", "vendored"] } quick-error = "2.0.1" shellexpand = "3.1.0" -synoptic = "2" +synoptic = "2.2.0" diff --git a/plugins/git.lua b/plugins/git.lua index 61daa735..2848659b 100644 --- a/plugins/git.lua +++ b/plugins/git.lua @@ -1,5 +1,5 @@ --[[ -Git v0.2 +Git v0.3 A plug-in for git integration that provides features to: - Choose which files to add to a commit @@ -12,7 +12,7 @@ A plug-in for git integration that provides features to: git = { status = {}, - icons = false, + icons = (git or { icons = false }).icons, has_git = shell:output("git --version"):find("git version"), } diff --git a/src/config/assistant.rs b/src/config/assistant.rs index 0c64b2bd..db1f8c81 100644 --- a/src/config/assistant.rs +++ b/src/config/assistant.rs @@ -765,6 +765,9 @@ impl Assistant { result += "\n-- Load Plug-Ins --\n"; for plugin in &self.plugins { result += &plugin.to_config(); + if plugin == &Plugin::Git { + result += "git = { icons = true }"; + } } // Ready to go result diff --git a/src/editor/interface.rs b/src/editor/interface.rs index b881c19c..9b39c0ca 100644 --- a/src/editor/interface.rs +++ b/src/editor/interface.rs @@ -11,7 +11,7 @@ use crossterm::{ }; use kaolinite::utils::{file_or_dir, get_cwd, get_parent, list_dir, width, Loc, Size}; use mlua::Lua; -use synoptic::{trim, Highlighter, TokOpt}; +use synoptic::{trim_fit, Highlighter, TokOpt}; use super::Editor; @@ -117,7 +117,7 @@ impl Editor { let idx = y as usize + self.doc().offset.y; if let Some(line) = self.doc().line(idx) { let tokens = self.highlighter().line(idx, &line); - let tokens = trim(&tokens, self.doc().offset.x, required_width, tab_width); + let tokens = trim_fit(&tokens, self.doc().offset.x, required_width, tab_width); let mut x_pos = self.doc().offset.x; for token in tokens { // Find out the text (and colour of that text) From 0a789da0b30d208f1c2aa8b8a72b49dd7ac6d0c5 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:05:32 +0100 Subject: [PATCH 22/27] Fixed some issues with the configuration assistant --- src/config/assistant.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/config/assistant.rs b/src/config/assistant.rs index db1f8c81..a0211b4a 100644 --- a/src/config/assistant.rs +++ b/src/config/assistant.rs @@ -75,7 +75,7 @@ Git - View and manage your git repository - requires git to be installed Pomodoro - A timer that helps you track your periods of work and breaks Todo - Makes .todo files interactive todo lists Typing Speed - Shows the rough speed that you're typing in the status line -Update Notification - Warns you if there is a new version of Ox - requires curl to be installed on unix based systems\n +Update Notification - Warns you if there is a new version of Ox - requires curl to be installed on unix based systems "; const FINAL_WORDS: &str = r" @@ -106,7 +106,6 @@ pub enum Theme { Galaxy, Transparent, Default, - //Custom(HashMap), } impl Theme { @@ -684,7 +683,7 @@ impl Assistant { } } // Configuration of line numbers - if sections.contains(&"line_number") { + if sections.contains(&"line_numbers") { result += "\n-- Line Number Configuration --\n"; if fields.contains(&"line_numbers") { result += &format!("line_numbers.enabled = {}\n", self.line_numbers); From 8b5b9d366b534e00095149232e5fb0b123a6b7b7 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:17:06 +0100 Subject: [PATCH 23/27] Changed around delete word command and changed galaxy theme slightly --- kaolinite/src/document.rs | 11 +++++++---- src/themes/galaxy.lua | 2 +- test.txt | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 test.txt diff --git a/kaolinite/src/document.rs b/kaolinite/src/document.rs index 286d585b..4d550758 100644 --- a/kaolinite/src/document.rs +++ b/kaolinite/src/document.rs @@ -803,8 +803,8 @@ impl Document { } WordState::AtStart(0) => 0, WordState::AtStart(idx) => { - // Delete back to end of the previous word - words[idx.saturating_sub(1)].1 + // Delete back to start of the previous word + words[idx.saturating_sub(1)].0 } WordState::Out => { // Delete back to the end of the previous word @@ -815,8 +815,11 @@ impl Document { break; } } - match self.cursor_word_state(&words, shift_back) { - WordState::AtEnd(idx) => words[idx].1, + match (line.chars().nth(shift_back), self.cursor_word_state(&words, shift_back)) { + // Shift to start of previous word if there is a space + (Some(' '), WordState::AtEnd(idx)) => words[idx].0, + // Shift to end of previous word if there is not a space + (_, WordState::AtEnd(idx)) => words[idx].1, _ => 0, } } diff --git a/src/themes/galaxy.lua b/src/themes/galaxy.lua index 40f39ea3..b9540372 100644 --- a/src/themes/galaxy.lua +++ b/src/themes/galaxy.lua @@ -32,7 +32,7 @@ colors.tab_active_bg = grey2 colors.tab_active_fg = purple colors.info_bg = black -colors.info_fg = lightblue +colors.info_fg = darkblue colors.warning_bg = black colors.warning_fg = yellow colors.error_bg = black diff --git a/test.txt b/test.txt new file mode 100644 index 00000000..4f4eb59d --- /dev/null +++ b/test.txt @@ -0,0 +1,2 @@ +hello world my name is luke +this::is::code From 1a3f11b63144b4b3106b094fe573b41bd741c005 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:21:03 +0100 Subject: [PATCH 24/27] Updated tests and rustfmt --- kaolinite/src/document.rs | 4 +++- kaolinite/tests/test.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/kaolinite/src/document.rs b/kaolinite/src/document.rs index 4d550758..2ac2ae7d 100644 --- a/kaolinite/src/document.rs +++ b/kaolinite/src/document.rs @@ -815,7 +815,9 @@ impl Document { break; } } - match (line.chars().nth(shift_back), self.cursor_word_state(&words, shift_back)) { + let char = line.chars().nth(shift_back); + let state = self.cursor_word_state(&words, shift_back); + match (char, state) { // Shift to start of previous word if there is a space (Some(' '), WordState::AtEnd(idx)) => words[idx].0, // Shift to end of previous word if there is not a space diff --git a/kaolinite/tests/test.rs b/kaolinite/tests/test.rs index c55aafd9..83b316f2 100644 --- a/kaolinite/tests/test.rs +++ b/kaolinite/tests/test.rs @@ -395,7 +395,7 @@ fn document_deletion() { doc.exe(Event::InsertLine(1, st!(" hello -world---"))); doc.move_to(&Loc { x: 11, y: 1 }); doc.delete_word(); - assert_eq!(doc.line(1).unwrap(), st!(" helloworld---")); + assert_eq!(doc.line(1).unwrap(), st!(" world---")); } #[test] From bd7cd1cd444fefa943caede30eae543d9c42f827 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:01:01 +0100 Subject: [PATCH 25/27] Fixed python syntax highlighting --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- test.txt | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 test.txt diff --git a/Cargo.lock b/Cargo.lock index c7cabcb6..b97821ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -591,9 +591,9 @@ dependencies = [ [[package]] name = "synoptic" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bd6803459770f73e455673c24d25751b808f8ea77b9fa0df0672d0ecdce5dd" +checksum = "b0ed930df773aaac411d90bbf1e20b1538514c981f0dc1231f5feea1ef2910f0" dependencies = [ "if_chain", "regex", diff --git a/Cargo.toml b/Cargo.toml index 3946d33c..775bc1d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,4 +41,4 @@ kaolinite = { path = "./kaolinite" } mlua = { version = "0.9.9", features = ["lua54", "vendored"] } quick-error = "2.0.1" shellexpand = "3.1.0" -synoptic = "2.2.0" +synoptic = "2.2.1" diff --git a/test.txt b/test.txt deleted file mode 100644 index 4f4eb59d..00000000 --- a/test.txt +++ /dev/null @@ -1,2 +0,0 @@ -hello world my name is luke -this::is::code From 6d3b4d17afdb5b1b78f07878cce547e5e73e04cb Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Tue, 22 Oct 2024 21:18:07 +0100 Subject: [PATCH 26/27] Fixed rendering issues in greeting message and changed its position --- src/editor/interface.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/editor/interface.rs b/src/editor/interface.rs index 9b39c0ca..c44b29f8 100644 --- a/src/editor/interface.rs +++ b/src/editor/interface.rs @@ -75,7 +75,8 @@ impl Editor { } }) .collect::>(); - let start = u16::try_from(h / 4).unwrap_or(u16::MAX); + let first_line = (h / 2).saturating_sub(message.len() / 2) + 1; + let start = u16::try_from(first_line).unwrap_or(u16::MAX); let end = start + u16::try_from(message.len()).unwrap_or(u16::MAX); // Render each line of the document for y in 0..u16::try_from(h).unwrap_or(0) { @@ -160,7 +161,10 @@ impl Editor { // Render help message if applicable (otherwise, just output padding to clear buffer) if self.config.help_message.borrow().enabled && (start..=end).contains(&y) { let idx = y.saturating_sub(start); - display!(self, message.get(idx as usize).unwrap_or(&String::new())); + let line = message + .get(idx as usize) + .map_or(" ".repeat(max_width), |s| s.to_string()); + display!(self, line, " ".repeat(max_width)); } } Ok(()) From 6bdf62a90436589740f95862432190a82b672be4 Mon Sep 17 00:00:00 2001 From: Luke <11898833+curlpipe@users.noreply.github.com> Date: Tue, 22 Oct 2024 21:53:25 +0100 Subject: [PATCH 27/27] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d6c85164..566ecc66 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ It is mainly used on linux systems, but macOS and Windows users (via WSL) are fr - :electric_plug: Plug-In system where you can write your own plug-ins or integrate other people's - :wrench: A wide number of options for configuration with everything from colours to the status line to syntax highlighting being open to customisation - :moon: Ox uses Lua as a configuration language for familiarity when scripting and configuring +- 🤝 A configuration assistant to quickly get Ox set up for you from the get-go ### Out of the box features