Mercurial Hosting > editor
comparison src/luan_editor/find.luan @ 37:b7ff52d45b9a
copy from luan
| author | Franklin Schmidt <fschmidt@gmail.com> |
|---|---|
| date | Mon, 21 Apr 2025 13:07:29 -0600 |
| parents | |
| children | dcd7d082196f |
comparison
equal
deleted
inserted
replaced
| 36:0a8865de3d53 | 37:b7ff52d45b9a |
|---|---|
| 1 local Luan = require "luan:Luan.luan" | |
| 2 local error = Luan.error | |
| 3 local ipairs = Luan.ipairs or error() | |
| 4 local range = Luan.range or error() | |
| 5 local String = require "luan:String.luan" | |
| 6 local regex = String.regex or error() | |
| 7 local regex_quote = String.regex_quote or error() | |
| 8 local regex_quote_replacement = String.regex_quote_replacement or error() | |
| 9 local repeated = String.repeated or error() | |
| 10 local new_text_field = require("luan:swing/Text_field.luan").new or error() | |
| 11 local new_check_box = require("luan:swing/Check_box.luan").new or error() | |
| 12 local new_label = require("luan:swing/Label.luan").new or error() | |
| 13 local new_panel = require("luan:swing/Component.luan").new_panel or error() | |
| 14 local Layout = require "luan:swing/Layout.luan" | |
| 15 local new_mig_layout = Layout.new_mig_layout or error() | |
| 16 local new_button = require("luan:swing/Button.luan").new or error() | |
| 17 local Swing = require "luan:swing/Swing.luan" | |
| 18 local browse = Swing.browse or error() | |
| 19 | |
| 20 | |
| 21 local function get_matches(text,s) | |
| 22 local r = regex(s) | |
| 23 local matches = {} | |
| 24 local i = 1 | |
| 25 local n = #text | |
| 26 while i <= n do | |
| 27 local j1, j2 = r.find(text,i) | |
| 28 if j1 == nil then | |
| 29 break | |
| 30 end | |
| 31 j2 = j2 + 1 | |
| 32 if j1 == j2 then | |
| 33 i = j2 + 1 | |
| 34 else | |
| 35 matches[#matches+1] = { start=j1, end_=j2 } | |
| 36 i = j2 | |
| 37 end | |
| 38 end | |
| 39 return matches | |
| 40 end | |
| 41 | |
| 42 local function make_find_panel(window) | |
| 43 local text_area = window.text_area | |
| 44 local status_bar = window.status_bar | |
| 45 local find_field, replace_field, regex_check_box | |
| 46 local function find_match(event) | |
| 47 --logger.info("action "..event.action) | |
| 48 local s = find_field.text | |
| 49 if #s == 0 then | |
| 50 status_bar.text = " " | |
| 51 return | |
| 52 end | |
| 53 if not regex_check_box.is_selected then | |
| 54 s = regex_quote(s) | |
| 55 end | |
| 56 local matches | |
| 57 try | |
| 58 matches = get_matches( text_area.text, s ) | |
| 59 catch e | |
| 60 status_bar.text = "Regex error: "..e.get_message() | |
| 61 return | |
| 62 end | |
| 63 text_area.set_hightlights(matches) | |
| 64 local n_matches = #matches | |
| 65 if n_matches == 0 then | |
| 66 status_bar.text = "0 matches" | |
| 67 return | |
| 68 end | |
| 69 local action = event.action | |
| 70 if action == "next" then | |
| 71 local _, pos = text_area.get_selection() | |
| 72 for i, match in ipairs(matches) do | |
| 73 if match.start >= pos then | |
| 74 text_area.set_selection( match.start, match.end_ ) | |
| 75 status_bar.text = i.." of "..n_matches.." matches" | |
| 76 return | |
| 77 end | |
| 78 end | |
| 79 local match = matches[1] | |
| 80 text_area.set_selection( match.start, match.end_ ) | |
| 81 status_bar.text = "1 of "..n_matches.." matches; wrapped past end" | |
| 82 elseif action == "previous" then | |
| 83 local pos = text_area.get_selection() | |
| 84 for i in range(n_matches,1,-1) do | |
| 85 local match = matches[i] | |
| 86 if match.end_ <= pos then | |
| 87 text_area.set_selection( match.start, match.end_ ) | |
| 88 status_bar.text = i.." of "..n_matches.." matches" | |
| 89 return | |
| 90 end | |
| 91 end | |
| 92 local match = matches[n_matches] | |
| 93 text_area.set_selection( match.start, match.end_ ) | |
| 94 status_bar.text = n_matches.." of "..n_matches.." matches; wrapped past end" | |
| 95 else | |
| 96 error(action) | |
| 97 end | |
| 98 end | |
| 99 local function replace_match(event) | |
| 100 local find = find_field.text | |
| 101 if #find == 0 then | |
| 102 status_bar.text = " " | |
| 103 return | |
| 104 end | |
| 105 local replace = replace_field.text | |
| 106 if not regex_check_box.is_selected then | |
| 107 find = regex_quote(find) | |
| 108 replace = regex_quote_replacement(replace) | |
| 109 end | |
| 110 local r | |
| 111 try | |
| 112 r = regex(find) | |
| 113 catch e | |
| 114 status_bar.text = "Regex error: "..e.get_message() | |
| 115 return | |
| 116 end | |
| 117 local new, n | |
| 118 local action = event.action | |
| 119 if action == "replace" then | |
| 120 local selection = text_area.selected_text | |
| 121 new, n = r.gsub(selection,replace) | |
| 122 if n > 0 then | |
| 123 text_area.selected_text = new | |
| 124 end | |
| 125 elseif action == "replace_all" then | |
| 126 local text = text_area.text | |
| 127 new, n = r.gsub(text,replace) | |
| 128 if n > 0 then | |
| 129 text_area.text = new | |
| 130 end | |
| 131 else | |
| 132 error(action) | |
| 133 end | |
| 134 status_bar.text = n.." replacements" | |
| 135 end | |
| 136 find_field = new_text_field{ | |
| 137 constraints = "growx" | |
| 138 whitespace_visible = true | |
| 139 action = "next" | |
| 140 action_listener = find_match | |
| 141 } | |
| 142 replace_field = new_text_field{ | |
| 143 constraints = "growx" | |
| 144 whitespace_visible = true | |
| 145 action = "replace" | |
| 146 action_listener = replace_match | |
| 147 } | |
| 148 regex_check_box = new_check_box{ | |
| 149 text = "Use Regex" | |
| 150 } | |
| 151 local find_panel = new_panel{ | |
| 152 constraints = "growy 0,growx" | |
| 153 layout = new_mig_layout("insets 8 16 0 16","[][grow][grow 0]") | |
| 154 visible = false | |
| 155 children = { | |
| 156 new_label{ | |
| 157 constraints = "right" | |
| 158 text = "Find:" | |
| 159 } | |
| 160 find_field | |
| 161 new_button{ | |
| 162 constraints = "grow" | |
| 163 text = "Find Next" | |
| 164 action = "next" | |
| 165 action_listener = find_match | |
| 166 } | |
| 167 new_button{ | |
| 168 constraints = "grow,wrap" | |
| 169 text = "Find Previous" | |
| 170 action = "previous" | |
| 171 action_listener = find_match | |
| 172 } | |
| 173 new_label{ | |
| 174 constraints = "right" | |
| 175 text = "Replace:" | |
| 176 } | |
| 177 replace_field | |
| 178 new_button{ | |
| 179 constraints = "grow" | |
| 180 text = "Replace" | |
| 181 tool_tip_text = "Replace matches in selected text" | |
| 182 action = "replace" | |
| 183 action_listener = replace_match | |
| 184 } | |
| 185 new_button{ | |
| 186 constraints = "grow,wrap" | |
| 187 text = "Replace All" | |
| 188 action = "replace_all" | |
| 189 action_listener = replace_match | |
| 190 } | |
| 191 new_panel{ | |
| 192 constraints = "span,wrap" | |
| 193 layout = new_mig_layout("insets 0,gap 16px") | |
| 194 children = { | |
| 195 regex_check_box | |
| 196 new_button{ | |
| 197 text = "Learn About Regular Expressions" | |
| 198 action_listener = function(_) | |
| 199 browse("https://www.reactionary.software/learn.html#regex") | |
| 200 end | |
| 201 } | |
| 202 } | |
| 203 } | |
| 204 } | |
| 205 } | |
| 206 function window.show_find_panel(visible) | |
| 207 find_panel.visible = visible | |
| 208 if visible then | |
| 209 find_field.request_focus_in_window() | |
| 210 else | |
| 211 text_area.clear_hightlights() | |
| 212 end | |
| 213 end | |
| 214 function window.find_case_insensitive(_) | |
| 215 find_panel.visible = true | |
| 216 window.find_menu_item.is_selected = true | |
| 217 regex_check_box.is_selected = true | |
| 218 find_field.text = "(?i)\Q\E" | |
| 219 find_field.set_selection(7) | |
| 220 find_field.request_focus_in_window() | |
| 221 status_bar.text = [[Put search text between "\Q" and "\E"]] | |
| 222 end | |
| 223 function window.tabs_to_spaces(_) | |
| 224 find_panel.visible = true | |
| 225 window.find_menu_item.is_selected = true | |
| 226 regex_check_box.is_selected = true | |
| 227 find_field.text = [[(?m)^(\t*)\t]] | |
| 228 local spaces = repeated( " ", text_area.tab_size ) | |
| 229 replace_field.text = "$1"..spaces | |
| 230 status_bar.text = [[Do "Replace All" until 0 replacements]] | |
| 231 end | |
| 232 function window.spaces_to_tabs(_) | |
| 233 find_panel.visible = true | |
| 234 window.find_menu_item.is_selected = true | |
| 235 regex_check_box.is_selected = true | |
| 236 local tab_size = text_area.tab_size | |
| 237 find_field.text = `%>(?m)^(( {<%=tab_size%>})*) {<%=tab_size%>}<%` | |
| 238 replace_field.text = "$1\t" | |
| 239 status_bar.text = [[Do "Replace All" until 0 replacements]] | |
| 240 end | |
| 241 return find_panel | |
| 242 end | |
| 243 | |
| 244 return make_find_panel |
