From 1a33e9b5b719c6a10ac2b5a37baaae560b0cd4cf Mon Sep 17 00:00:00 2001 From: mitchell <70453897+orbitalquark@users.noreply.github.com> Date: Tue, 13 Oct 2020 00:15:41 -0400 Subject: Implement \U, \L, \u, and \l case transformations in regex replacements. --- docs/manual.md | 12 +++++++++--- modules/textadept/find.lua | 25 +++++++++++++++++++++++-- test/test.lua | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/docs/manual.md b/docs/manual.md index 1efe9acc..1f1f4da4 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -527,9 +527,15 @@ While the pane is open in the GUI, the following key bindings apply: * Perform "Find Next" and "Find Prev" in the "Find" entry via `Enter` and `Shift+Enter`, respectively. * Perform "Replace" and "Replace All" in the "Replace" entry via `Enter` and - `Shift+Enter`, respectively. When the "Regex" find option is enabled, `\`*`n`* - in the "Replace" entry represents the *n*th captured matching region's text, - and `\0` represents all matched text. + `Shift+Enter`, respectively. When the "Regex" find option is enabled, + + + `\`*`n`* in the "Replace" entry represents the *n*th captured matching + region's text, and `\0` represents all matched text. + + `\U` and `\L` converts everything up to the next `\L`, `\U`, or `\E` to + uppercase and lowercase, respectively. (`\E` turns off conversion.) + + `\u` and `\l` converts the next character to uppercase and lowercase, + respectively. These may appear within `\U` and `\L` constructs. + * For at least the English locale, toggle the find options using their button mnemonics: `Alt+M`, `Alt+W`, `Alt+X`, `Alt+I` on Windows, Linux, and BSD, and `⌘M`, `⌘W`, `⌘X`, and `⌘I`, respectively, on macOS. diff --git a/modules/textadept/find.lua b/modules/textadept/find.lua index b8d37eed..a71faa26 100644 --- a/modules/textadept/find.lua +++ b/modules/textadept/find.lua @@ -381,11 +381,32 @@ local function unescape(text) end) or text end +-- Replaces the text in the target range with string *text* subject to: +-- * "\d" sequences replaced with the text of capture number *d* from the +-- regular expression (or the entire match for *d* = 0) +-- * "\U" and "\L" sequences convert everything up to the next "\U", "\L", or +-- "\E" to uppercase and lowercase, respectively. +-- * "\u" and "\l" sequences convert the next character to uppercase and +-- lowercase, respectively. They may appear within "\U" and "\L" constructs. +local function replace_target_re(buffer, rtext) + rtext = rtext:gsub('\\0', buffer.target_text):gsub('\\(%d)', buffer.tag) + local P, V, upper, lower = lpeg.P, lpeg.V, string.upper, string.lower + local patt = lpeg.Cs(P{ + (V('text') + V('u') + V('l') + V('U') + V('L'))^1, + text = (1 - '\\' * lpeg.S('uUlLE'))^1, + u = '\\u' * lpeg.C(1) / upper, l = '\\l' * lpeg.C(1) / lower, + U = P('\\U') / '' * (V('text') / upper + V('u') + V('l'))^0 * V('E')^-1, + L = P('\\L') / '' * (V('text') / lower + V('u') + V('l'))^0 * V('E')^-1, + E = P('\\E') / '', + }) + buffer:replace_target(lpeg.match(patt, rtext) or rtext) +end + -- Replaces found (selected) text. events.connect(events.REPLACE, function(rtext) if buffer.selection_empty then return end buffer:target_from_selection() - local f = not M.regex and buffer.replace_target or buffer.replace_target_re + local f = not M.regex and buffer.replace_target or replace_target_re f(buffer, unescape(rtext)) buffer:set_sel(buffer.target_start, buffer.target_end) end) @@ -405,7 +426,7 @@ events.connect(events.REPLACE_ALL, function(ftext, rtext) buffer:indicator_fill_range(e, 1) end local EOF = replace_in_sel and e == buffer.length + 1 -- no indicator at EOF - local f = not M.regex and buffer.replace_target or buffer.replace_target_re + local f = not M.regex and buffer.replace_target or replace_target_re rtext, repl_text = unescape(rtext), rtext -- save for ui.find.focus() -- Perform the search and replace. diff --git a/test/test.lua b/test/test.lua index 2d26293d..30f22433 100644 --- a/test/test.lua +++ b/test/test.lua @@ -2536,6 +2536,45 @@ function test_ui_find_replace_all() buffer:close(true) end +function test_find_replace_regex_transforms() + buffer.new() + buffer:set_text('foObaRbaz') + ui.find.find_entry_text = 'f([oO]+)ba(..)' + ui.find.regex = true + local replacements = { + ['f\\1ba\\2'] = 'foObaRbaz', + ['f\\u\\1ba\\l\\2'] = 'fOObarbaz', + ['f\\U\\1ba\\2'] = 'fOOBARBaz', + ['f\\U\\1ba\\l\\2'] = 'fOOBArBaz', + ['f\\U\\1\\Eba\\2'] = 'fOObaRbaz', + ['f\\L\\1ba\\2'] = 'foobarbaz', + ['f\\L\\1ba\\u\\2'] = 'foobaRbaz', + ['f\\L\\1ba\\U\\2'] = 'foobaRBaz', + ['f\\L\\1\\Eba\\2'] = 'foobaRbaz', + ['f\\L\\u\\1ba\\2'] = 'fOobarbaz', + ['f\\L\\u\\1ba\\U\\l\\2'] = 'fOobarBaz', + ['f\\L\\u\\1\\Eba\\2'] = 'fOobaRbaz', + ['f\\1ba\\U\\2'] = 'foObaRBaz', + ['f\\1ba\\L\\2'] = 'foObarbaz', + ['f\\1ba\\U\\l\\2'] = 'foObarBaz', + [''] = 'az', + ['\\0'] = 'foObaRbaz' + } + for regex, replacement in pairs(replacements) do + ui.find.replace_entry_text = regex + ui.find.find_next() + ui.find.replace() + assert_equal(buffer:get_text(), replacement) + buffer:undo() + ui.find.replace_all() + assert_equal(buffer:get_text(), replacement) + buffer:undo() + end + ui.find.find_entry_text, ui.find.replace_entry_text = '', '' + ui.find.regex = false + buffer:close(true) +end + function test_history() local filename1 = _HOME .. '/test/modules/textadept/history/1' io.open_file(filename1) -- cgit v1.2.3