aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormitchell <70453897+orbitalquark@users.noreply.github.com>2020-10-13 00:15:41 -0400
committermitchell <70453897+orbitalquark@users.noreply.github.com>2020-10-13 00:15:41 -0400
commit1a33e9b5b719c6a10ac2b5a37baaae560b0cd4cf (patch)
tree043cd2cf5e4369c371218e3880018fea478731d7
parentf65a88be1c179f1a29ddf1e8483b14167ce94e06 (diff)
downloadtextadept-1a33e9b5b719c6a10ac2b5a37baaae560b0cd4cf.tar.gz
textadept-1a33e9b5b719c6a10ac2b5a37baaae560b0cd4cf.zip
Implement \U, \L, \u, and \l case transformations in regex replacements.
-rw-r--r--docs/manual.md12
-rw-r--r--modules/textadept/find.lua25
-rw-r--r--test/test.lua39
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)