/*
* Copyright (C) 2012-2013 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include "sciteco.h"
#include "expressions.h"
#include "undo.h"
#include "qregisters.h"
#include "ring.h"
#include "parser.h"
#include "search.h"
namespace States {
StateSearch search;
StateSearchAll searchall;
StateSearchKill searchkill;
StateSearchDelete searchdelete;
StateReplace replace;
StateReplace_insert replace_insert;
StateReplace_ignore replace_ignore;
StateReplaceDefault replacedefault;
StateReplaceDefault_insert replacedefault_insert;
StateReplaceDefault_ignore replacedefault_ignore;
}
/*
* Command states
*/
void
StateSearch::initial(void) throw (Error)
{
tecoInt v1, v2;
undo.push_var(parameters);
parameters.dot = interface.ssm(SCI_GETCURRENTPOS);
v2 = expressions.pop_num_calc();
if (expressions.args()) {
/* TODO: optional count argument? */
v1 = expressions.pop_num_calc();
if (v1 <= v2) {
parameters.count = 1;
parameters.from = (gint)v1;
parameters.to = (gint)v2;
} else {
parameters.count = -1;
parameters.from = (gint)v2;
parameters.to = (gint)v1;
}
if (!Validate::pos(parameters.from) ||
!Validate::pos(parameters.to))
throw RangeError("S");
} else {
parameters.count = (gint)v2;
if (v2 >= 0) {
parameters.from = parameters.dot;
parameters.to = interface.ssm(SCI_GETLENGTH);
} else {
parameters.from = 0;
parameters.to = parameters.dot;
}
}
parameters.from_buffer = ring.current;
parameters.to_buffer = NULL;
}
static inline const gchar *
regexp_escape_chr(gchar chr)
{
static gchar escaped[] = {'\\', '\0', '\0'};
escaped[1] = chr;
return g_ascii_isalnum(chr) ? escaped + 1 : escaped;
}
gchar *
StateSearch::class2regexp(MatchState &state, const gchar *&pattern,
bool escape_default)
{
while (*pattern) {
QRegister *reg;
gchar *temp, *temp2;
switch (state) {
case STATE_START:
switch (*pattern) {
case CTL_KEY('S'):
return g_strdup("[:^alnum:]");
case CTL_KEY('E'):
state = STATE_CTL_E;
break;
default:
temp = escape_default
? g_strdup(regexp_escape_chr(*pattern))
: NULL;
return temp;
}
break;
case STATE_CTL_E:
switch (g_ascii_toupper(*pattern)) {
case 'A':
state = STATE_START;
return g_strdup("[:alpha:]");
/* same as */
case 'B':
state = STATE_START;
return g_strdup("[:^alnum:]");
case 'C':
state = STATE_START;
return g_strdup("[:alnum:].$");
case 'D':
state = STATE_START;
return g_strdup("[:digit:]");
case 'G':
state = STATE_ANYQ;
break;
case 'L':
state = STATE_START;
return g_strdup("\r\n\v\f");
case 'R':
state = STATE_START;
return g_strdup("[:alnum:]");
case 'V':
state = STATE_START;
return g_strdup("[:lower:]");
case 'W':
state = STATE_START;
return g_strdup("[:upper:]");
default:
return NULL;
}
break;
case STATE_ANYQ:
/* FIXME: Q-Register spec might get more complicated */
reg = QRegisters::globals[g_ascii_toupper(*pattern)];
if (!reg)
return NULL;
temp = reg->get_string();
temp2 = g_regex_escape_string(temp, -1);
g_free(temp);
state = STATE_START;
return temp2;
default:
return NULL;
}
pattern++;
}
return NULL;
}
gchar *
StateSearch::pattern2regexp(const gchar *&pattern,
bool single_expr)
{
MatchState state = STATE_START;
gchar *re = NULL;
while (*pattern) {
gchar *new_re, *temp;
temp = class2regexp(state, pattern);
if (temp) {
new_re = g_strconcat(re ? : "", "[", temp, "]", NULL);
g_free(temp);
g_free(re);
re = new_re;
goto next;
}
if (!*pattern)
break;
switch (state) {
case STATE_START:
switch (*pattern) {
case CTL_KEY('X'): String::append(re, "."); break;
case CTL_KEY('N'): state = STATE_NOT; break;
default:
String::append(re, regexp_escape_chr(*pattern));
}
break;
case STATE_NOT:
state = STATE_START;
temp = class2regexp(state, pattern, true);
if (!temp)
goto error;
new_re = g_strconcat(re ? : "", "[^", temp, "]", NULL);
g_free(temp);
g_free(re);
re = new_re;
g_assert(state == STATE_START);
break;
case STATE_CTL_E:
state = STATE_START;
switch (g_ascii_toupper(*pattern)) {
case 'M': state = STATE_MANY; break;
case 'S': String::append(re, "\\s+"); break;
/* same as */
case 'X': String::append(re, "."); break;
/* TODO: ASCII octal code!? */
case '[':
String::append(re, "(");
state = STATE_ALT;
break;
default:
goto error;
}
break;
case STATE_MANY:
temp = pattern2regexp(pattern, true);
if (!temp)
goto error;
new_re = g_strconcat(re ? : "", "(", temp, ")+", NULL);
g_free(temp);
g_free(re);
re = new_re;
state = STATE_START;
break;
case STATE_ALT:
switch (*pattern) {
case ',':
String::append(re, "|");
break;
case ']':
String::append(re, ")");
state = STATE_START;
break;
default:
temp = pattern2regexp(pattern, true);
if (!temp)
goto error;
String::append(re, temp);
g_free(temp);
}
break;
default:
/* shouldn't happen */
g_assert(true);
}
next:
if (single_expr && state == STATE_START)
return re;
pattern++;
}
if (state == STATE_ALT)
String::append(re, ")");
return re;
error:
g_free(re);
return NULL;
}
void
StateSearch::do_search(GRegex *re, gint from, gint to, gint &count)
{
GMatchInfo *info;
const gchar *buffer;
gint matched_from = -1, matched_to = -1;
buffer = (const gchar *)interface.ssm(SCI_GETCHARACTERPOINTER);
g_regex_match_full(re, buffer, (gssize)to, from,
(GRegexMatchFlags)0, &info, NULL);
if (count >= 0) {
while (g_match_info_matches(info) && --count)
g_match_info_next(info, NULL);
if (!count)
/* successful */
g_match_info_fetch_pos(info, 0,
&matched_from, &matched_to);
} else {
/* only keep the last `count' matches, in a circular stack */
struct Range {
gint from, to;
};
Range *matched = new Range[-count];
gint matched_total = 0, i = 0;
while (g_match_info_matches(info)) {
g_match_info_fetch_pos(info, 0,
&matched[i].from,
&matched[i].to);
g_match_info_next(info, NULL);
i = ++matched_total % -count;
}
count = MIN(count + matched_total, 0);
if (!count) {
/* successful, i points to stack bottom */
matched_from = matched[i].from;
matched_to = matched[i].to;
}
delete matched;
}
g_match_info_free(info);
if (matched_from >= 0 && matched_to >= 0)
/* match success */
interface.ssm(SCI_SETSEL, matched_from, matched_to);
}
void
StateSearch::process(const gchar *str,
gint new_chars __attribute__((unused))) throw (Error)
{
static const gint flags = G_REGEX_CASELESS | G_REGEX_MULTILINE |
G_REGEX_DOTALL | G_REGEX_RAW;
QRegister *search_reg = QRegisters::globals["_"];
gchar *re_pattern;
GRegex *re;
gint count = parameters.count;
undo.push_msg(SCI_SETSEL,
interface.ssm(SCI_GETANCHOR),
interface.ssm(SCI_GETCURRENTPOS));
search_reg->undo_set_integer();
search_reg->set_integer(FAILURE);
/* NOTE: pattern2regexp() modifies str pointer */
re_pattern = pattern2regexp(str);
#ifdef DEBUG
g_printf("REGEXP: %s\n", re_pattern);
#endif
if (!re_pattern)
goto failure;
re = g_regex_new(re_pattern, (GRegexCompileFlags)flags,
(GRegexMatchFlags)0, NULL);
g_free(re_patteHTTP/1.1 200 OK
Connection: keep-alive
Connection: keep-alive
Content-Disposition: inline; filename="search.cpp"
Content-Disposition: inline; filename="search.cpp"
Content-Length: 13931
Content-Length: 13931
Content-Security-Policy: default-src 'none'
Content-Security-Policy: default-src 'none'
Content-Type: text/plain; charset=UTF-8
Content-Type: text/plain; charset=UTF-8
Date: Mon, 20 Oct 2025 19:11:24 UTC
ETag: "38aa011dadb8e033776b8fd60781562310ac886b"
ETag: "38aa011dadb8e033776b8fd60781562310ac886b"
Expires: Thu, 18 Oct 2035 19:11:23 GMT
Expires: Thu, 18 Oct 2035 19:11:24 GMT
Last-Modified: Mon, 20 Oct 2025 19:11:23 GMT
Last-Modified: Mon, 20 Oct 2025 19:11:24 GMT
Server: OpenBSD httpd
Server: OpenBSD httpd
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
/*
* Copyright (C) 2012-2013 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include "sciteco.h"
#include "expressions.h"
#include "undo.h"
#include "qregisters.h"
#include "ring.h"
#include "parser.h"
#include "search.h"
namespace States {
StateSearch search;
StateSearchAll searchall;
StateSearchKill searchkill;
StateSearchDelete searchdelete;
StateReplace replace;
StateReplace_insert replace_insert;
StateReplace_ignore replace_ignore;
StateReplaceDefault replacedefault;
StateReplaceDefault_insert replacedefault_insert;
StateReplaceDefault_ignore replacedefault_ignore;
}
/*
* Command states
*/
void
StateSearch::initial(void) throw (Error)
{
tecoInt v1, v2;
undo.push_var(parameters);
parameters.dot = interface.ssm(SCI_GETCURRENTPOS);
v2 = expressions.pop_num_calc();
if (expressions.args()) {
/* TODO: optional count argument? */
v1 = expressions.pop_num_calc();
if (v1 <= v2) {
parameters.count = 1;
parameters.from = (gint)v1;
parameters.to = (gint)v2;
} else {
parameters.count = -1;
parameters.from = (gint)v2;
parameters.to = (gint)v1;
}
if (!Validate::pos(parameters.from) ||
!Validate::pos(parameters.to))
throw RangeError("S");
} else {
parameters.count = (gint)v2;
if (v2 >= 0) {
parameters.from = parameters.dot;
parameters.to = interface.ssm(SCI_GETLENGTH);
} else {
parameters.from = 0;
parameters.to = parameters.dot;
}
}
parameters.from_buffer = ring.current;
parameters.to_buffer = NULL;
}
static inline const gchar *
regexp_escape_chr(gchar chr)
{
static gchar escaped[] = {'\\', '\0', '\0'};
escaped[1] = chr;
return g_ascii_isalnum(chr) ? escaped + 1 : escaped;
}
gchar *
StateSearch::class2regexp(MatchState &state, const gchar *&pattern,
bool escape_default)
{
while (*pattern) {
QRegister *reg;
gchar *temp, *temp2;
switch (state) {
case STATE_START:
switch (*pattern) {
case CTL_KEY('S'):
return g_strdup("[:^alnum:]");
case CTL_KEY('E'):
state = STATE_CTL_E;
break;
default:
temp = escape_default
? g_strdup(regexp_escape_chr(*pattern))
: NULL;
return temp;
}
break;
case STATE_CTL_E:
switch (g_ascii_toupper(*pattern)) {
case 'A':
state = STATE_START;
return g_strdup("[:alpha:]");
/* same as */
case 'B':
state = STATE_START;
return g_strdup("[:^alnum:]");
case 'C':
state = STATE_START;
return g_strdup("[:alnum:].$");
case 'D':
state = STATE_START;
return g_strdup("[:digit:]");
case 'G':
state = STATE_ANYQ;
break;
case 'L':
state = STATE_START;
return g_strdup("\r\n\v\f");
case 'R':
state = STATE_START;
return g_strdup("[:alnum:]");
case 'V':
state = STATE_START;
return g_strdup("[:lower:]");
case 'W':
state = STATE_START;
return g_strdup("[:upper:]");
default:
return NULL;
}
break;
case STATE_ANYQ:
/* FIXME: Q-Register spec might get more complicated */
reg = QRegisters::globals[g_ascii_toupper(*pattern)];
if (!reg)
return NULL;
temp = reg->get_string();
temp2 = g_regex_escape_string(temp, -1);
g_free(temp);
state = STATE_START;
return temp2;
default:
return NULL;
}
pattern++;
}
return NULL;
}
gchar *
StateSearch::pattern2regexp(const gchar *&pattern,
bool single_expr)
{
MatchState state = STATE_START;
gchar *re = NULL;
while (*pattern) {
gchar *new_re, *temp;
temp = class2regexp(state, pattern);
if (temp) {
new_re = g_strconcat(re ? : "", "[", temp, "]", NULL);
g_free(temp);
g_free(re);
re = new_re;
goto next;
}
if (!*pattern)
break;
switch (state) {
case STATE_START:
switch (*pattern) {
case CTL_KEY('X'): String::append(re, "."); break;
case CTL_KEY('N'): state = STATE_NOT; break;
default:
String::append(re, regexp_escape_chr(*pattern));
}
break;
case STATE_NOT:
state = STATE_START;
temp = class2regexp(state, pattern, true);
if (!temp)
goto error;
new_re = g_strconcat(re ? : "", "[^", temp, "]", NULL);
g_free(temp);
g_free(re);
re = new_re;
g_assert(state == STATE_START);