diff options
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | manifest.json | 90 | ||||
-rw-r--r-- | plugins/colorpicker.lua | 56 | ||||
-rw-r--r-- | plugins/custom_caret.lua | 73 | ||||
-rw-r--r-- | plugins/language_rivet.lua | 34 | ||||
-rw-r--r-- | plugins/minimap.lua | 33 | ||||
-rw-r--r-- | plugins/search_ui.lua | 979 | ||||
-rw-r--r-- | plugins/settings.lua | 41 |
8 files changed, 1186 insertions, 127 deletions
@@ -48,13 +48,15 @@ but only with a `url` must provide a `checksum` that matches the existing plugin | [`bigclock`](plugins/bigclock.lua?raw=1) | Shows the current time and date in a view with large text *([screenshot](https://user-images.githubusercontent.com/3920290/82752891-3318df00-9db9-11ea-803f-261d80d5cf53.png))* | | [`black`](https://git.sr.ht/~tmpod/black-lite)\* | Integrates the [black](https://github.com/psf/black) Python formatter with lite | | [`bracketmatch`](plugins/bracketmatch.lua?raw=1) | Underlines matching pair for bracket under the caret *([screenshot](https://user-images.githubusercontent.com/3920290/80132745-0c863f00-8594-11ea-8875-c455c6fd7eae.png))* | +| [`build`](https://github.com/adamharrison/lite-xl-ide.git)\* | Provides a build system, messages window, and easily clickable errors. Supports an internal build system, and `make`. *([screenshot](https://raw.githubusercontent.com/adamharrison/lite-xl-ide/main/screenshots/build.png))* | | [`centerdoc`](plugins/centerdoc.lua?raw=1) | Centers document's content on the screen and adds zen mode support *([screenshot](https://user-images.githubusercontent.com/3920290/82127896-bf6e4500-97ae-11ea-97fc-ba9a552bc9a4.png))* | +| [`colorpicker`](plugins/colorpicker.lua?raw=1) | Color picker dialog that supports html and rgb notations. | | [`colorpreview`](plugins/colorpreview.lua?raw=1) | Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their resultant color. *([screenshot](https://user-images.githubusercontent.com/3920290/80743752-731bd780-8b15-11ea-97d3-847db927c5dc.png))* | | [`console`](https://github.com/franko/console)\* | A console for running external commands and capturing their output *([gif](https://user-images.githubusercontent.com/3920290/81343656-49325a00-90ad-11ea-8647-ff39d8f1d730.gif))* | -| [`contextmenu`](https://github.com/takase1121/lite-contextmenu)\* | Simple context menu *([screenshot](https://github.com/takase1121/lite-contextmenu/blob/master/assets/screenshot.jpg?raw=true))* | | [`copyfilelocation`](plugins/copyfilelocation.lua?raw=1) | Copy file location to clipboard | | [`custom_caret`](plugins/custom_caret.lua?raw=1) | Customize the caret in the editor | | [`datetimestamps`](plugins/datetimestamps.lua?raw=1) | Insert date-, time- and date-time-stamps | +| [`debugger`](https://github.com/adamharrison/lite-xl-ide.git)\* | Provides a debugger integration, with pluggable backends. Currently supports only gdb. *([screenshot](https://raw.githubusercontent.com/adamharrison/lite-xl-ide/main/screenshots/debugger.png))* | | [`discord-presence`](https://github.com/vincens2005/lite-xl-discord)\* | Adds the current workspace and file to your Discord Rich Presence | | [`dragdropselected`](plugins/dragdropselected.lua?raw=1) | Provides basic drag and drop of selected text (in same document) | | [`editorconfig`](plugins/editorconfig) | [EditorConfig](https://editorconfig.org/) implementation for Lite XL | @@ -178,18 +180,21 @@ but only with a `url` must provide a `checksum` that matches the existing plugin | [`openfilelocation`](plugins/openfilelocation.lua?raw=1) | Opens the parent directory of the current file in the file manager | | [`openselected`](plugins/openselected.lua?raw=1) | Opens the selected filename or url | | [`pdfview`](plugins/pdfview.lua?raw=1) | PDF preview for TeX files | +| [`plugin_manager`](https://github.com/lite-xl/lite-xl-plugin-manager.git)\* | A plugin manager view for lite-xl that provides GUI access to `lpm` | | [`primary_selection`](plugins/primary_selection.lua?raw=1) | Adds middle mouse click copy/paste (primary selection). To use this plugin, `xclip` must be installed. | | [`profiler`](plugins/profiler) | Adds the ability to profile lite-xl with the [lua-profiler](https://github.com/charlesmallah/lua-profiler) | | [`rainbowparen`](plugins/rainbowparen.lua?raw=1) | Show nesting of parentheses with rainbow colours | | [`regexreplacepreview`](plugins/regexreplacepreview.lua?raw=1) | Allows for you to write a regex and its replacement in one go, and live preview the results. | | [`restoretabs`](plugins/restoretabs.lua?raw=1) | Keep a list of recently closed tabs, and restore the tab in order on ctrl+shift+t. | | [`scalestatus`](plugins/scalestatus.lua?raw=1) | Displays current scale (zoom) in status view (depends on scale plugin) | +| [`search_ui`](plugins/search_ui.lua?raw=1) | Friendlier search and replace user interface using Widgets. | | [`select_colorscheme`](plugins/select_colorscheme.lua?raw=1) | Select a color theme, like VScode, Sublime Text.(plugin saves changes) | | [`selectionhighlight`](plugins/selectionhighlight.lua?raw=1) | Highlights regions of code that match the current selection *([screenshot](https://user-images.githubusercontent.com/3920290/80710883-5f597c80-8ae7-11ea-97f0-76dfacc08439.png))* | | [`settings`](plugins/settings.lua?raw=1) | Provides a GUI to manage core and plugin settings, bindings and select color theme *([video](https://user-images.githubusercontent.com/1702572/169743674-ececae24-f6b7-4ff2-bfa2-c4762cd327d9.mp4))*. (depends on [`widget`](https://github.com/lite-xl/lite-xl-widgets)) | | [`smallclock`](plugins/smallclock.lua?raw=1) | Displays the current time in the corner of the status view | | [`smoothcaret`](plugins/smoothcaret.lua?raw=1) | Smooth caret animation *([gif](https://user-images.githubusercontent.com/20792268/139006049-a0ba6559-88cb-49a7-8077-4822445b4a1f.gif))* | | [`sort`](plugins/sort.lua?raw=1) | Sorts selected lines alphabetically | +| [`Source Control Management`](https://github.com/lite-xl/lite-xl-scm)\* | Extensible source control management plugin with git and fossil backends. | | [`spellcheck`](plugins/spellcheck.lua?raw=1) | Underlines misspelt words *([screenshot](https://user-images.githubusercontent.com/3920290/79923973-9caa7400-842e-11ea-85d4-7a196a91ca50.png))* *-- note: on Windows a [`words.txt`](https://github.com/dwyl/english-words/blob/master/words.txt) dictionary file must be placed beside the exe* | | [`statusclock`](plugins/statusclock.lua?raw=1) | Displays the current date and time in the corner of the status view | | [`tab_switcher`](plugins/tab_switcher.lua?raw=1) | Switch between open tabs by searching by name | diff --git a/manifest.json b/manifest.json index d27e626..e9a843d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { - "plugins": [ + "addons": [ { "description": "Align multiple carets and selections *([clip](https://user-images.githubusercontent.com/2798487/165631951-532f8d24-d596-4dd0-9d21-ff53c71ed32f.mp4))*", "version": "0.1", @@ -57,6 +57,13 @@ "mod_version": "3" }, { + "description": "Provides a build system, messages window, and easily clickable errors. Supports an internal build system, and `make`. *([screenshot](https://raw.githubusercontent.com/adamharrison/lite-xl-ide/main/screenshots/build.png))*", + "version": "0.1", + "remote": "https://github.com/adamharrison/lite-xl-ide.git:c254d8cbc1932fd69e4c135f1d53c4e81a9f293a", + "id": "build", + "mod_version": "3" + }, + { "description": "Centers document's content on the screen and adds zen mode support *([screenshot](https://user-images.githubusercontent.com/3920290/82127896-bf6e4500-97ae-11ea-97fc-ba9a552bc9a4.png))*", "version": "0.1", "path": "plugins/centerdoc.lua", @@ -64,6 +71,14 @@ "mod_version": "3" }, { + "description": "Color picker dialog that supports html and rgb notations.", + "version": "0.1", + "path": "plugins/colorpicker.lua", + "id": "colorpicker", + "mod_version": "3", + "dependencies": { "widget": {} } + }, + { "description": "Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their resultant color. *([screenshot](https://user-images.githubusercontent.com/3920290/80743752-731bd780-8b15-11ea-97d3-847db927c5dc.png))*", "version": "0.1", "path": "plugins/colorpreview.lua", @@ -78,13 +93,6 @@ "id": "console" }, { - "description": "Simple context menu *([screenshot](https://github.com/takase1121/lite-contextmenu/blob/master/assets/screenshot.jpg?raw=true))*", - "version": "0.1", - "remote": "https://github.com/takase1121/lite-contextmenu:5af041bf27319c6c26316c7fc8b7a60494be7d32", - "mod_version": "3", - "id": "contextmenu" - }, - { "description": "Copy file location to clipboard", "version": "0.1", "path": "plugins/copyfilelocation.lua", @@ -93,7 +101,7 @@ }, { "description": "Customize the caret in the editor", - "version": "0.1", + "version": "0.2", "path": "plugins/custom_caret.lua", "id": "custom_caret", "mod_version": "3" @@ -106,6 +114,13 @@ "mod_version": "3" }, { + "description": "Provides a debugger integration, with pluggable backends. Currently supports only gdb. *([screenshot](https://raw.githubusercontent.com/adamharrison/lite-xl-ide/main/screenshots/debugger.png))*", + "version": "0.1", + "remote": "https://github.com/adamharrison/lite-xl-ide.git:c254d8cbc1932fd69e4c135f1d53c4e81a9f293a", + "id": "debugger", + "mod_version": "3" + }, + { "description": "Adds the current workspace and file to your Discord Rich Presence", "version": "0.1", "remote": "https://github.com/vincens2005/lite-xl-discord:93ac3abb7381fe6d5c9734e40c008cd26713f1a8", @@ -130,14 +145,14 @@ "description": "Add support for detecting file and string encodings as converting between them.", "version": "1.1", "type": "library", - "remote": "https://github.com/jgmdev/lite-xl-encoding:b1ddf226277ea12a03ed9db2ddda458988020e91", + "remote": "https://github.com/jgmdev/lite-xl-encoding:16e2477e916f52e18f6d63f5ac61ace58b0c5e45", "mod_version": "3", "id": "encoding" }, { "description": "Properly read files that are not encoded in UTF-8 or ASCII by auto-detecting their encoding and allows saving on different text encodings.", "version": "1.0", - "remote": "https://github.com/jgmdev/lite-xl-encoding:b1ddf226277ea12a03ed9db2ddda458988020e91", + "remote": "https://github.com/jgmdev/lite-xl-encoding:16e2477e916f52e18f6d63f5ac61ace58b0c5e45", "mod_version": "3", "id": "encodings", "dependencies": { "encoding": { } } @@ -210,7 +225,7 @@ "name": "Multithreaded Find File", "description": "Threaded project find files.", "version": "1.0", - "remote": "https://github.com/jgmdev/lite-xl-threads:e61ffd28fc852b143fe468c4b43c68d605f22335", + "remote": "https://github.com/jgmdev/lite-xl-threads:9299a9a6b778cb34b12f0286b9162779920a9197", "mod_version": "3", "id": "findfileimproved", "dependencies": { "thread": { } } @@ -846,8 +861,8 @@ }, { "description": "Advanced linter with ErrorLens-like error reporting. Compatible with linters made for `linter` *([screenshot](https://raw.githubusercontent.com/liquid600pgm/lintplus/master/screenshots/1.png))*", - "version": "0.1", - "remote": "https://github.com/liquid600pgm/lintplus:3268641818069070b270486a88966b2a8bfef97e", + "version": "0.2", + "remote": "https://github.com/liquid600pgm/lintplus:771b1fe6cddb7897cd034ed5ee96201d6a2831c2", "mod_version": "3", "id": "lintplus", "name": "lint+" @@ -875,8 +890,8 @@ }, { "description": "Provides code completion (also known as IntelliSense) using the Language Server Protocol", - "version": "0.1", - "remote": "https://github.com/lite-xl/lite-xl-lsp:a6a8f70d6304bd77c7588e0a652945002df7fbad", + "version": "0.2", + "remote": "https://github.com/lite-xl/lite-xl-lsp:dc37d18c91d3243f9d7530364d8c24a3da8446fa", "mod_version": "3", "id": "lsp" }, @@ -910,7 +925,7 @@ }, { "description": "Shows a minimap on the right-hand side of the docview. Taken from [@andsve](https://github.com/andsve/lite-plugins/tree/minimap-plugin), and improved upon.", - "version": "0.1", + "version": "0.2", "path": "plugins/minimap.lua", "id": "minimap", "mod_version": "3" @@ -933,7 +948,7 @@ "description": "Add support for TCP and UDP sockets using SDL_net.", "version": "1.1", "type": "library", - "remote": "https://github.com/jgmdev/lite-xl-net:a1930395c89e24344db686f2e83ce67a602c5dbf", + "remote": "https://github.com/jgmdev/lite-xl-net:4ddece50cdc6d00ab09be1896ef0474e89da89b8", "mod_version": "3", "id": "net" }, @@ -980,6 +995,13 @@ "mod_version": "3" }, { + "description": "A plugin manager view for lite-xl that provides GUI access to `lpm`", + "version": "0.1", + "remote": "https://github.com/lite-xl/lite-xl-plugin-manager.git:3e5d5b0827058f2eeddfb166d8128fc086a87e28", + "id": "plugin_manager", + "mod_version": "3" + }, + { "description": "Adds middle mouse click copy/paste (primary selection). To use this plugin, `xclip` must be installed.", "version": "0.1", "path": "plugins/primary_selection.lua", @@ -997,7 +1019,7 @@ "name": "Multithreaded Project Search", "description": "Threaded project search with 5-10x better performance.", "version": "1.2", - "remote": "https://github.com/jgmdev/lite-xl-threads:e61ffd28fc852b143fe468c4b43c68d605f22335", + "remote": "https://github.com/jgmdev/lite-xl-threads:9299a9a6b778cb34b12f0286b9162779920a9197", "mod_version": "3", "id": "projectsearch", "dependencies": { "thread": { } } @@ -1031,6 +1053,23 @@ "mod_version": "3" }, { + "name": "Source Control Management", + "description": "Extensible source control management plugin with git and fossil backends.", + "version": "0.1", + "remote": "https://github.com/lite-xl/lite-xl-scm:930951990f9a3c78178265e5380e3c9e40b109d2", + "id": "scm", + "mod_version": "3", + "dependencies": { "widget": {} } + }, + { + "description": "Friendlier search and replace user interface using Widgets.", + "version": "0.1", + "path": "plugins/search_ui.lua", + "id": "search_ui", + "mod_version": "3", + "dependencies": { "widget": {} } + }, + { "description": "Select a color theme, like VScode, Sublime Text.(plugin saves changes)", "version": "0.1", "path": "plugins/select_colorscheme.lua", @@ -1046,10 +1085,11 @@ }, { "description": "Provides a GUI to manage core and plugin settings, bindings and select color theme *([video](https://user-images.githubusercontent.com/1702572/169743674-ececae24-f6b7-4ff2-bfa2-c4762cd327d9.mp4))*. (depends on [`widget`](https://github.com/lite-xl/lite-xl-widgets))", - "version": "0.3", + "version": "0.5", "path": "plugins/settings.lua", "id": "settings", - "mod_version": "3" + "mod_version": "3", + "dependencies": { "widget": {} } }, { "description": "Displays the current time in the corner of the status view", @@ -1148,9 +1188,9 @@ { "name": "Threads", "description": "Adds the missing multithreading functionality.", - "version": "1.1", + "version": "1.3", "type": "library", - "remote": "https://github.com/jgmdev/lite-xl-threads:e61ffd28fc852b143fe468c4b43c68d605f22335", + "remote": "https://github.com/jgmdev/lite-xl-threads:9299a9a6b778cb34b12f0286b9162779920a9197", "mod_version": "3", "id": "thread" }, @@ -1198,8 +1238,8 @@ }, { "description": "Plugin library that provides a set of re-usable components to more easily write UI elements for your plugins", - "version": "0.1", - "remote": "https://github.com/lite-xl/lite-xl-widgets:a632bfdf7c66bacc272fe2c962621cd9860058e1", + "version": "0.2", + "remote": "https://github.com/lite-xl/lite-xl-widgets:4c29ff3f89fb2988a7169094a554fee7972c9803", "mod_version": "3", "id": "widget" }, diff --git a/plugins/colorpicker.lua b/plugins/colorpicker.lua new file mode 100644 index 0000000..c280fb6 --- /dev/null +++ b/plugins/colorpicker.lua @@ -0,0 +1,56 @@ +-- mod-version:3 +local command = require "core.command" +local keymap = require "core.keymap" +local ColorPickerDialog = require "libraries.widget.colorpickerdialog" + +---Get the color format of given text. +---@param text string +---@return "html" | "html_opacity" | "rgb" +local function get_color_type(text) + local found = text:find("#%x%x%x%x%x%x%x?%x?") + if found then + found = text:find("#%x%x%x%x%x%x%x%x") + if found then return "html_opacity" end + return "html" + else + found = text:find("#%x%x%x") + if found then + return "html" + else + found = text:find( + "rgba?%((%d+)%D+(%d+)%D+(%d+)[%s,]-([%.%d]-)%s-%)" + ) + if found then return "rgb" end + end + end + return "html" +end + +command.add("core.docview!", { + ["color-picker:open"] = function(dv) + ---@type core.doc + local doc = dv.doc + local selection = doc:get_text(doc:get_selection()) + local type = get_color_type(selection) + + ---@type widget.colorpickerdialog + local picker = ColorPickerDialog(nil, selection) + function picker:on_apply(c) + local value + if type == "html" then + value = string.format("#%02X%02X%02X", c[1], c[2], c[3]) + elseif type == "html_opacity" then + value = string.format("#%02X%02X%02X%02X", c[1], c[2], c[3], c[4]) + elseif type == "rgb" then + value = string.format("rgba(%d, %d, %d, %.2f)", c[1], c[2], c[3], c[4]/255) + end + doc:text_input(value) + end + picker:show() + picker:centered() + end, +}) + +keymap.add { + ["ctrl+alt+k"] = "color-picker:open" +} diff --git a/plugins/custom_caret.lua b/plugins/custom_caret.lua index f0358cd..a8d0601 100644 --- a/plugins/custom_caret.lua +++ b/plugins/custom_caret.lua @@ -29,10 +29,7 @@ local DocView = require "core.docview" config.plugins.custom_caret = common.merge({ shape = "line", custom_color = true, - color_r = style.caret[1], - color_g = style.caret[2], - color_b = style.caret[3], - opacity = style.caret[4] + caret_color = table.pack(table.unpack(style.caret)) }, config.plugins.custom_caret) -- Reference to plugin config @@ -41,20 +38,17 @@ local conf = config.plugins.custom_caret -- Get real default caret color after everything is loaded up core.add_thread(function() if - conf.color_r == 147 and conf.color_g == 221 + conf.caret_color[1] == 147 and conf.caret_color[2] == 221 and - conf.color_b == 250 and conf.opacity == 255 + conf.caret_color[3] == 250 and conf.caret_color[4] == 255 and ( - style.caret[1] ~= conf.color_r or style.caret[2] ~= conf.color_g + style.caret[1] ~= conf.caret_color[1] or style.caret[2] ~= conf.caret_color[2] or - style.caret[3] ~= conf.color_b or style.caret[4] ~= conf.opacity + style.caret[3] ~= conf.caret_color[3] or style.caret[4] ~= conf.caret_color[4] ) then - conf.color_r = style.caret[1] - conf.color_g = style.caret[2] - conf.color_b = style.caret[3] - conf.opacity = style.caret[4] + conf.caret_color = table.pack(table.unpack(style.caret)) end local settings_loaded, settings = pcall(require, "plugins.settings") @@ -81,48 +75,12 @@ core.add_thread(function() default = true }, { - label = "Red Component of Color", - description = "The color consists of 3 components RGB, " - .. "This modifies the 'R' component of the caret's color", - path = "color_r", - type = "number", - min = 0, - max = 255, - default = style.caret[1], - step = 1, - }, - { - label = "Green Component of Color", - description = "The color consists of 3 components RGB, " - .. "This modifies the 'G' component of the caret's color", - path = "color_g", - type = "number", - min = 0, - max = 255, - default = style.caret[2], - step = 1, - }, - { - label = "Blue Component of Color", - description = "The color consists of 3 components RGB, " - .. "This modifies the 'B' component of the caret's color", - path = "color_b", - type = "number", - min = 0, - max = 255, - default = style.caret[3], - step = 1, - }, - { - label = "Opacity of the Cursor", - description = "The Opacity of the caret", - path = "opacity", - type = "number", - min = 0, - max = 255, - default = style.caret[4], - step = 1, - }, + label = "Caret Color", + description = "Custom color of the caret.", + path = "caret_color", + type = "color", + default = table.pack(table.unpack(style.caret)), + } } ---@cast settings plugins.settings @@ -134,12 +92,7 @@ function DocView:draw_caret(x, y) local caret_width = style.caret_width local caret_height = self:get_line_height() local current_caret_shape = conf.shape - local caret_color = conf.custom_color and { - conf.color_r, - conf.color_g, - conf.color_b, - conf.opacity - } or style.caret + local caret_color = conf.custom_color and conf.caret_color or style.caret if (current_caret_shape == "block") then caret_width = math.ceil(self:get_font():get_width("a")) diff --git a/plugins/language_rivet.lua b/plugins/language_rivet.lua index aceb5a1..cbd6bde 100644 --- a/plugins/language_rivet.lua +++ b/plugins/language_rivet.lua @@ -35,6 +35,7 @@ syntax.add { {pattern = "#%s?[%a_][%w_]*", type = "comment"} -- if/elif/else/endif }, symbols = { + ["alias"] = "keyword", ["and"] = "keyword", ["as"] = "keyword", ["base"] = "literal", @@ -51,19 +52,17 @@ syntax.add { ["extern"] = "keyword", ["export"] = "keyword", ["false"] = "literal", - ["fn"] = "keyword", + ["func"] = "keyword", ["for"] = "keyword", ["from"] = "keyword", ["if"] = "keyword", ["import"] = "keyword", ["in"] = "keyword", ["is"] = "keyword", - ["let"] = "keyword", ["mut"] = "keyword", ["nil"] = "literal", ["or"] = "keyword", - ["pub"] = "keyword", - ["prot"] = "keyword", + ["public"] = "keyword", ["return"] = "keyword", ["self"] = "literal", ["struct"] = "keyword", @@ -71,26 +70,29 @@ syntax.add { ["test"] = "keyword", ["trait"] = "keyword", ["true"] = "literal", - ["type"] = "keyword", ["unsafe"] = "keyword", + ["var"] = "keyword", ["while"] = "keyword", -- types ["never"] = "keyword2", - ["void"] = "keyword2", ["bool"] = "keyword2", - ["i8"] = "keyword2", - ["i16"] = "keyword2", - ["i32"] = "keyword2", - ["i64"] = "keyword2", + ["comptime_int"] = "keyword2", + ["comptime_float"] = "keyword2", + ["int8"] = "keyword2", + ["int16"] = "keyword2", + ["int32"] = "keyword2", + ["int64"] = "keyword2", ["isize"] = "keyword2", - ["u8"] = "keyword2", - ["u16"] = "keyword2", - ["u32"] = "keyword2", - ["u64"] = "keyword2", + ["uint8"] = "keyword2", + ["uint16"] = "keyword2", + ["uint32"] = "keyword2", + ["uint64"] = "keyword2", ["usize"] = "keyword2", - ["f32"] = "keyword2", - ["f64"] = "keyword2", + ["float32"] = "keyword2", + ["float64"] = "keyword2", + ["anyptr"] = "keyword2", + ["mut_anyptr"] = "keyword2", ["rune"] = "keyword2", ["string"] = "keyword2", ["Base"] = "keyword2", diff --git a/plugins/minimap.lua b/plugins/minimap.lua index 75eb272..ac94849 100644 --- a/plugins/minimap.lua +++ b/plugins/minimap.lua @@ -135,29 +135,21 @@ config.plugins.minimap = common.merge({ }, { label = "Selection Color", - description = "Background color of selected text in html notation eg: #FFFFFF. Leave empty to use default.", - path = "selection_color_html", - type = "string", - on_apply = function(value) - if value and value:match("#%x%x%x%x%x%x") then - config.plugins.minimap.selection_color = { common.color(value) } - else - config.plugins.minimap.selection_color = nil - end - end + description = "Background color of selected text.", + path = "selection_color", + type = "color", + default = string.format("#%02X%02X%02X%02X", + style.dim[1], style.dim[2], style.dim[3], style.dim[4] + ) }, { label = "Caret Color", - description = "Background color of active line in html notation eg: #FFFFFF. Leave empty to use default.", - path = "caret_color_html", - type = "string", - on_apply = function(value) - if value and value:match("#%x%x%x%x%x%x") then - config.plugins.minimap.caret_color = { common.color(value) } - else - config.plugins.minimap.caret_color = nil - end - end + description = "Background color of active line.", + path = "caret_color", + type = "color", + default = string.format("#%02X%02X%02X%02X", + style.caret[1], style.caret[2], style.caret[3], style.caret[4] + ) }, { label = "Highlight Alignment", @@ -663,4 +655,3 @@ command.add("core.docview!", { }) return MiniMap - diff --git a/plugins/search_ui.lua b/plugins/search_ui.lua new file mode 100644 index 0000000..e030184 --- /dev/null +++ b/plugins/search_ui.lua @@ -0,0 +1,979 @@ +-- mod-version:3 +-- +-- Replacement for the find/replace and project search CommandView +---interface using Widgets with some extra features. +-- @copyright Jefferson Gonzalez <jgmdev@gmail.com> +-- @license MIT +-- +local core = require "core" +local config = require "core.config" +local common = require "core.common" +local command = require "core.command" +local keymap = require "core.keymap" +local search = require "core.doc.search" +local projectsearch = require "plugins.projectsearch" +local CommandView = require "core.commandview" +local DocView = require "core.docview" +local Widget = require "libraries.widget" +local Button = require "libraries.widget.button" +local CheckBox = require "libraries.widget.checkbox" +local Line = require "libraries.widget.line" +local Label = require "libraries.widget.label" +local TextBox = require "libraries.widget.textbox" +local SelectBox = require "libraries.widget.selectbox" +local FilePicker = require "libraries.widget.filepicker" + +---@class config.plugins.search_ui +---@field replace_core_find boolean +---@field position "right" | "bottom" +config.plugins.search_ui = common.merge({ + replace_core_find = true, + position = "bottom", + config_spec = { + name = "Search User Interface", + { + label = "Replace Core Find", + description = "Replaces the core find view when using the find shortcut.", + path = "replace_core_find", + type = "toggle", + default = true + }, + { + label = "Position", + description = "Location of search interface.", + path = "position", + type = "selection", + default = "bottom", + values = { + { "Top", "top" }, + { "Right", "right" }, + { "Bottom", "bottom" } + } + } + } +}, config.plugins.search_ui) + +---@type core.docview +local doc_view = nil + +---@type widget +local widget = Widget(nil, false) +widget.name = "Search and Replace" +widget:set_border_width(0) +widget.scrollable = true +widget:hide() +widget.init_size = true + +---@type widget.label +local label = Label(widget, "Find and Replace") +label:set_position(10, 10) + +---@type widget.line +local line = Line(widget) +line:set_position(0, label:get_bottom() + 10) + +---@type widget.textbox +local findtext = TextBox(widget, "", "search...") +findtext:set_position(10, line:get_bottom() + 10) +findtext:set_tooltip("Text to search") + +---@type widget.textbox +local replacetext = TextBox(widget, "", "replacement...") +replacetext:set_position(10, findtext:get_bottom() + 10) +replacetext:set_tooltip("Text to replace") + +---@type widget.button +local findprev = Button(widget, "") +findprev:set_icon("<") +findprev:set_position(10, replacetext:get_bottom() + 10) +findprev:set_tooltip("Find previous") + +---@type widget.button +local findnext = Button(widget, "") +findnext:set_icon(">") +findnext:set_position(findprev:get_right() + 5, replacetext:get_bottom() + 10) +findnext:set_tooltip("Find next") + +---@type widget.button +local findproject = Button(widget, "Find") +findproject:set_icon("L") +findproject:set_position(findprev:get_right() + 5, replacetext:get_bottom() + 10) +findproject:set_tooltip("Find in project") +findproject:hide() + +---@type widget.button +local replace = Button(widget, "Replace") +replace:set_position(10, findnext:get_bottom() + 10) +replace:set_tooltip("Replace all matching results") + +---@type widget.line +local line_options = Line(widget) +line_options:set_position(0, replace:get_bottom() + 10) + +---@type widget.checkbox +local insensitive = CheckBox(widget, "Insensitive") +insensitive:set_position(10, line_options:get_bottom() + 10) +insensitive:set_tooltip("Case insensitive search") +insensitive:set_checked(true) + +---@type widget.checkbox +local patterncheck = CheckBox(widget, "Pattern") +patterncheck:set_position(10, insensitive:get_bottom() + 10) +patterncheck:set_tooltip("Treat search text as a lua pattern") + +---@type widget.checkbox +local regexcheck = CheckBox(widget, "Regex") +regexcheck:set_position(10, patterncheck:get_bottom() + 10) +regexcheck:set_tooltip("Treat search text as a regular expression") + +---@type widget.checkbox +local replaceinselection = CheckBox(widget, "Replace in Selection") +replaceinselection:set_position(10, regexcheck:get_bottom() + 10) +replaceinselection:set_tooltip("Perform replace only on selected text") + +---@type widget.selectbox +local scope = SelectBox(widget, "scope") +scope:set_position(10, regexcheck:get_bottom() + 10) +scope:add_option("current file") +scope:add_option("project files") +scope:set_selected(1) + +---@type widget.filepicker +local filepicker = FilePicker(widget) +filepicker:set_mode(FilePicker.mode.DIRECTORY) +filepicker:set_position(10, scope:get_bottom() + 10) +filepicker:set_tooltip("Directory to perform the search") +filepicker:hide() + +---@type widget.line +local statusline = Line(widget) +statusline:set_position(0, scope:get_bottom() + 10) + +---@type widget.label +local status = Label(widget, "") +status:set_position(10, statusline:get_bottom() + 10) + +-------------------------------------------------------------------------------- +-- Helper class to keep track of amount of matches and display on status label +-------------------------------------------------------------------------------- +---@class plugins.search_ui.result +---@field line integer +---@field col integer + +---@class plugins.search_ui.results +---@field text string +---@field matches plugins.search_ui.result[] +---@field doc core.doc? +local Results = { + text = "", + matches = {}, + doc = nil, + prev_search_id = 0 +} + +---@param text string +---@param doc core.doc +function Results:find(text, doc, force) + if self.text == text and self.doc == doc and not force then + self:set_status() + return + end + + -- disable previous search thread + if self.prev_search_id > 0 and core.threads[self.prev_search_id] then + core.threads[self.prev_search_id] = { + cr = coroutine.create(function() end), wake = 0 + } + end + + self.text = text + self.doc = doc + + local search_func + + -- regex search + if regexcheck:is_checked() then + local regex_find_offsets = regex.match + if regex.find_offsets then + regex_find_offsets = regex.find_offsets + end + local pattern = regex.compile( + findtext:get_text(), + insensitive:is_checked() and "im" or "m" + ) + if not pattern then return end + search_func = function(line_text) + ---@cast line_text string + local results = nil + local offsets = {regex_find_offsets(pattern, line_text)} + if offsets[1] then + results = {} + for i=1, #offsets, 2 do + table.insert(results, offsets[i]) + end + end + return results + end + -- plain or pattern search + else + local no_case = insensitive:is_checked() + local is_plain = not patterncheck:is_checked() + if is_plain and no_case then + text = text:ulower() + end + search_func = function(line_text) + ---@cast line_text string + if is_plain and no_case then + line_text = line_text:ulower() + end + local results = nil + local col1, col2 = line_text:find(text, 1, is_plain) + if col1 then + results = {} + table.insert(results, col1) + while col1 do + col1, col2 = line_text:find(text, col2+1, is_plain) + if col1 then + table.insert(results, col1) + end + end + end + return results + end + end + + self.prev_search_id = core.add_thread(function() + self.matches = {} + local lines_count = #doc.lines + for i=1, lines_count do + local offsets = search_func(doc.lines[i]) + if offsets then + for _, col in ipairs(offsets) do + table.insert(self.matches, {line = i, col = col}) + end + end + if i % 100 == 0 then + coroutine.yield() + end + end + self:set_status() + end) +end + +---@return integer +function Results:current() + if not self.doc then return 0 end + local line1, col1, line2, col2 = self.doc:get_selection() + if line1 == line2 and col1 == col2 then return 0 end + local line = math.min(line1, line2) + local col = math.min(col1, col2) + if self.matches and #self.matches > 0 then + for i, result in ipairs(self.matches) do + if result.line == line and result.col == col then + return i + end + end + end + return 0 +end + +function Results:clear() + self.text = "" + self.matches = {} + self.doc = nil + status:set_label("") +end + +function Results:set_status() + local current = self:current() + local total = self.matches and #self.matches or 0 + if total > 0 then + status:set_label( + "Result: " .. tostring(current .. " of " .. tostring(total)) + ) + else + status:set_label("") + end +end + +-------------------------------------------------------------------------------- +-- Helper functions +-------------------------------------------------------------------------------- +local function view_is_open(target_view) + if not target_view then return false end + local found = false + for _, view in ipairs(core.root_view.root_node:get_children()) do + if view == target_view then + found = true + break + end + end + return found +end + +local function toggle_scope(idx, not_set) + if not not_set then scope:set_selected(idx) end + + if idx == 1 then + replacetext:show() + findnext:show() + findprev:show() + replace:show() + patterncheck:show() + replaceinselection:show() + findproject:hide() + filepicker:hide() + + if view_is_open(doc_view) and findtext:get_text() ~= "" then + Results:find(findtext:get_text(), doc_view.doc) + else + Results:clear() + end + else + replacetext:hide() + findnext:hide() + findprev:hide() + replace:hide() + patterncheck:hide() + replaceinselection:hide() + findproject:show() + filepicker:show() + + Results:clear() + end +end + +local function project_search() + if findtext:get_text() == "" then return end + if not regexcheck:is_checked() then + projectsearch.search_plain( + findtext:get_text(), filepicker:get_path(), insensitive:is_checked() + ) + else + projectsearch.search_regex( + findtext:get_text(), filepicker:get_path(), insensitive:is_checked() + ) + end + command.perform "search-replace:hide" +end + +local find_enabled = true +local function find(reverse) + if + not view_is_open(doc_view) or findtext:get_text() == "" or not find_enabled + then + Results:clear() + return + end + + if core.last_active_view and core.last_active_view:is(DocView) then + doc_view = core.last_active_view + end + + local doc = doc_view.doc + local cline1, ccol1, cline2, ccol2 = doc:get_selection() + local line, col = cline1, ccol1 + if reverse and ccol2 < ccol1 then + col = ccol2 + end + + local opt = { + wrap = true, + no_case = insensitive:is_checked(), + pattern = patterncheck:is_checked(), + regex = regexcheck:is_checked(), + reverse = reverse + } + + if opt.regex and not regex.compile(findtext:get_text()) then + return + end + + status:set_label("") + + core.try(function() + local line1, col1, line2, col2 = search.find( + doc, line, col, findtext:get_text(), opt + ) + + local current_text = doc:get_text( + table.unpack({ doc:get_selection() }) + ) + + if opt.no_case and not opt.regex and not opt.pattern then + current_text = current_text:ulower() + end + + if line1 then + local text = findtext:get_text() + if opt.no_case and not opt.regex and not opt.pattern then + text = text:ulower() + end + if reverse or (current_text == text or current_text == "") then + doc:set_selection(line1, col2, line2, col1) + else + doc:set_selection(line1, col1, line2, col2) + end + doc_view:scroll_to_line(line1, true) + Results:find(text, doc) + end + end) +end + +local function find_replace() + if core.last_active_view:is(DocView) then + doc_view = core.last_active_view + end + local doc = doc_view.doc + + if not replaceinselection:is_checked() then + local line1, col1, line2, col2 = doc:get_selection() + if line1 ~= line2 or col1 ~= col2 then + doc:set_selection(line1, col1) + end + end + + local old = findtext:get_text() + local new = replacetext:get_text() + + local results = doc:replace(function(text) + if not regexcheck:is_checked() then + if not patterncheck:is_checked() then + return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil) + else + return text:gsub(old, new) + end + end + local result, matches = regex.gsub(regex.compile(old, "m"), text, new) + if type(matches) == "table" then + return result, #matches + end + return result, matches + end) + + local n = 0 + for _,v in pairs(results) do + n = n + v + end + + status:set_label(string.format("Total Replaced: %d", n)) +end + +local inside_node = false +local current_node = nil +local current_position = "" + +local function add_to_node() + if not inside_node or current_position ~= config.plugins.search_ui.position then + if + current_position ~= "" + and + current_position ~= config.plugins.search_ui.position + then + widget:hide() + current_node:remove_view(core.root_view.root_node, widget) + core.root_view.root_node:update_layout() + widget:set_size(0, 0) + widget.init_size = true + end + local node = core.root_view:get_primary_node() + if config.plugins.search_ui.position == "right" then + current_node = node:split("right", widget, {x=true}, true) + current_position = "right" + elseif config.plugins.search_ui.position == "top" then + current_node = node:split("up", widget, {y=true}, false) + current_position = "top" + else + current_node = node:split("down", widget, {y=true}, false) + current_position = "bottom" + end + widget:show() + inside_node = true + end +end + +---Show or hide the search pane. +---@param av? core.docview +---@param toggle? boolean +local function show_find(av, toggle) + if + not view_is_open(av) + and + scope:get_selected() == 1 + then + widget:swap_active_child() + if config.plugins.search_ui.position == "right" then + widget:hide_animated(false, true) + else + widget:hide_animated(true, false) + end + return + end + + if inside_node and current_position == config.plugins.search_ui.position then + if toggle then + widget:toggle_visible(true, false, true) + else + if not widget:is_visible() then + if config.plugins.search_ui.position == "right" then + widget:show_animated(false, true) + else + widget:show_animated(true, false) + end + end + end + else + add_to_node() + end + + if widget:is_visible() then + status:set_label("") + + widget:swap_active_child(findtext) + doc_view = av + if view_is_open(doc_view) and doc_view.doc then + local doc_text = doc_view.doc:get_text( + table.unpack({ doc_view.doc:get_selection() }) + ) + if insensitive:is_checked() then doc_text = doc_text:ulower() end + local current_text = findtext:get_text() + if insensitive:is_checked() then current_text = current_text:ulower() end + if doc_text and doc_text ~= "" and current_text ~= doc_text then + local original_text = doc_view.doc:get_text( + table.unpack({ doc_view.doc:get_selection() }) + ) + find_enabled = false + findtext:set_text(original_text) + find_enabled = true + elseif current_text ~= "" and doc_text == "" then + if scope:get_selected() == 1 then + find(false) + end + end + if findtext:get_text() ~= "" then + findtext.textview.doc:set_selection(1, math.huge, 1, 1) + if scope:get_selected() == 1 then + Results:find(findtext:get_text(), doc_view.doc) + else + Results:clear() + end + else + Results:clear() + end + end + else + widget:swap_active_child() + if view_is_open(doc_view) then + core.set_active_view(doc_view) + end + end +end + +-------------------------------------------------------------------------------- +-- Widgets event overrides +-------------------------------------------------------------------------------- +function findtext:on_change(text) + if scope:get_selected() == 1 then + find(false) + end +end + +function insensitive:on_checked(checked) + Results:clear() +end + +function patterncheck:on_checked(checked) + if checked then + regexcheck:set_checked(false) + end + Results:clear() +end + +function regexcheck:on_checked(checked) + if checked then + patterncheck:set_checked(false) + end + Results:clear() +end + +function scope:on_selected(idx) + toggle_scope(idx, true) + if not view_is_open(doc_view) and idx == 1 then + command.perform "search-replace:hide" + end +end + +function findnext:on_click() find(false) end +function findprev:on_click() find(true) end +function findproject:on_click() project_search() end +function replace:on_click() find_replace() end + +---@param self widget +local function update_size(self) + if config.plugins.search_ui.position == "right" then + if scope:get_selected() == 1 then + if self.size.x < replace:get_right() + replace:get_width() / 2 then + self.size.x = replace:get_right() + replace:get_width() / 2 + end + else + if self.size.x < findproject:get_right() + findproject:get_width() * 2 then + self.size.x = findproject:get_right() + findproject:get_width() * 2 + end + end + else + self:set_size(nil, self:get_real_height() + 10) + end +end + +---@param self widget +local function update_right_positioning(self) + scope:show() + label:show() + status:show() + line_options:show() + label:set_label("Find and Replace") + + label:set_position(10, 10) + line:set_position(0, label:get_bottom() + 10) + findtext:set_position(10, line:get_bottom() + 10) + findtext.size.x = self.size.x - 20 + + if scope:get_selected() == 1 then + replacetext:set_position(10, findtext:get_bottom() + 10) + replacetext.size.x = self.size.x - 20 + findprev:set_position(10, replacetext:get_bottom() + 10) + findnext:set_position(findprev:get_right() + 5, replacetext:get_bottom() + 10) + replace:set_position(findnext:get_right() + 5, replacetext:get_bottom() + 10) + line_options:set_position(0, replace:get_bottom() + 10) + else + findproject:set_position(10, findtext:get_bottom() + 10) + replace:set_position(findproject:get_right() + 5, replacetext:get_bottom() + 10) + line_options:set_position(0, findproject:get_bottom() + 10) + end + + insensitive:set_position(10, line_options:get_bottom() + 10) + if scope:get_selected() == 1 then + patterncheck:set_position(10, insensitive:get_bottom() + 10) + regexcheck:set_position(10, patterncheck:get_bottom() + 10) + replaceinselection:set_position(10, regexcheck:get_bottom() + 10) + scope:set_position(10, replaceinselection:get_bottom() + 10) + else + regexcheck:set_position(10, insensitive:get_bottom() + 10) + scope:set_position(10, regexcheck:get_bottom() + 10) + end + + scope:set_size(self.size.x - 20) + if scope:get_selected() == 1 then + statusline:set_position(0, scope:get_bottom() + 30) + else + filepicker:set_position(10, scope:get_bottom() + 10) + filepicker:set_size(self.size.x - 20, nil) + statusline:set_position(0, filepicker:get_bottom() + 30) + end + + status:set_position(10, statusline:get_bottom() + 10) + if status.label == "" then + statusline:hide() + else + statusline:show() + end + + if self.init_size then + update_size(self) + self.init_size = false + self:show_animated(false, true) + end + + add_to_node() +end + +---@param self widget +local function update_bottom_positioning(self) + scope:hide() + statusline:hide() + + if scope:get_selected() == 1 then + label:hide() + status:show() + status:set_position(10, 10) + replaceinselection:set_position(self.size.x - replaceinselection:get_width() - 10, 10) + regexcheck:set_position(replaceinselection:get_position().x - 10 - regexcheck:get_width(), 10) + patterncheck:set_position(regexcheck:get_position().x - 10 - patterncheck:get_width(), 10) + insensitive:set_position(patterncheck:get_position().x - 10 - insensitive:get_width(), 10) + line:set_position(0, status:get_bottom() + 10) + else + label:show() + status:hide() + label:set_label("Find in Directory") + label:set_position(10, 10) + regexcheck:set_position(self.size.x - regexcheck:get_width() - 10, 10) + insensitive:set_position(regexcheck:get_position().x - 10 - insensitive:get_width(), 10) + line:set_position(0, label:get_bottom() + 10) + end + + if scope:get_selected() == 1 then + findtext:set_position(10, line:get_bottom() + 10) + findtext.size.x = self.size.x - 40 - findprev:get_width() - findnext:get_width() + findnext:set_position(self.size.x - 10 - findnext:get_width(), line:get_bottom() + 10) + findprev:set_position(findnext:get_position().x - 10 - findprev:get_width(), line:get_bottom() + 10) + replacetext:set_position(10, findtext:get_bottom() + 10) + replacetext.size.x = findtext.size.x + replace:set_position(self.size.x - 15 - replace:get_width(), findtext:get_bottom() + 10) + replace.size.x = findprev:get_width() + findnext:get_width() + 10 + line_options:hide() + else + findtext:set_position(10, line:get_bottom() + 10) + findtext.size.x = self.size.x - 30 - findproject:get_width() + findproject:set_position(self.size.x - 10 - findproject:get_width(), line:get_bottom() + 10) + replace:set_position(findproject:get_right() + 5, replacetext:get_bottom() + 10) + line_options:show() + line_options:set_position(0, findproject:get_bottom() + 10) + filepicker:set_position(10, line_options:get_bottom() + 10) + filepicker:set_size(self.size.x - 20, nil) + end + + if self.init_size then + update_size(self) + self.init_size = false + self:show_animated(true, false) + end + + add_to_node() +end + +-- reposition items on scale changes +function widget:update() + if Widget.update(self) then + if config.plugins.search_ui.position == "right" then + update_right_positioning(self) + else + update_bottom_positioning(self) + end + end +end + +function widget:on_scale_change(...) + Widget.on_scale_change(self, ...) + update_size(self) +end + +-------------------------------------------------------------------------------- +-- Override set_active_view to keep track of currently active docview +-------------------------------------------------------------------------------- +local core_set_active_view = core.set_active_view +function core.set_active_view(...) + core_set_active_view(...) + local view = core.next_active_view or core.active_view + if + view ~= doc_view + and + widget:is_visible() + and + view:extends(DocView) + and + view ~= findtext.textview + and + view ~= replacetext.textview + and + view.doc.filename + then + doc_view = view + Results:clear() + end +end + +-------------------------------------------------------------------------------- +-- Register commands +-------------------------------------------------------------------------------- +command.add( + function() + if core.active_view:is(DocView) then + return true, core.active_view + elseif widget:is_visible() then + return true, doc_view + elseif scope:get_selected() == 2 then + return true, nil + end + return false + end, + { + ["search-replace:show"] = function(av) + show_find(av, false) + end, + + ["search-replace:toggle"] = function(av) + show_find(av, true) + end + } +) + +command.add(function() return widget:is_visible() and not core.active_view:is(CommandView) end, { + ["search-replace:hide"] = function() + widget:swap_active_child() + if config.plugins.search_ui.position == "right" then + widget:hide_animated(false, true) + else + widget:hide_animated(true, false) + end + if view_is_open(doc_view) then + core.set_active_view(doc_view) + end + end, + + ["search-replace:file-search"] = function() + toggle_scope(1) + command.perform "search-replace:show" + end, + + ["search-replace:next"] = function() + find(false) + end, + + ["search-replace:previous"] = function() + find(true) + end, + + ["search-replace:toggle-sensitivity"] = function() + insensitive:set_checked(not insensitive:is_checked()) + Results:clear() + end, + + ["search-replace:toggle-regex"] = function() + regexcheck:set_checked(not regexcheck:is_checked()) + Results:clear() + end, + + ["search-replace:toggle-in-selection"] = function() + replaceinselection:set_checked(not replaceinselection:is_checked()) + end +}) + +command.add( + function() + return widget:is_visible() + and + not core.active_view:is(CommandView) + and + ( + widget.child_active == findtext + or + widget.child_active == replacetext + ) + end, + { + ["search-replace:perform"] = function() + if scope:get_selected() == 1 then + if widget.child_active == findtext then + ---@type core.doc + local doc = doc_view.doc + local line1, col1, line2, col2 = doc:get_selection() + -- correct cursor position to properly search next result + if line1 ~= line2 or col1 ~= col2 then + doc:set_selection( + line1, + math.max(col1, col2), + line2, + math.min(col1, col2) + ) + end + find(false) + else + find_replace() + end + else + project_search() + end + end + } +) + +-------------------------------------------------------------------------------- +-- Override core find/replace commands +-------------------------------------------------------------------------------- +local find_replace_find = command.map["find-replace:find"].perform +command.map["find-replace:find"].perform = function(...) + if config.plugins.search_ui.replace_core_find then + toggle_scope(1) + command.perform "search-replace:show" + else + find_replace_find(...) + end +end + +local find_replace_replace = command.map["find-replace:replace"].perform +command.map["find-replace:replace"].perform = function(...) + if config.plugins.search_ui.replace_core_find then + toggle_scope(1) + command.perform "search-replace:show" + else + find_replace_replace(...) + end +end + +local find_replace_repeat = command.map["find-replace:repeat-find"].perform +command.map["find-replace:repeat-find"].perform = function(...) + if + widget:is_visible() + or + (config.plugins.search_ui.replace_core_find and findtext:get_text() ~= "") + then + find(false) + return + end + find_replace_repeat(...) +end + +local find_replace_previous = command.map["find-replace:previous-find"].perform +command.map["find-replace:previous-find"].perform = function(...) + if + widget:is_visible() + or + (config.plugins.search_ui.replace_core_find and findtext:get_text() ~= "") + then + find(true) + return + end + find_replace_previous(...) +end + +local project_search_find = command.map["project-search:find"].perform +command.map["project-search:find"].perform = function(path) + if config.plugins.search_ui.replace_core_find then + toggle_scope(2) + if path then + filepicker:set_path(path) + end + local av = doc_view + if + core.active_view:extends(DocView) + and + core.active_view ~= findtext.textview + and + core.active_view ~= replacetext.textview + then + av = core.active_view + end + show_find(av, false) + return + end + project_search_find(path) +end + +-------------------------------------------------------------------------------- +-- Register keymaps +-------------------------------------------------------------------------------- +keymap.add { + ["alt+h"] = "search-replace:toggle", + ["escape"] = "search-replace:hide", + ["f3"] = "search-replace:next", + ["shift+f3"] = "search-replace:previous", + ["return"] = "search-replace:perform", + ["shift+return"] = "search-replace:previous", + ["ctrl+i"] = "search-replace:toggle-sensitivity", + ["ctrl+shift+i"] = "search-replace:toggle-regex", + ["ctrl+alt+i"] = "search-replace:toggle-in-selection", + ["ctrl+f"] = "search-replace:file-search" +} + + +return widget diff --git a/plugins/settings.lua b/plugins/settings.lua index ec722bc..d3af077 100644 --- a/plugins/settings.lua +++ b/plugins/settings.lua @@ -30,6 +30,7 @@ local ItemsList = require "libraries.widget.itemslist" local KeybindingDialog = require "libraries.widget.keybinddialog" local Fonts = require "libraries.widget.fonts" local FilePicker = require "libraries.widget.filepicker" +local ColorPicker = require "libraries.widget.colorpicker" local MessageBox = require "libraries.widget.messagebox" ---@class plugins.settings @@ -53,7 +54,8 @@ settings.type = { BUTTON = 6, FONT = 7, FILE = 8, - DIRECTORY = 9 + DIRECTORY = 9, + COLOR = 10 } ---@alias settings.types @@ -65,6 +67,8 @@ settings.type = { ---| `settings.type.BUTTON` ---| `settings.type.FONT` ---| `settings.type.FILE` +---| `settings.type.DIRECTORY` +---| `settings.type.COLOR` ---Represents a setting to render on a settings pane. ---@class settings.option @@ -1261,6 +1265,14 @@ local function add_control(pane, option, plugin_name) file.filters = option.filters or {} widget = file found = true + + elseif option.type == settings.type.COLOR then + ---@type widget.label + Label(pane, option.label .. ":") + ---@type widget.colorpicker + local color = ColorPicker(pane, option_value) + widget = color + found = true end if widget and type(path) ~= "nil" then @@ -1624,11 +1636,13 @@ function Settings:load_keymap_settings() end table.sort(ordered) + ---@type widget.textbox + local textbox = TextBox(self.keybinds, "", "filter bindings...") + ---@type widget.listbox local listbox = ListBox(self.keybinds) listbox.border.width = 0 - listbox:enable_expand(true) listbox:add_column("Command") listbox:add_column("Bindings") @@ -1653,8 +1667,15 @@ function Settings:load_keymap_settings() }, name) end - function listbox:on_row_click(idx, data) - if not keymap_dialog:is_visible() then + function textbox:on_change(value) + listbox:filter(value) + end + + function listbox:on_mouse_pressed(button, x, y, clicks) + listbox.super.on_mouse_pressed(self, button, x, y, clicks) + local idx = listbox:get_selected() + local data = listbox:get_row_data(idx) + if clicks == 2 and not keymap_dialog:is_visible() then local bindings = { keymap.get_binding(data) } keymap_dialog:set_bindings(bindings) keymap_dialog.row_id = idx @@ -1663,6 +1684,14 @@ function Settings:load_keymap_settings() keymap_dialog:show() end end + + ---@param self widget + function self.keybinds:update_positions() + textbox:set_position(0, 0) + textbox:set_size(self:get_width() - self.border.width * 2) + listbox:set_position(0, textbox:get_bottom()) + listbox:set_size(self:get_width() - self.border.width * 2, self:get_height() - textbox:get_height()) + end end function Settings:setup_about() @@ -1819,6 +1848,10 @@ function Settings:update() end end + if self.keybinds:is_visible() then + self.keybinds:update_positions() + end + if self.about:is_visible() then self.about:update_positions() end |