Mercurial Hosting > editor
diff src/luan_editor/find.luan @ 37:b7ff52d45b9a default tip
copy from luan
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Mon, 21 Apr 2025 13:07:29 -0600 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan_editor/find.luan Mon Apr 21 13:07:29 2025 -0600 @@ -0,0 +1,244 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local ipairs = Luan.ipairs or error() +local range = Luan.range or error() +local String = require "luan:String.luan" +local regex = String.regex or error() +local regex_quote = String.regex_quote or error() +local regex_quote_replacement = String.regex_quote_replacement or error() +local repeated = String.repeated or error() +local new_text_field = require("luan:swing/Text_field.luan").new or error() +local new_check_box = require("luan:swing/Check_box.luan").new or error() +local new_label = require("luan:swing/Label.luan").new or error() +local new_panel = require("luan:swing/Component.luan").new_panel or error() +local Layout = require "luan:swing/Layout.luan" +local new_mig_layout = Layout.new_mig_layout or error() +local new_button = require("luan:swing/Button.luan").new or error() +local Swing = require "luan:swing/Swing.luan" +local browse = Swing.browse or error() + + +local function get_matches(text,s) + local r = regex(s) + local matches = {} + local i = 1 + local n = #text + while i <= n do + local j1, j2 = r.find(text,i) + if j1 == nil then + break + end + j2 = j2 + 1 + if j1 == j2 then + i = j2 + 1 + else + matches[#matches+1] = { start=j1, end_=j2 } + i = j2 + end + end + return matches +end + +local function make_find_panel(window) + local text_area = window.text_area + local status_bar = window.status_bar + local find_field, replace_field, regex_check_box + local function find_match(event) + --logger.info("action "..event.action) + local s = find_field.text + if #s == 0 then + status_bar.text = " " + return + end + if not regex_check_box.is_selected then + s = regex_quote(s) + end + local matches + try + matches = get_matches( text_area.text, s ) + catch e + status_bar.text = "Regex error: "..e.get_message() + return + end + text_area.set_hightlights(matches) + local n_matches = #matches + if n_matches == 0 then + status_bar.text = "0 matches" + return + end + local action = event.action + if action == "next" then + local _, pos = text_area.get_selection() + for i, match in ipairs(matches) do + if match.start >= pos then + text_area.set_selection( match.start, match.end_ ) + status_bar.text = i.." of "..n_matches.." matches" + return + end + end + local match = matches[1] + text_area.set_selection( match.start, match.end_ ) + status_bar.text = "1 of "..n_matches.." matches; wrapped past end" + elseif action == "previous" then + local pos = text_area.get_selection() + for i in range(n_matches,1,-1) do + local match = matches[i] + if match.end_ <= pos then + text_area.set_selection( match.start, match.end_ ) + status_bar.text = i.." of "..n_matches.." matches" + return + end + end + local match = matches[n_matches] + text_area.set_selection( match.start, match.end_ ) + status_bar.text = n_matches.." of "..n_matches.." matches; wrapped past end" + else + error(action) + end + end + local function replace_match(event) + local find = find_field.text + if #find == 0 then + status_bar.text = " " + return + end + local replace = replace_field.text + if not regex_check_box.is_selected then + find = regex_quote(find) + replace = regex_quote_replacement(replace) + end + local r + try + r = regex(find) + catch e + status_bar.text = "Regex error: "..e.get_message() + return + end + local new, n + local action = event.action + if action == "replace" then + local selection = text_area.selected_text + new, n = r.gsub(selection,replace) + if n > 0 then + text_area.selected_text = new + end + elseif action == "replace_all" then + local text = text_area.text + new, n = r.gsub(text,replace) + if n > 0 then + text_area.text = new + end + else + error(action) + end + status_bar.text = n.." replacements" + end + find_field = new_text_field{ + constraints = "growx" + whitespace_visible = true + action = "next" + action_listener = find_match + } + replace_field = new_text_field{ + constraints = "growx" + whitespace_visible = true + action = "replace" + action_listener = replace_match + } + regex_check_box = new_check_box{ + text = "Use Regex" + } + local find_panel = new_panel{ + constraints = "growy 0,growx" + layout = new_mig_layout("insets 8 16 0 16","[][grow][grow 0]") + visible = false + children = { + new_label{ + constraints = "right" + text = "Find:" + } + find_field + new_button{ + constraints = "grow" + text = "Find Next" + action = "next" + action_listener = find_match + } + new_button{ + constraints = "grow,wrap" + text = "Find Previous" + action = "previous" + action_listener = find_match + } + new_label{ + constraints = "right" + text = "Replace:" + } + replace_field + new_button{ + constraints = "grow" + text = "Replace" + tool_tip_text = "Replace matches in selected text" + action = "replace" + action_listener = replace_match + } + new_button{ + constraints = "grow,wrap" + text = "Replace All" + action = "replace_all" + action_listener = replace_match + } + new_panel{ + constraints = "span,wrap" + layout = new_mig_layout("insets 0,gap 16px") + children = { + regex_check_box + new_button{ + text = "Learn About Regular Expressions" + action_listener = function(_) + browse("https://www.reactionary.software/learn.html#regex") + end + } + } + } + } + } + function window.show_find_panel(visible) + find_panel.visible = visible + if visible then + find_field.request_focus_in_window() + else + text_area.clear_hightlights() + end + end + function window.find_case_insensitive(_) + find_panel.visible = true + window.find_menu_item.is_selected = true + regex_check_box.is_selected = true + find_field.text = "(?i)\Q\E" + find_field.set_selection(7) + find_field.request_focus_in_window() + status_bar.text = [[Put search text between "\Q" and "\E"]] + end + function window.tabs_to_spaces(_) + find_panel.visible = true + window.find_menu_item.is_selected = true + regex_check_box.is_selected = true + find_field.text = [[(?m)^(\t*)\t]] + local spaces = repeated( " ", text_area.tab_size ) + replace_field.text = "$1"..spaces + status_bar.text = [[Do "Replace All" until 0 replacements]] + end + function window.spaces_to_tabs(_) + find_panel.visible = true + window.find_menu_item.is_selected = true + regex_check_box.is_selected = true + local tab_size = text_area.tab_size + find_field.text = `%>(?m)^(( {<%=tab_size%>})*) {<%=tab_size%>}<%` + replace_field.text = "$1\t" + status_bar.text = [[Do "Replace All" until 0 replacements]] + end + return find_panel +end + +return make_find_panel