aboutsummaryrefslogtreecommitdiffhomepage
path: root/search.cpp
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2012-12-03 17:09:13 +0100
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2012-12-03 17:09:13 +0100
commitaca9517b3d90180581570596cb5ac768ef8c127e (patch)
tree2f7bc6a0a180af4fb03bf6b3085d304b0a964302 /search.cpp
parent14246a1c5a8b542eadd19d74640d9e69c7df9b98 (diff)
downloadsciteco-aca9517b3d90180581570596cb5ac768ef8c127e.tar.gz
refactored search command (states): they are now in a separate search.cpp file
Diffstat (limited to 'search.cpp')
-rw-r--r--search.cpp476
1 files changed, 476 insertions, 0 deletions
diff --git a/search.cpp b/search.cpp
new file mode 100644
index 0000000..a19ed45
--- /dev/null
+++ b/search.cpp
@@ -0,0 +1,476 @@
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "sciteco.h"
+#include "expressions.h"
+#include "undo.h"
+#include "qbuffers.h"
+#include "parser.h"
+#include "search.h"
+
+namespace States {
+ StateSearch search;
+ StateSearchAll searchall;
+}
+
+void
+StateSearch::initial(void) throw (Error)
+{
+ gint64 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 <CTRL/S> */
+ 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 <CTRL/X> */
+ 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_GOTOPOS, 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_pattern);
+ if (!re)
+ goto failure;
+
+ if (ring.current != parameters.from_buffer) {
+ ring.undo_edit();
+ parameters.from_buffer->edit();
+ }
+
+ do_search(re, parameters.from, parameters.to, count);
+
+ if (parameters.to_buffer && count) {
+ Buffer *buffer = parameters.from_buffer;
+
+ if (ring.current == buffer)
+ ring.undo_edit();
+
+ if (count > 0) {
+ do {
+ buffer = buffer->next() ? : ring.first();
+ buffer->edit();
+
+ if (buffer == parameters.to_buffer) {
+ do_search(re, 0, parameters.dot, count);
+ break;
+ }
+
+ do_search(re, 0, interface.ssm(SCI_GETLENGTH),
+ count);
+ } while (count);
+ } else /* count < 0 */ {
+ do {
+ buffer = buffer->prev() ? : ring.last();
+ buffer->edit();
+
+ if (buffer == parameters.to_buffer) {
+ do_search(re, parameters.dot,
+ interface.ssm(SCI_GETLENGTH),
+ count);
+ break;
+ }
+
+ do_search(re, 0, interface.ssm(SCI_GETLENGTH),
+ count);
+ } while (count);
+ }
+
+ ring.current = buffer;
+ }
+
+ search_reg->set_integer(TECO_BOOL(!count));
+
+ g_regex_unref(re);
+
+ if (!count)
+ return;
+
+failure:
+ interface.ssm(SCI_GOTOPOS, parameters.dot);
+}
+
+State *
+StateSearch::done(const gchar *str) throw (Error)
+{
+ BEGIN_EXEC(&States::start);
+
+ QRegister *search_reg = QRegisters::globals["_"];
+
+ if (*str) {
+ search_reg->undo_set_string();
+ search_reg->set_string(str);
+ } else {
+ gchar *search_str = search_reg->get_string();
+ process(search_str, 0 /* unused */);
+ g_free(search_str);
+ }
+
+ if (eval_colon())
+ expressions.push(search_reg->get_integer());
+ else if (IS_FAILURE(search_reg->get_integer()) &&
+ !expressions.find_op(Expressions::OP_LOOP) /* not in loop */)
+ interface.msg(Interface::MSG_ERROR, "Search string not found!");
+
+ return &States::start;
+}
+
+void
+StateSearchAll::initial(void) throw (Error)
+{
+ gint64 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_buffer = ring.find(v1);
+ parameters.to_buffer = ring.find(v2);
+ } else {
+ parameters.count = -1;
+ parameters.from_buffer = ring.find(v2);
+ parameters.to_buffer = ring.find(v1);
+ }
+
+ if (!parameters.from_buffer || !parameters.to_buffer)
+ throw RangeError("N");
+ } else {
+ parameters.count = (gint)v2;
+ /* NOTE: on Q-Registers, behave like "S" */
+ parameters.from_buffer = parameters.to_buffer = ring.current;
+ }
+
+ if (parameters.count >= 0) {
+ parameters.from = parameters.dot;
+ parameters.to = interface.ssm(SCI_GETLENGTH);
+ } else {
+ parameters.from = 0;
+ parameters.to = parameters.dot;
+ }
+}
+
+State *
+StateSearchAll::done(const gchar *str) throw (Error)
+{
+ BEGIN_EXEC(&States::start);
+
+ StateSearch::done(str);
+
+ QRegisters::hook(QRegisters::HOOK_EDIT);
+ return &States::start;
+}