From 0c9e5177914fc8283eba61e5aa4be1d95d4a69aa Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 28 Apr 2021 00:22:26 -0400 Subject: [PATCH 01/12] Initial commit of PCRE engine. --- build.sh | 2 +- data/core/init.lua | 1 + data/core/regex.lua | 25 ++++++++++++++ src/api/api.c | 2 ++ src/api/regex.c | 79 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 data/core/regex.lua create mode 100644 src/api/regex.c diff --git a/build.sh b/build.sh index bfb679b0..6f605797 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ cflags+="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc -Ilib/font_renderer" cflags+=" $(pkg-config --cflags lua5.2) $(sdl2-config --cflags)" lflags="-static-libgcc -static-libstdc++" -for package in libagg freetype2 lua5.2 x11; do +for package in libagg freetype2 lua5.2 x11 libpcre2-8; do lflags+=" $(pkg-config --libs $package)" done lflags+=" $(sdl2-config --libs) -lm" diff --git a/data/core/init.lua b/data/core/init.lua index 8d4d84c3..1db71164 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1,4 +1,5 @@ require "core.strict" +require "core.regex" local common = require "core.common" local config = require "core.config" local style = require "core.style" diff --git a/data/core/regex.lua b/data/core/regex.lua new file mode 100644 index 00000000..a8873a5f --- /dev/null +++ b/data/core/regex.lua @@ -0,0 +1,25 @@ + +-- So that in addition to regex.gsub(pattern, string), we can also do pattern:gsub(string). +regex.__index = function(table, key) return regex[key]; end + +regex.match = function(pattern_string, string) + local pattern = type(pattern_string) == "userdata" and pattern_string or regex.compile(pattern_string) + return regex.cmatch(pattern, string) +end + +-- Build off matching. For now, only support basic replacements, but capture groupings should be doable. +-- We can even have custom group replacements and transformations and stuff in lua. +regex.gsub = function(pattern_string, string, replacement) + local pattern = type(pattern_string) == "userdata" and pattern_string or regex.compile(pattern_string) + local offset, result, str, indices = 0, "", string + repeat + str = str:sub(offset) + indices = { regex.cmatch(pattern, str) } + if #indices > 0 then + result = result .. str:sub(offset, indices[1] - 1) .. replacement + offset = indices[2] + end + until #indices == 0 or indices[1] == indices[2] + return result .. str:sub(offset - 1) +end + diff --git a/src/api/api.c b/src/api/api.c index 34067a9c..5ea2e782 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -3,11 +3,13 @@ int luaopen_system(lua_State *L); int luaopen_renderer(lua_State *L); +int luaopen_regex(lua_State *L); static const luaL_Reg libs[] = { { "system", luaopen_system }, { "renderer", luaopen_renderer }, + { "regex", luaopen_regex }, { NULL, NULL } }; diff --git a/src/api/regex.c b/src/api/regex.c new file mode 100644 index 00000000..0392d783 --- /dev/null +++ b/src/api/regex.c @@ -0,0 +1,79 @@ +#include "api.h" + +#define PCRE2_CODE_UNIT_WIDTH 8 + +#include +#include + +static int f_pcre_gc(lua_State* L) { + lua_rawgeti(L, -1, 1); + pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1); + if (re) + pcre2_code_free(re); + return 0; +} + +static int f_pcre_compile(lua_State *L) { + size_t len; + PCRE2_SIZE errorOffset; + int errorNumber; + const char* str = luaL_checklstring(L, -1, &len); + pcre2_code* re = pcre2_compile((PCRE2_SPTR)str, len, 0, &errorNumber, &errorOffset, NULL); + if (re) { + lua_newtable(L); + lua_pushlightuserdata(L, re); + lua_rawseti(L, -2, 1); + lua_getfield(L, LUA_REGISTRYINDEX, "regex"); + lua_setmetatable(L, -2); + return 1; + } + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(errorNumber, buffer, sizeof(buffer)); + luaL_error(L, "regex compilation failed at offset %d: %s", (int)errorOffset, buffer); + return 0; +} + +// Takes string, compiled regex, returns list of indices of matched groups (including the whole match), if a match was found. +static int f_pcre_match(lua_State *L) { + size_t len; + const char* str = luaL_checklstring(L, -1, &len); + luaL_checktype(L, -2, LUA_TTABLE); + lua_rawgeti(L, -2, 1); + pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1); + pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(re, NULL); + int rc = pcre2_match(re, (PCRE2_SPTR)str, len, 0, 0, match_data, NULL); + if (rc < 0) { + pcre2_match_data_free(match_data); + if (rc != PCRE2_ERROR_NOMATCH) + luaL_error(L, "regex matching error %d", rc); + return 0; + } + PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data); + if (ovector[0] > ovector[1]) { + /* We must guard against patterns such as /(?=.\K)/ that use \K in an assertion + to set the start of a match later than its end. In the editor, we just detect this case and give up. */ + luaL_error(L, "regex matching error: \\K was used in an assertion to set the match start after its end"); + pcre2_match_data_free(match_data); + return 0; + } + for (int i = 0; i < rc*2; i++) + lua_pushnumber(L, ovector[i]+1); + pcre2_match_data_free(match_data); + return rc*2; +} + +static const luaL_Reg lib[] = { + { "compile", f_pcre_compile }, + { "cmatch", f_pcre_match }, + { "__gc", f_pcre_gc }, + { NULL, NULL } +}; + +int luaopen_regex(lua_State *L) { + luaL_newlib(L, lib); + lua_pushliteral(L, "regex"); + lua_setfield(L, -2, "__name"); + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, "regex"); + return 1; +} From 488ebba7e4317871fb652723193a4df9de3695a4 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 28 Apr 2021 17:41:43 -0400 Subject: [PATCH 02/12] Added in dependency. --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index 5b9576d4..ab9c4e74 100644 --- a/meson.build +++ b/meson.build @@ -9,6 +9,7 @@ libm = cc.find_library('m', required : false) libdl = cc.find_library('dl', required : false) libx11 = dependency('x11', required : false) lua_dep = dependency('lua5.2', required : false) +pcre2_dep = dependency('libpcre2', required : false) if not lua_dep.found() lua_subproject = subproject('lua', default_options: ['shared=false', 'use_readline=false', 'app=false']) From a9dd790321b044b969f34dc75be7d21eacd9bee4 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 28 Apr 2021 18:18:30 -0400 Subject: [PATCH 03/12] Reworked files to conform to style guidelines. --- data/core/regex.lua | 14 +++++++++----- src/api/regex.c | 38 ++++++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/data/core/regex.lua b/data/core/regex.lua index a8873a5f..ae74c83b 100644 --- a/data/core/regex.lua +++ b/data/core/regex.lua @@ -1,16 +1,20 @@ --- So that in addition to regex.gsub(pattern, string), we can also do pattern:gsub(string). +-- So that in addition to regex.gsub(pattern, string), we can also do +-- pattern:gsub(string). regex.__index = function(table, key) return regex[key]; end regex.match = function(pattern_string, string) - local pattern = type(pattern_string) == "userdata" and pattern_string or regex.compile(pattern_string) + local pattern = type(pattern_string) == "userdata" and + pattern_string or regex.compile(pattern_string) return regex.cmatch(pattern, string) end --- Build off matching. For now, only support basic replacements, but capture groupings should be doable. --- We can even have custom group replacements and transformations and stuff in lua. +-- Build off matching. For now, only support basic replacements, but capture +-- groupings should be doable. We can even have custom group replacements and +-- transformations and stuff in lua. regex.gsub = function(pattern_string, string, replacement) - local pattern = type(pattern_string) == "userdata" and pattern_string or regex.compile(pattern_string) + local pattern = type(pattern_string) == "userdata" and + pattern_string or regex.compile(pattern_string) local offset, result, str, indices = 0, "", string repeat str = str:sub(offset) diff --git a/src/api/regex.c b/src/api/regex.c index 0392d783..312a25bc 100644 --- a/src/api/regex.c +++ b/src/api/regex.c @@ -18,47 +18,57 @@ static int f_pcre_compile(lua_State *L) { PCRE2_SIZE errorOffset; int errorNumber; const char* str = luaL_checklstring(L, -1, &len); - pcre2_code* re = pcre2_compile((PCRE2_SPTR)str, len, 0, &errorNumber, &errorOffset, NULL); + pcre2_code* re = pcre2_compile( + (PCRE2_SPTR)str, + len, + 0, + &errorNumber, + &errorOffset, + NULL + ); if (re) { lua_newtable(L); lua_pushlightuserdata(L, re); lua_rawseti(L, -2, 1); - lua_getfield(L, LUA_REGISTRYINDEX, "regex"); - lua_setmetatable(L, -2); + luaL_setmetatable(L, "regex"); return 1; } PCRE2_UCHAR buffer[256]; pcre2_get_error_message(errorNumber, buffer, sizeof(buffer)); - luaL_error(L, "regex compilation failed at offset %d: %s", (int)errorOffset, buffer); + luaL_error(L, "regex compilation failed at offset %d: %s", + (int)errorOffset, buffer); return 0; } -// Takes string, compiled regex, returns list of indices of matched groups (including the whole match), if a match was found. +// Takes string, compiled regex, returns list of indices of matched groups +// (including the whole match), if a match was found. static int f_pcre_match(lua_State *L) { size_t len; const char* str = luaL_checklstring(L, -1, &len); luaL_checktype(L, -2, LUA_TTABLE); lua_rawgeti(L, -2, 1); pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1); - pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(re, NULL); - int rc = pcre2_match(re, (PCRE2_SPTR)str, len, 0, 0, match_data, NULL); + pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL); + int rc = pcre2_match(re, (PCRE2_SPTR)str, len, 0, 0, md, NULL); if (rc < 0) { - pcre2_match_data_free(match_data); + pcre2_match_data_free(md); if (rc != PCRE2_ERROR_NOMATCH) luaL_error(L, "regex matching error %d", rc); return 0; } - PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data); + PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(md); if (ovector[0] > ovector[1]) { - /* We must guard against patterns such as /(?=.\K)/ that use \K in an assertion - to set the start of a match later than its end. In the editor, we just detect this case and give up. */ - luaL_error(L, "regex matching error: \\K was used in an assertion to set the match start after its end"); - pcre2_match_data_free(match_data); + /* We must guard against patterns such as /(?=.\K)/ that use \K in an + assertion to set the start of a match later than its end. In the editor, + we just detect this case and give up. */ + luaL_error(L, "regex matching error: \\K was used in an assertion to " + " set the match start after its end"); + pcre2_match_data_free(md); return 0; } for (int i = 0; i < rc*2; i++) lua_pushnumber(L, ovector[i]+1); - pcre2_match_data_free(match_data); + pcre2_match_data_free(md); return rc*2; } From 272067124df824325a022255c289042e5d2881da Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 1 May 2021 01:31:49 -0400 Subject: [PATCH 04/12] Updated PCRE to use utf8, and created sample plugin. --- data/core/regex.lua | 64 +++++++++++++++++++++-------- data/plugins/regex_find_replace.lua | 53 ++++++++++++++++++++++++ src/api/regex.c | 14 ++++++- 3 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 data/plugins/regex_find_replace.lua diff --git a/data/core/regex.lua b/data/core/regex.lua index ae74c83b..83ba9378 100644 --- a/data/core/regex.lua +++ b/data/core/regex.lua @@ -4,26 +4,58 @@ regex.__index = function(table, key) return regex[key]; end regex.match = function(pattern_string, string) - local pattern = type(pattern_string) == "userdata" and + local pattern = type(pattern_string) == "table" and pattern_string or regex.compile(pattern_string) return regex.cmatch(pattern, string) end --- Build off matching. For now, only support basic replacements, but capture --- groupings should be doable. We can even have custom group replacements and --- transformations and stuff in lua. -regex.gsub = function(pattern_string, string, replacement) - local pattern = type(pattern_string) == "userdata" and - pattern_string or regex.compile(pattern_string) - local offset, result, str, indices = 0, "", string +-- Will iterate back through any UTF-8 bytes so that we don't replace bits mid character. +local function previous_character(str, index) + local byte repeat - str = str:sub(offset) - indices = { regex.cmatch(pattern, str) } - if #indices > 0 then - result = result .. str:sub(offset, indices[1] - 1) .. replacement - offset = indices[2] - end - until #indices == 0 or indices[1] == indices[2] - return result .. str:sub(offset - 1) + index = index - 1 + byte = string.byte(str, index) + until byte < 128 or byte >= 192 + return index +end + +-- Moves to the end of the identified character. +local function end_character(str, index) + local byte = string.byte(str, index + 1) + while byte >= 128 and byte < 192 do + index = index + 1 + byte = string.byte(str, index + 1) + end + return index +end + +-- Build off matching. For now, only support basic replacements, but capture +-- groupings should be doable. We can even have custom group replacements and +-- transformations and stuff in lua. Currently, this takes group replacements +-- as \1 - \9. +-- Should work on UTF-8 text. +regex.gsub = function(pattern_string, string, replacement) + local pattern = type(pattern_string) == "table" and + pattern_string or regex.compile(pattern_string) + local result, str, indices = "", string + repeat + indices = { regex.cmatch(pattern, str) } + if #indices > 0 then + local currentReplacement = replacement + if #indices > 2 then + for i = 1, (#indices/2 - 1) do + currentReplacement = string.gsub(currentReplacement, "\\" .. i, str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))) + end + end + currentReplacement = string.gsub(currentReplacement, "\\%d", "") + if indices[1] > 1 then + result = result .. str:sub(1, previous_character(str, indices[1])) .. currentReplacement + else + result = result .. currentReplacement + end + str = str:sub(indices[2]) + end + until #indices == 0 or indices[1] == indices[2] + return result .. str end diff --git a/data/plugins/regex_find_replace.lua b/data/plugins/regex_find_replace.lua new file mode 100644 index 00000000..c0305096 --- /dev/null +++ b/data/plugins/regex_find_replace.lua @@ -0,0 +1,53 @@ +-- lite-xl 1.16 + +local core = require "core" +local command = require "core.command" + +-- Takes the following pattern: /pattern/replace/ +-- Capture groupings can be replaced using \1 through \9 +local function regex_replace_file(pattern) + local doc = core.active_view.doc + + local start_pattern = 2; + local end_pattern = 2 + repeat + end_pattern = string.find(pattern, "/", end_pattern) + until end_pattern == nil or pattern[end_pattern-1] ~= "\\" + if end_pattern == nil then + core.log("Can't find end to pattern.") + return + end + end_pattern = end_pattern - 1 + local start_replacement = end_pattern+2; + local end_replacement = end_pattern+2; + repeat + end_replacement = string.find(pattern, "/", end_replacement) + until end_replacement == nil or pattern[end_replacement-1] ~= "\\" + if end_replacement == nil then + core.log("Can't find end to replacement.") + return + end + end_replacement = end_replacement - 1 + + local re = regex.compile(pattern:sub(start_pattern, end_pattern)) + local replacement = pattern:sub(start_replacement, end_replacement) + for i=1,#doc.lines do + local old_length = #doc.lines[i] + local old_text = doc:get_text(i, 1, i, old_length) + local new_text = regex.gsub(re, old_text, replacement) + if old_text ~= new_text then + doc:insert(i, old_length, new_text) + doc:remove(i, 1, i, old_length) + end + end +end + +local initial_regex_replace = "/" +command.add("core.docview", { + ["regex:find-replace"] = function() + core.command_view:set_text(initial_regex_replace) + core.command_view:enter("Regex Replace (enter pattern as /old/new/)", function(pattern) + regex_replace_file(pattern) + initial_regex_replace = pattern + end) end +}) diff --git a/src/api/regex.c b/src/api/regex.c index 312a25bc..b30a806d 100644 --- a/src/api/regex.c +++ b/src/api/regex.c @@ -17,11 +17,21 @@ static int f_pcre_compile(lua_State *L) { size_t len; PCRE2_SIZE errorOffset; int errorNumber; - const char* str = luaL_checklstring(L, -1, &len); + int pattern = PCRE2_UTF; + const char* str = luaL_checklstring(L, 1, &len); + if (lua_gettop(L) > 1) { + const char* options = luaL_checkstring(L, 2); + if (strstr(options,"i")) + pattern |= PCRE2_CASELESS; + if (strstr(options,"m")) + pattern |= PCRE2_MULTILINE; + if (strstr(options,"s")) + pattern |= PCRE2_DOTALL; + } pcre2_code* re = pcre2_compile( (PCRE2_SPTR)str, len, - 0, + PCRE2_UTF, &errorNumber, &errorOffset, NULL From a66e1f15598c96e8e0d97eb914db806b2c3d4959 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 1 May 2021 01:33:01 -0400 Subject: [PATCH 05/12] Added in keyboard shortcut. --- data/plugins/regex_find_replace.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/plugins/regex_find_replace.lua b/data/plugins/regex_find_replace.lua index c0305096..7d804f58 100644 --- a/data/plugins/regex_find_replace.lua +++ b/data/plugins/regex_find_replace.lua @@ -2,6 +2,7 @@ local core = require "core" local command = require "core.command" +local keymap = require "core.keymap" -- Takes the following pattern: /pattern/replace/ -- Capture groupings can be replaced using \1 through \9 @@ -51,3 +52,6 @@ command.add("core.docview", { initial_regex_replace = pattern end) end }) + + +keymap.add { ["ctrl+shift+r"] = "regex:find-replace" } From bf09582478a472942688d1f5f01a251de5f6caeb Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 1 May 2021 01:46:06 -0400 Subject: [PATCH 06/12] Fixed lines to conform to style guidelines. --- data/core/regex.lua | 12 +++++++++--- data/plugins/regex_find_replace.lua | 12 ++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/data/core/regex.lua b/data/core/regex.lua index 83ba9378..7704c14e 100644 --- a/data/core/regex.lua +++ b/data/core/regex.lua @@ -9,7 +9,8 @@ regex.match = function(pattern_string, string) return regex.cmatch(pattern, string) end --- Will iterate back through any UTF-8 bytes so that we don't replace bits mid character. +-- Will iterate back through any UTF-8 bytes so that we don't replace bits +-- mid character. local function previous_character(str, index) local byte repeat @@ -44,12 +45,17 @@ regex.gsub = function(pattern_string, string, replacement) local currentReplacement = replacement if #indices > 2 then for i = 1, (#indices/2 - 1) do - currentReplacement = string.gsub(currentReplacement, "\\" .. i, str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))) + currentReplacement = string.gsub( + currentReplacement, + "\\" .. i, + str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1)) + ) end end currentReplacement = string.gsub(currentReplacement, "\\%d", "") if indices[1] > 1 then - result = result .. str:sub(1, previous_character(str, indices[1])) .. currentReplacement + result = result .. + str:sub(1, previous_character(str, indices[1])) .. currentReplacement else result = result .. currentReplacement end diff --git a/data/plugins/regex_find_replace.lua b/data/plugins/regex_find_replace.lua index 7d804f58..65eea074 100644 --- a/data/plugins/regex_find_replace.lua +++ b/data/plugins/regex_find_replace.lua @@ -47,10 +47,14 @@ local initial_regex_replace = "/" command.add("core.docview", { ["regex:find-replace"] = function() core.command_view:set_text(initial_regex_replace) - core.command_view:enter("Regex Replace (enter pattern as /old/new/)", function(pattern) - regex_replace_file(pattern) - initial_regex_replace = pattern - end) end + core.command_view:enter( + "Regex Replace (enter pattern as /old/new/)", + function(pattern) + regex_replace_file(pattern) + initial_regex_replace = pattern + end + ) + end }) From 57b1adcec2ee8233c62802d484125fd19aa4dc76 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 1 May 2021 01:52:41 -0400 Subject: [PATCH 07/12] Fixed pattern variable. --- src/api/regex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/regex.c b/src/api/regex.c index b30a806d..ba3dddac 100644 --- a/src/api/regex.c +++ b/src/api/regex.c @@ -31,7 +31,7 @@ static int f_pcre_compile(lua_State *L) { pcre2_code* re = pcre2_compile( (PCRE2_SPTR)str, len, - PCRE2_UTF, + pattern, &errorNumber, &errorOffset, NULL From b3f9faff4a289ff28082bdba8c738ce11954232a Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 1 May 2021 16:20:28 +0200 Subject: [PATCH 08/12] Fix pcre2 meson build config --- meson.build | 2 +- src/meson.build | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index ab9c4e74..c0857769 100644 --- a/meson.build +++ b/meson.build @@ -9,7 +9,7 @@ libm = cc.find_library('m', required : false) libdl = cc.find_library('dl', required : false) libx11 = dependency('x11', required : false) lua_dep = dependency('lua5.2', required : false) -pcre2_dep = dependency('libpcre2', required : false) +pcre2_dep = dependency('libpcre2-8') if not lua_dep.found() lua_subproject = subproject('lua', default_options: ['shared=false', 'use_readline=false', 'app=false']) diff --git a/src/meson.build b/src/meson.build index 881014be..faa1a8ea 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,6 +3,7 @@ lite_sources = [ 'api/cp_replace.c', 'api/renderer.c', 'api/renderer_font.c', + 'api/regex.c', 'api/system.c', 'renderer.c', 'renwindow.c', @@ -18,7 +19,7 @@ endif executable('lite', lite_sources + lite_rc, include_directories: [lite_include, font_renderer_include], - dependencies: [lua_dep, sdl_dep, libm, libdl, libx11], + dependencies: [lua_dep, sdl_dep, pcre2_dep, libm, libdl, libx11], c_args: lite_cargs, link_with: libfontrenderer, link_args: lite_link_args, From c0c6bc5be9d412d34d263d6371384b3b3722c9fb Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 4 May 2021 18:31:46 -0400 Subject: [PATCH 09/12] Replaced pattern-based commands with regexes. --- data/core/commands/findreplace.lua | 14 +++++++----- data/core/doc/search.lua | 36 ++++++++++++++++++------------ data/core/regex.lua | 5 +++-- data/plugins/projectsearch.lua | 9 +++++--- src/api/regex.c | 12 +++++----- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/data/core/commands/findreplace.lua b/data/core/commands/findreplace.lua index 937c410a..af60f33f 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -90,6 +90,7 @@ local function has_selection() and core.active_view.doc:has_selection() end + command.add(has_selection, { ["find-replace:select-next"] = function() local l1, c1, l2, c2 = doc():get_selection(true) @@ -107,9 +108,9 @@ command.add("core.docview", { end) end, - ["find-replace:find-pattern"] = function() - find("Find Text Pattern", function(doc, line, col, text) - local opt = { wrap = true, no_case = true, pattern = true } + ["find-replace:find-regex"] = function() + find("Find Text Regex", function(doc, line, col, text) + local opt = { wrap = true, no_case = true, regex = true } return search.find(doc, line, col, text, opt) end) end, @@ -144,9 +145,10 @@ command.add("core.docview", { end) end, - ["find-replace:replace-pattern"] = function() - replace("Pattern", "", function(text, old, new) - return text:gsub(old, new) + ["find-replace:replace-regex"] = function() + replace("Regex", "", function(text, old, new) + local re = regex.compile(old) + return regex.gsub(re, text, new) end) end, diff --git a/data/core/doc/search.lua b/data/core/doc/search.lua index fe57523e..3fc19c70 100644 --- a/data/core/doc/search.lua +++ b/data/core/doc/search.lua @@ -15,12 +15,8 @@ local function init_args(doc, line, col, text, opt) opt = opt or default_opt line, col = doc:sanitize_position(line, col) - if opt.no_case then - if opt.pattern then - text = text:gsub("%%?.", pattern_lower) - else - text = text:lower() - end + if opt.no_case and not opt.regex then + text = text:lower() end return doc, line, col, text, opt @@ -30,20 +26,32 @@ end function search.find(doc, line, col, text, opt) doc, line, col, text, opt = init_args(doc, line, col, text, opt) + local re + if opt.regex then + re = regex.compile(text, opt.no_case and "i" or "") + end for line = line, #doc.lines do local line_text = doc.lines[line] - if opt.no_case then - line_text = line_text:lower() + if opt.regex then + local s, e = re:match(line_text, col, true) + if s then + return line, s, line, e + 1 + end + col = 1 + else + if opt.no_case then + line_text = line_text:lower() + end + local s, e = line_text:find(text, col, true) + if s then + return line, s, line, e + 1 + end + col = 1 end - local s, e = line_text:find(text, col, not opt.pattern) - if s then - return line, s, line, e + 1 - end - col = 1 end if opt.wrap then - opt = { no_case = opt.no_case, pattern = opt.pattern } + opt = { no_case = opt.no_case, regex = opt.regex } return search.find(doc, 1, 1, text, opt) end end diff --git a/data/core/regex.lua b/data/core/regex.lua index 7704c14e..3b343238 100644 --- a/data/core/regex.lua +++ b/data/core/regex.lua @@ -38,10 +38,11 @@ end regex.gsub = function(pattern_string, string, replacement) local pattern = type(pattern_string) == "table" and pattern_string or regex.compile(pattern_string) - local result, str, indices = "", string + local result, str, indices, n = "", string repeat indices = { regex.cmatch(pattern, str) } if #indices > 0 then + n = n + 1 local currentReplacement = replacement if #indices > 2 then for i = 1, (#indices/2 - 1) do @@ -62,6 +63,6 @@ regex.gsub = function(pattern_string, string, replacement) str = str:sub(indices[2]) end until #indices == 0 or indices[1] == indices[2] - return result .. str + return result .. str, n end diff --git a/data/plugins/projectsearch.lua b/data/plugins/projectsearch.lua index 4c1fca6d..e4d10fb3 100644 --- a/data/plugins/projectsearch.lua +++ b/data/plugins/projectsearch.lua @@ -229,9 +229,12 @@ command.add(nil, { end) end, - ["project-search:find-pattern"] = function() - core.command_view:enter("Find Pattern In Project", function(text) - begin_search(text, function(line_text) return line_text:find(text) end) + ["project-search:find-regex"] = function() + core.command_view:enter("Find Regex In Project", function(text) + local re = regex.compile(text, "i") + begin_search(text, function(line_text) + return regex.cmatch(re, line_text) + end) end) end, diff --git a/src/api/regex.c b/src/api/regex.c index ba3dddac..05df8eee 100644 --- a/src/api/regex.c +++ b/src/api/regex.c @@ -53,13 +53,15 @@ static int f_pcre_compile(lua_State *L) { // Takes string, compiled regex, returns list of indices of matched groups // (including the whole match), if a match was found. static int f_pcre_match(lua_State *L) { - size_t len; - const char* str = luaL_checklstring(L, -1, &len); - luaL_checktype(L, -2, LUA_TTABLE); - lua_rawgeti(L, -2, 1); + size_t len, offset = 0; + luaL_checktype(L, 1, LUA_TTABLE); + const char* str = luaL_checklstring(L, 2, &len); + if (lua_gettop(L) > 2) + offset = luaL_checknumber(L, 3); + lua_rawgeti(L, 1, 1); pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1); pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL); - int rc = pcre2_match(re, (PCRE2_SPTR)str, len, 0, 0, md, NULL); + int rc = pcre2_match(re, (PCRE2_SPTR)str, len, offset, 0, md, NULL); if (rc < 0) { pcre2_match_data_free(md); if (rc != PCRE2_ERROR_NOMATCH) From 88c5424e0bf7739c856fcd18d19a1bcb184f2b4f Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 4 May 2021 18:32:38 -0400 Subject: [PATCH 10/12] Removed unecessary plugin. --- data/plugins/regex_find_replace.lua | 61 ----------------------------- 1 file changed, 61 deletions(-) delete mode 100644 data/plugins/regex_find_replace.lua diff --git a/data/plugins/regex_find_replace.lua b/data/plugins/regex_find_replace.lua deleted file mode 100644 index 65eea074..00000000 --- a/data/plugins/regex_find_replace.lua +++ /dev/null @@ -1,61 +0,0 @@ --- lite-xl 1.16 - -local core = require "core" -local command = require "core.command" -local keymap = require "core.keymap" - --- Takes the following pattern: /pattern/replace/ --- Capture groupings can be replaced using \1 through \9 -local function regex_replace_file(pattern) - local doc = core.active_view.doc - - local start_pattern = 2; - local end_pattern = 2 - repeat - end_pattern = string.find(pattern, "/", end_pattern) - until end_pattern == nil or pattern[end_pattern-1] ~= "\\" - if end_pattern == nil then - core.log("Can't find end to pattern.") - return - end - end_pattern = end_pattern - 1 - local start_replacement = end_pattern+2; - local end_replacement = end_pattern+2; - repeat - end_replacement = string.find(pattern, "/", end_replacement) - until end_replacement == nil or pattern[end_replacement-1] ~= "\\" - if end_replacement == nil then - core.log("Can't find end to replacement.") - return - end - end_replacement = end_replacement - 1 - - local re = regex.compile(pattern:sub(start_pattern, end_pattern)) - local replacement = pattern:sub(start_replacement, end_replacement) - for i=1,#doc.lines do - local old_length = #doc.lines[i] - local old_text = doc:get_text(i, 1, i, old_length) - local new_text = regex.gsub(re, old_text, replacement) - if old_text ~= new_text then - doc:insert(i, old_length, new_text) - doc:remove(i, 1, i, old_length) - end - end -end - -local initial_regex_replace = "/" -command.add("core.docview", { - ["regex:find-replace"] = function() - core.command_view:set_text(initial_regex_replace) - core.command_view:enter( - "Regex Replace (enter pattern as /old/new/)", - function(pattern) - regex_replace_file(pattern) - initial_regex_replace = pattern - end - ) - end -}) - - -keymap.add { ["ctrl+shift+r"] = "regex:find-replace" } From a100a7b6a905e55f6052a639fe5b906d477020f9 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 6 May 2021 19:22:36 -0400 Subject: [PATCH 11/12] Fixed offsets, and passed through an additional parameter. --- data/core/doc/search.lua | 4 ++-- data/core/regex.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/core/doc/search.lua b/data/core/doc/search.lua index 3fc19c70..7fc85c41 100644 --- a/data/core/doc/search.lua +++ b/data/core/doc/search.lua @@ -33,9 +33,9 @@ function search.find(doc, line, col, text, opt) for line = line, #doc.lines do local line_text = doc.lines[line] if opt.regex then - local s, e = re:match(line_text, col, true) + local s, e = re:cmatch(line_text, col, true) if s then - return line, s, line, e + 1 + return line, s, line, e end col = 1 else diff --git a/data/core/regex.lua b/data/core/regex.lua index 3b343238..690e04b0 100644 --- a/data/core/regex.lua +++ b/data/core/regex.lua @@ -3,10 +3,10 @@ -- pattern:gsub(string). regex.__index = function(table, key) return regex[key]; end -regex.match = function(pattern_string, string) +regex.match = function(pattern_string, string, offset) local pattern = type(pattern_string) == "table" and pattern_string or regex.compile(pattern_string) - return regex.cmatch(pattern, string) + return regex.cmatch(pattern, string, offset) end -- Will iterate back through any UTF-8 bytes so that we don't replace bits From ba48cb1382b4b1bd60ec555cce13fe503903e241 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Fri, 7 May 2021 10:44:38 +0200 Subject: [PATCH 12/12] Fix problem in regex.gsub The local n was not initialized and the "string" argument was shadowing the Lua's "string" module. --- data/core/regex.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/data/core/regex.lua b/data/core/regex.lua index 690e04b0..a6ee0d5a 100644 --- a/data/core/regex.lua +++ b/data/core/regex.lua @@ -35,10 +35,11 @@ end -- transformations and stuff in lua. Currently, this takes group replacements -- as \1 - \9. -- Should work on UTF-8 text. -regex.gsub = function(pattern_string, string, replacement) - local pattern = type(pattern_string) == "table" and +regex.gsub = function(pattern_string, str, replacement) + local pattern = type(pattern_string) == "table" and pattern_string or regex.compile(pattern_string) - local result, str, indices, n = "", string + local result, indices = "" + local n = 0 repeat indices = { regex.cmatch(pattern, str) } if #indices > 0 then