1267 lines
30 KiB
C
1267 lines
30 KiB
C
/*
|
|
* Copyright (c) 1999 by The XFree86 Project, Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
|
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
* Except as contained in this notice, the name of the XFree86 Project shall
|
|
* not be used in advertising or otherwise to promote the sale, use or other
|
|
* dealings in this Software without prior written authorization from the
|
|
* XFree86 Project.
|
|
*
|
|
* Author: Paulo César Pereira de Andrade
|
|
*/
|
|
|
|
/* $XFree86: xc/programs/xedit/hook.c,v 1.9 2003/01/08 05:07:40 paulo Exp $ */
|
|
|
|
/*
|
|
* This file is intended to be used to add all the necessary hooks to xedit
|
|
* emulate certain features of emacs (and other text editors) that are better
|
|
* kept only in xedit, to avoid unnecessary code in the Text Widget.
|
|
*
|
|
* The code here is not finished, and will probably be changed frequently.
|
|
*/
|
|
|
|
#include "xedit.h"
|
|
#include "re.h"
|
|
#include "util.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
/*
|
|
* Types
|
|
*/
|
|
typedef struct _ReplaceEntry {
|
|
hash_key *word;
|
|
struct _ReplaceEntry *next;
|
|
char *replace;
|
|
} ReplaceEntry;
|
|
|
|
typedef enum {
|
|
SubstituteDisabled,
|
|
SubstituteAsk,
|
|
SubstituteNo,
|
|
SubstituteYes
|
|
} SubstitutionState;
|
|
|
|
typedef struct _EditInfo {
|
|
/* Xedit regex data */
|
|
re_cod regex;
|
|
re_mat mats[10];
|
|
|
|
/* Last command entered */
|
|
char command[128];
|
|
|
|
/* String and flags used to compile regex */
|
|
char pattern[64];
|
|
char subst_pattern[64];
|
|
int pat_length;
|
|
int flags;
|
|
|
|
/* Substitution buffer */
|
|
char subst[64];
|
|
int soff, slen, sref;
|
|
|
|
/* For interactive substitution */
|
|
int callback;
|
|
Widget widget;
|
|
char *text_line;
|
|
SubstitutionState state;
|
|
XawTextPosition from, to, start, end, first, last;
|
|
|
|
/* Use if need to allocate a buffer to pass the entire line to reexec */
|
|
char *line;
|
|
long lsize;
|
|
|
|
/* Buffer to prepare replacement, if needs to expand backreferences */
|
|
char *buffer;
|
|
long bsize;
|
|
} EditInfo;
|
|
|
|
/*
|
|
* Prototypes
|
|
*/
|
|
static void ActionHook(Widget, XtPointer, String, XEvent*, String*, Cardinal*);
|
|
static void AutoReplaceHook(Widget, String, XEvent*);
|
|
static Bool StartAutoReplace(void);
|
|
static char *ReplacedWord(char*, char*);
|
|
static void AutoReplace(Widget, XEvent*);
|
|
static void AutoReplaceCallback(Widget, XtPointer, XtPointer);
|
|
|
|
static void SubstituteHook(Widget w, String action, XEvent *event);
|
|
static void SubstituteCallback(Widget, XtPointer, XtPointer);
|
|
|
|
/*
|
|
* Initialization
|
|
*/
|
|
#define STRTBLSZ 11
|
|
static hash_table *replace_hash;
|
|
static EditInfo einfo;
|
|
extern Widget scratch;
|
|
|
|
/*
|
|
* Implementation
|
|
*/
|
|
Bool
|
|
StartHooks(XtAppContext app)
|
|
{
|
|
static Bool first_time = True;
|
|
|
|
if (first_time) {
|
|
StartAutoReplace();
|
|
(void)XtAppAddActionHook(app, ActionHook, NULL);
|
|
first_time = False;
|
|
|
|
return (True);
|
|
}
|
|
return (False);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
ActionHook(Widget w, XtPointer client_data, String action, XEvent *event,
|
|
String *params, Cardinal *num_params)
|
|
{
|
|
AutoReplaceHook(w, action, event);
|
|
SubstituteHook(w, action, event);
|
|
}
|
|
|
|
/*** auto replace ***/
|
|
struct {
|
|
Widget widget;
|
|
String text;
|
|
Cardinal length;
|
|
XawTextPosition left, right;
|
|
Bool replace;
|
|
Bool enabled;
|
|
} auto_replace;
|
|
|
|
static void
|
|
AutoReplaceHook(Widget w, String action, XEvent *event)
|
|
{
|
|
static Bool multiply;
|
|
|
|
if (w != textwindow || !auto_replace.enabled)
|
|
return;
|
|
|
|
if (auto_replace.widget != textwindow) {
|
|
if (auto_replace.replace) {
|
|
auto_replace.replace = False;
|
|
XtRemoveCallback(auto_replace.widget, XtNpositionCallback,
|
|
AutoReplaceCallback, NULL);
|
|
}
|
|
}
|
|
else if (strcmp(action, "multiply") == 0) {
|
|
multiply = True;
|
|
return;
|
|
}
|
|
else if (strcmp(action, "numeric") == 0) {
|
|
if (multiply)
|
|
return;
|
|
}
|
|
else if (strcmp(action, "insert-char") && strcmp(action, "newline") &&
|
|
strcmp(action, "newline-and-indent")) {
|
|
return;
|
|
}
|
|
multiply = False;
|
|
|
|
AutoReplace(w, event);
|
|
}
|
|
|
|
static Bool
|
|
StartAutoReplace(void)
|
|
{
|
|
Bool esc;
|
|
int len, llen, rlen, count = 0;
|
|
char ch, *tmp, *left, *right, *replace = app_resources.auto_replace;
|
|
|
|
if (!replace || !*replace)
|
|
return (False);
|
|
|
|
replace_hash = hash_new(STRTBLSZ, NULL);
|
|
|
|
left = XtMalloc(llen = 256);
|
|
right = XtMalloc(rlen = 256);
|
|
while (*replace) {
|
|
/* skip white spaces */
|
|
while (*replace && isspace(*replace))
|
|
++replace;
|
|
if (!*replace)
|
|
break;
|
|
|
|
/* read left */
|
|
tmp = replace;
|
|
while (*replace && !isspace(*replace))
|
|
++replace;
|
|
len = replace - tmp;
|
|
if (len >= llen)
|
|
left = XtRealloc(left, llen = len + 1);
|
|
strncpy(left, tmp, len);
|
|
left[len] = '\0';
|
|
|
|
/* skip white spaces */
|
|
while (*replace && isspace(*replace))
|
|
++replace;
|
|
|
|
/* read right */
|
|
len = 0;
|
|
esc = False;
|
|
while ((ch = *replace) != '\0') {
|
|
++replace;
|
|
if (len + 2 >= rlen)
|
|
right = XtRealloc(right, rlen += 256);
|
|
if (ch == '\\') {
|
|
if (esc)
|
|
right[len++] = '\\';
|
|
esc = !esc;
|
|
continue;
|
|
}
|
|
else if (ch == '\n' && !esc)
|
|
break;
|
|
else
|
|
right[len++] = ch;
|
|
esc = False;
|
|
}
|
|
right[len] = '\0';
|
|
|
|
(void)ReplacedWord(left, right);
|
|
++count;
|
|
}
|
|
XtFree(left);
|
|
XtFree(right);
|
|
|
|
return (auto_replace.enabled = count > 0);
|
|
}
|
|
|
|
static char *
|
|
ReplacedWord(char *word, char *replace)
|
|
{
|
|
int length;
|
|
ReplaceEntry *entry;
|
|
|
|
length = strlen(word);
|
|
entry = (ReplaceEntry *)hash_check(replace_hash, word, length);
|
|
if (entry == NULL && replace != NULL) {
|
|
entry = XtNew(ReplaceEntry);
|
|
entry->word = XtNew(hash_key);
|
|
entry->word->value = XtNewString(word);
|
|
entry->word->length = length;
|
|
entry->next = NULL;
|
|
entry->replace = XtNewString(replace);
|
|
hash_put(replace_hash, (hash_entry *)entry);
|
|
}
|
|
else if (replace) {
|
|
XtFree(entry->replace);
|
|
entry->replace = XtNewString(replace);
|
|
}
|
|
|
|
return (entry ? entry->replace : NULL);
|
|
}
|
|
|
|
static void
|
|
AutoReplace(Widget w, XEvent *event)
|
|
{
|
|
static XComposeStatus compose = {NULL, 0};
|
|
KeySym keysym;
|
|
XawTextBlock block;
|
|
XawTextPosition left, right, pos;
|
|
Widget source;
|
|
int i, len, size;
|
|
char *str, buf[32], mb[sizeof(wchar_t)];
|
|
|
|
size = XLookupString((XKeyEvent*)event, mb, sizeof(mb), &keysym, &compose);
|
|
|
|
if (size != 1 || isalnum(*mb))
|
|
return;
|
|
|
|
source = XawTextGetSource(w);
|
|
right = XawTextGetInsertionPoint(w);
|
|
left = XawTextSourceScan(source, right, XawstWhiteSpace,
|
|
XawsdLeft, 1, False);
|
|
|
|
if (left < 0 || left == right)
|
|
return;
|
|
|
|
len = 0;
|
|
str = buf;
|
|
size = sizeof(buf);
|
|
pos = left;
|
|
while (pos < right) {
|
|
pos = XawTextSourceRead(source, pos, &block, right - pos);
|
|
for (i = 0; i < block.length; i++) {
|
|
if (block.format == FMT8BIT)
|
|
*mb = block.ptr[i];
|
|
else
|
|
wctomb(mb, ((wchar_t*)block.ptr)[i]);
|
|
str[len++] = *mb;
|
|
if (len + 2 >= size) {
|
|
if (str == buf)
|
|
str = XtMalloc(size += sizeof(buf));
|
|
else
|
|
str = XtRealloc(str, size += sizeof(buf));
|
|
}
|
|
}
|
|
}
|
|
str[len] = '\0';
|
|
if ((auto_replace.text = ReplacedWord(str, NULL)) != NULL) {
|
|
auto_replace.length = strlen(auto_replace.text);
|
|
auto_replace.left = left;
|
|
auto_replace.right = right;
|
|
auto_replace.replace = True;
|
|
XtAddCallback(auto_replace.widget = w, XtNpositionCallback,
|
|
AutoReplaceCallback, NULL);
|
|
}
|
|
if (str != buf)
|
|
XtFree(str);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
AutoReplaceCallback(Widget w, XtPointer client_data, XtPointer call_data)
|
|
{
|
|
int i, inc;
|
|
XawTextBlock block, text;
|
|
char buffer[1024], mb[sizeof(wchar_t)];
|
|
XawTextPosition left, right, pos;
|
|
|
|
if (!auto_replace.replace || w != auto_replace.widget)
|
|
return;
|
|
|
|
XtRemoveCallback(auto_replace.widget, XtNpositionCallback,
|
|
AutoReplaceCallback, NULL);
|
|
auto_replace.replace = False;
|
|
|
|
inc = XawTextGetInsertionPoint(w) - auto_replace.right;
|
|
if (auto_replace.length + inc > sizeof(buffer))
|
|
block.ptr = XtMalloc(auto_replace.length + inc);
|
|
else
|
|
block.ptr = buffer;
|
|
memcpy(block.ptr, auto_replace.text, auto_replace.length);
|
|
|
|
block.length = auto_replace.length;
|
|
pos = left = auto_replace.right;
|
|
right = left + inc;
|
|
while (pos < right) {
|
|
pos = XawTextSourceRead(XawTextGetSource(w), pos, &text, inc);
|
|
for (i = 0; i < text.length; i++) {
|
|
if (text.format == FMT8BIT)
|
|
*mb = text.ptr[i];
|
|
else
|
|
wctomb(mb, ((wchar_t*)text.ptr)[i]);
|
|
block.ptr[block.length++] = *mb;
|
|
}
|
|
}
|
|
|
|
block.firstPos = 0;
|
|
block.format = FMT8BIT;
|
|
|
|
if (XawTextReplace(w, auto_replace.left, auto_replace.right + inc,
|
|
&block) == XawEditDone)
|
|
XawTextSetInsertionPoint(w, auto_replace.left + block.length);
|
|
|
|
if (block.ptr != buffer)
|
|
XtFree(block.ptr);
|
|
}
|
|
|
|
/*ARGUSED*/
|
|
void
|
|
LineEditAction(Widget w, XEvent *event, String *params, Cardinal *num_params)
|
|
{
|
|
XawTextBlock block;
|
|
|
|
if (international) {
|
|
/* XXX FIXME */
|
|
fprintf(stderr, "LineEditAction: Not working in international mode.\n");
|
|
return;
|
|
}
|
|
|
|
block.firstPos = 0;
|
|
block.format = FMT8BIT;
|
|
block.ptr = einfo.command;
|
|
block.length = strlen(einfo.command);
|
|
|
|
XawTextReplace(filenamewindow, 0,
|
|
XawTextLastPosition(filenamewindow), &block);
|
|
XtSetKeyboardFocus(topwindow, filenamewindow);
|
|
line_edit = True;
|
|
}
|
|
|
|
void
|
|
LineEdit(Widget w)
|
|
{
|
|
/* Global usage variables */
|
|
XawTextPosition from, to, first, last, position, length, redisplay;
|
|
int replace, compile, ecode, nth, flags, count, etype;
|
|
char *command, *line, buffer[128];
|
|
XawTextBlock block;
|
|
Widget source;
|
|
XawTextScanDirection direction;
|
|
xedit_flist_item *item;
|
|
|
|
/* Variables used while parsing command */
|
|
int state, action, offset, icase, confirm;
|
|
long lfrom, lto, lfinc, ltinc, number;
|
|
char *ptr, *pstart, *pend, *rstart, *rend, *tmp;
|
|
|
|
/* Variables used in the search/replace loop */
|
|
int len;
|
|
XawTextPosition adjust = 0;
|
|
|
|
command = GetString(filenamewindow);
|
|
length = strlen(command);
|
|
if (length >= sizeof(einfo.command)) {
|
|
Feep();
|
|
return;
|
|
}
|
|
|
|
item = FindTextSource(XawTextGetSource(w), NULL);
|
|
source = item->source;
|
|
position = XawTextGetInsertionPoint(w);
|
|
first = XawTextSourceScan(source, 0, XawstAll, XawsdLeft, 1, True);
|
|
last = XawTextSourceScan(source, 0, XawstAll, XawsdRight, 1, True);
|
|
compile = redisplay = nth = count = confirm = 0;
|
|
direction = XawsdRight;
|
|
flags = RE_STARTEND;
|
|
|
|
/* Error types */
|
|
#define T_NONE 0
|
|
#define T_OPTION 1
|
|
#define T_ICASE 2
|
|
#define T_COMMAND 3
|
|
#define T_REPLACE 4
|
|
#define T_SEARCH 5
|
|
#define T_BACKSLASH 6
|
|
#define T_DIRECTION 7
|
|
#define T_COMMA 8
|
|
#define T_OFFSET 9
|
|
#define T_INCREMENT 10
|
|
#define T_NUMBER 11
|
|
#define T_UNFINISHED 12
|
|
#define T_RANGE 13
|
|
#define T_BACKREF 14
|
|
#define T_EDIT 15
|
|
etype = T_NONE;
|
|
|
|
#define FAIL(code) { etype = code; goto fail; }
|
|
|
|
/* Value for the line value, anything else is the line number */
|
|
#define L_FIRST -1
|
|
#define L_CURRENT -2
|
|
#define L_LAST -3
|
|
lfrom = L_FIRST;
|
|
lto = L_LAST;
|
|
|
|
/* Parsing states */
|
|
#define E_FINC 0
|
|
#define E_FROM 1
|
|
#define E_COMMA 2
|
|
#define E_TINC 3
|
|
#define E_TO 4
|
|
#define E_COMMAND 5
|
|
#define E_REGEX 6
|
|
#define E_SUBST 7
|
|
#define E_OPTIONS 8
|
|
state = E_FROM; /* Beginning interpretation */
|
|
|
|
/* Known commands */
|
|
#define A_SEARCH 0
|
|
#define A_REPLACE 1
|
|
action = A_SEARCH;
|
|
|
|
/* Flag to replace all occurrences */
|
|
#define O_ALL -1
|
|
|
|
number = 1;
|
|
lfinc = ltinc = 0;
|
|
icase = offset = 0;
|
|
pstart = pend = rstart = rend = NULL;
|
|
|
|
if (einfo.state != SubstituteDisabled) {
|
|
if (einfo.widget != w || strcmp(einfo.command, command)) {
|
|
einfo.widget = w;
|
|
einfo.state = SubstituteAsk;
|
|
}
|
|
else {
|
|
XawTextPosition s_start, s_end;
|
|
|
|
XawTextGetSelectionPos(w, &s_start, &s_end);
|
|
if (s_start != einfo.start || s_end != einfo.end)
|
|
einfo.state = SubstituteAsk;
|
|
confirm = replace = 1;
|
|
from = einfo.from;
|
|
to = einfo.to;
|
|
first = einfo.first;
|
|
last = einfo.last;
|
|
goto confirm_label;
|
|
}
|
|
}
|
|
|
|
/* Remember last command */
|
|
strcpy(einfo.command, command);
|
|
|
|
/* Loop parsing command */
|
|
for (ptr = einfo.command; *ptr;) {
|
|
switch (*ptr++) {
|
|
case 'c':
|
|
if (state != E_OPTIONS &&
|
|
state != E_COMMAND &&
|
|
state != E_REGEX)
|
|
FAIL(T_OPTION)
|
|
confirm = 1;
|
|
break;
|
|
case 'g':
|
|
if (state != E_OPTIONS &&
|
|
state != E_COMMAND &&
|
|
state != E_REGEX)
|
|
FAIL(T_OPTION)
|
|
offset = O_ALL;
|
|
break;
|
|
case 'i':
|
|
if (state != E_OPTIONS &&
|
|
state != E_COMMAND &&
|
|
state != E_REGEX &&
|
|
state != E_FROM)
|
|
FAIL(T_ICASE)
|
|
icase = 1;
|
|
break;
|
|
case 's':
|
|
if (state == E_FROM)
|
|
lfrom = lto = L_CURRENT;
|
|
else if (state == E_COMMA) {
|
|
lto = L_CURRENT;
|
|
ltinc = lfinc;
|
|
}
|
|
else if (state == E_TO)
|
|
lto = L_LAST;
|
|
else if (state == E_FINC) {
|
|
ltinc = lfinc;
|
|
lto = L_CURRENT;
|
|
}
|
|
else if (state != E_COMMAND && state != E_TINC)
|
|
FAIL(T_COMMAND)
|
|
action = A_REPLACE;
|
|
state = E_REGEX;
|
|
break;
|
|
case '?':
|
|
if (action == A_REPLACE)
|
|
FAIL(T_REPLACE)
|
|
case '/':
|
|
if (state == E_TINC)
|
|
state = action == A_REPLACE ? E_REGEX : E_FROM;
|
|
else if (state == E_COMMA || state == E_FINC) {
|
|
lto = L_LAST;
|
|
state = E_FROM;
|
|
}
|
|
else if (state == E_TO) {
|
|
if (ltinc == 0)
|
|
lto = L_LAST;
|
|
state = E_FROM;
|
|
}
|
|
else if (state == E_COMMAND)
|
|
state = E_FROM;
|
|
else if (state != E_REGEX &&
|
|
state != E_SUBST &&
|
|
state != E_FROM)
|
|
FAIL(T_SEARCH)
|
|
if (state != E_SUBST)
|
|
direction = ptr[-1] == '/' ? XawsdRight : XawsdLeft;
|
|
for (tmp = ptr; *tmp; tmp++) {
|
|
if (*tmp == '\\') {
|
|
if (*++tmp == '\0')
|
|
FAIL(T_BACKSLASH)
|
|
}
|
|
else if (*tmp == ptr[-1])
|
|
break;
|
|
}
|
|
if (state == E_REGEX) {
|
|
if (*tmp != ptr[-1])
|
|
FAIL(T_DIRECTION)
|
|
pstart = ptr;
|
|
pend = ptr = tmp;
|
|
state = E_SUBST;
|
|
}
|
|
else if (state == E_FROM) {
|
|
pstart = ptr;
|
|
pend = ptr = tmp;
|
|
state = E_OPTIONS;
|
|
if (*ptr)
|
|
++ptr;
|
|
}
|
|
else { /* E_SUBST */
|
|
rstart = ptr;
|
|
rend = tmp;
|
|
state = E_OPTIONS;
|
|
ptr = tmp;
|
|
if (*ptr)
|
|
++ptr;
|
|
}
|
|
break;
|
|
case ',':
|
|
if (state == E_FROM)
|
|
lfrom = L_FIRST;
|
|
else if (state == E_FINC)
|
|
lfrom = L_CURRENT;
|
|
else if (state != E_COMMA)
|
|
FAIL(T_COMMA)
|
|
state = E_TO;
|
|
break;
|
|
case '%':
|
|
if (state == E_FROM) {
|
|
lfrom = L_FIRST;
|
|
lto = L_LAST;
|
|
state = E_COMMAND;
|
|
}
|
|
else
|
|
FAIL(T_OFFSET)
|
|
break;
|
|
case '$':
|
|
if (state != E_TO)
|
|
FAIL(T_OFFSET)
|
|
lto = L_LAST;
|
|
state = E_COMMAND;
|
|
break;
|
|
case '.':
|
|
if (state == E_FROM) {
|
|
lfrom = L_CURRENT;
|
|
state = E_COMMA;
|
|
}
|
|
else if (state == E_TO) {
|
|
lto = L_CURRENT;
|
|
state = E_COMMAND;
|
|
}
|
|
else
|
|
FAIL(T_OFFSET)
|
|
break;
|
|
case '+':
|
|
if (state == E_FROM) {
|
|
lfinc = 1;
|
|
lfrom = L_CURRENT;
|
|
state = E_FINC;
|
|
}
|
|
else if (state == E_TO) {
|
|
ltinc = 1;
|
|
lto = L_CURRENT;
|
|
state = E_TINC;
|
|
}
|
|
else
|
|
FAIL(T_INCREMENT)
|
|
break;
|
|
case '-': case '^':
|
|
if (state == E_FROM) {
|
|
lfinc = -1;
|
|
lfrom = L_CURRENT;
|
|
state = E_FINC;
|
|
}
|
|
else if (state == E_TO) {
|
|
ltinc = -1;
|
|
lto = L_CURRENT;
|
|
state = E_TINC;
|
|
}
|
|
else
|
|
FAIL(T_INCREMENT)
|
|
number = -1;
|
|
break;
|
|
case ';':
|
|
if (state != E_FROM)
|
|
FAIL(T_OFFSET)
|
|
lfrom = L_CURRENT;
|
|
lto = L_LAST;
|
|
state = E_COMMAND;
|
|
break;
|
|
case '1': case '2': case '3':
|
|
case '4': case '5': case '6':
|
|
case '7': case '8': case '9':
|
|
number = number * (ptr[-1] - '0');
|
|
while (isdigit(*ptr))
|
|
number = number * 10 + (*ptr++ - '0');
|
|
if (state == E_FROM) {
|
|
lfrom = number;
|
|
state = E_COMMA;
|
|
}
|
|
else if (state == E_FINC) {
|
|
lfinc = number;
|
|
state = E_COMMA;
|
|
}
|
|
else if (state == E_TO) {
|
|
lto = number;
|
|
state = E_COMMAND;
|
|
}
|
|
else if (state == E_TINC) {
|
|
ltinc = number;
|
|
state = E_COMMAND;
|
|
}
|
|
else if (state == E_OPTIONS && action == A_REPLACE)
|
|
offset = number - 1;
|
|
else
|
|
FAIL(T_NUMBER)
|
|
number = 1;
|
|
break;
|
|
case '\0':
|
|
if (state == E_OPTIONS)
|
|
break;
|
|
default:
|
|
FAIL(T_UNFINISHED)
|
|
}
|
|
}
|
|
|
|
replace = action == A_REPLACE;
|
|
|
|
switch (lfrom) {
|
|
case L_FIRST:
|
|
from = first;
|
|
break;
|
|
case L_LAST:
|
|
from = LSCAN(last, 1, False);
|
|
break;
|
|
case L_CURRENT:
|
|
if (lfinc <= 0)
|
|
from = LSCAN(position, -lfinc + 1, False);
|
|
else {
|
|
from = RSCAN(position, lfinc + 1, False);
|
|
from = LSCAN(from, 1, False);
|
|
}
|
|
break;
|
|
default:
|
|
from = RSCAN(first, lfrom, False);
|
|
from = LSCAN(from, 1, False);
|
|
break;
|
|
}
|
|
/* Just requesting to go to the numbered line */
|
|
if (state == E_COMMA || state == E_FINC) {
|
|
XawTextSetInsertionPoint(w, from);
|
|
return;
|
|
}
|
|
|
|
length = pend - pstart;
|
|
if (pstart == NULL || (replace && rstart == NULL) ||
|
|
length >= sizeof(einfo.pattern) - 1)
|
|
FAIL(T_UNFINISHED)
|
|
|
|
/* Need to (re)compile regular expression pattern? */
|
|
if ((!!(einfo.flags & RE_ICASE) ^ icase) ||
|
|
einfo.pat_length != length ||
|
|
memcmp(pstart, einfo.pattern,
|
|
length > einfo.pat_length ? einfo.pat_length : length)) {
|
|
compile = 1;
|
|
memcpy(einfo.pattern, pstart, length);
|
|
einfo.pattern[length] = '\0';
|
|
einfo.flags = icase ? RE_ICASE : 0;
|
|
}
|
|
|
|
/* Check range of lines to operate on */
|
|
switch (lto) {
|
|
case L_FIRST:
|
|
to = RSCAN(first, 1, True);
|
|
break;
|
|
case L_LAST:
|
|
to = last;
|
|
break;
|
|
case L_CURRENT:
|
|
if (ltinc < 0) {
|
|
to = LSCAN(position, -ltinc + 1, True);
|
|
to = RSCAN(to, 2, True);
|
|
}
|
|
else
|
|
to = RSCAN(position, ltinc + 1, True);
|
|
break;
|
|
default:
|
|
to = RSCAN(first, lto, True);
|
|
break;
|
|
}
|
|
if (from >= to)
|
|
FAIL(T_RANGE)
|
|
|
|
/* Set first and last position allowed to search/replace */
|
|
first = from;
|
|
last = to;
|
|
|
|
/* Check bounds to work on */
|
|
if (replace) {
|
|
int i, j, ch;
|
|
|
|
/* Check number of required match results and remove/parse backslashes */
|
|
einfo.slen = rend - rstart;
|
|
einfo.sref = 0;
|
|
einfo.soff = offset;
|
|
for (i = j = 0; i < einfo.slen; i++) {
|
|
ch = rstart[i];
|
|
if (ch == '\\') {
|
|
++i;
|
|
switch (rstart[i]) {
|
|
case '0': ch = '\0'; break;
|
|
case 'a': ch = '\a'; break;
|
|
case 'b': ch = '\b'; break;
|
|
case 'f': ch = '\f'; break;
|
|
case 'n': ch = '\n'; break;
|
|
case 'r': ch = '\r'; break;
|
|
case 't': ch = '\t'; break;
|
|
case 'v': ch = '\v'; break;
|
|
case '1': case '2': case '3':
|
|
case '4': case '5': case '6':
|
|
case '7': case '8': case '9':
|
|
einfo.subst[j++] = '\\';
|
|
if (rstart[i] - '0' > einfo.sref)
|
|
einfo.sref = rstart[i] - '0';
|
|
/* FALLTHROUGH */
|
|
default:
|
|
ch = rstart[i];
|
|
break;
|
|
}
|
|
}
|
|
einfo.subst[j++] = ch;
|
|
}
|
|
einfo.slen = j;
|
|
}
|
|
else if (einfo.widget != w) {
|
|
/* Just a flag for backward search */
|
|
einfo.from = last;
|
|
einfo.widget = w;
|
|
}
|
|
|
|
/* Compile pattern if required */
|
|
if (compile) {
|
|
int ch;
|
|
char *eptr, *pptr;
|
|
|
|
/* Parse backslashes */
|
|
pptr = einfo.subst_pattern;
|
|
for (eptr = einfo.pattern, ch = *eptr++; ch; ch = *eptr++) {
|
|
if (ch == '\\') {
|
|
switch (*eptr) {
|
|
case '0': ch = '\0'; einfo.flags |= RE_PEND; break;
|
|
case 'a': ch = '\a'; break;
|
|
case 'b': ch = '\b'; break;
|
|
case 'f': ch = '\f'; break;
|
|
case 'n': ch = '\n'; break;
|
|
case 'r': ch = '\r'; break;
|
|
case 't': ch = '\t'; break;
|
|
case 'v': ch = '\v'; break;
|
|
default: break;
|
|
}
|
|
if (ch != '\\')
|
|
++eptr;
|
|
}
|
|
*pptr++ = ch;
|
|
}
|
|
*pptr = '\0';
|
|
|
|
refree(&einfo.regex);
|
|
/* Allow nuls in search regex */
|
|
einfo.regex.re_endp = pptr;
|
|
ecode = recomp(&einfo.regex, einfo.subst_pattern, einfo.flags);
|
|
if (ecode)
|
|
goto print;
|
|
}
|
|
|
|
if (!replace && position >= first && position <= last) {
|
|
from = position;
|
|
/* The backwards repetition currently is only backwards when
|
|
* changing lines, so remember from where started, to also
|
|
* search in the first line. */
|
|
if (LSCAN(from, 1, False) == from) {
|
|
if (direction == XawsdLeft)
|
|
einfo.from = from;
|
|
}
|
|
else
|
|
flags |= RE_NOTBOL;
|
|
}
|
|
to = RSCAN(from, 1, True);
|
|
|
|
if (confirm) {
|
|
if (!replace)
|
|
FAIL(T_UNFINISHED)
|
|
einfo.widget = w;
|
|
einfo.state = SubstituteAsk;
|
|
einfo.from = from;
|
|
einfo.to = to;
|
|
einfo.first = first;
|
|
einfo.last = last;
|
|
}
|
|
else
|
|
einfo.state = SubstituteDisabled;
|
|
|
|
confirm_label:
|
|
if (replace) {
|
|
redisplay = 1;
|
|
XawTextDisableRedisplay(w);
|
|
}
|
|
|
|
for (;;) {
|
|
if (confirm && einfo.state != SubstituteAsk) {
|
|
/* Restore state from previous call */
|
|
ecode = 0;
|
|
nth = einfo.soff;
|
|
/* einfo.mats should not have changed */
|
|
if (einfo.state == SubstituteYes) {
|
|
einfo.state = SubstituteAsk;
|
|
line = einfo.text_line;
|
|
goto substitute_label;
|
|
}
|
|
else {
|
|
++nth;
|
|
einfo.state = SubstituteAsk;
|
|
from = einfo.from = einfo.end;
|
|
goto no_substitute_label;
|
|
}
|
|
}
|
|
|
|
/* Read or use a line of text inplace */
|
|
position = from;
|
|
length = to - from;
|
|
XawTextSourceRead(source, position, &block, to - position);
|
|
if (block.length >= length)
|
|
line = block.ptr;
|
|
else {
|
|
if (length > einfo.lsize) {
|
|
einfo.line = XtRealloc(einfo.line, to - from);
|
|
einfo.lsize = to - from;
|
|
}
|
|
memcpy(einfo.line, block.ptr, block.length);
|
|
length = block.length;
|
|
for (position += length;
|
|
position < to && block.length;
|
|
position += block.length) {
|
|
XawTextSourceRead(source, position, &block, to - position);
|
|
memcpy(einfo.line + length, block.ptr, block.length);
|
|
length += block.length;
|
|
}
|
|
line = einfo.line;
|
|
}
|
|
|
|
/* Execute expression */
|
|
einfo.mats[0].rm_so = 0;
|
|
einfo.mats[0].rm_eo = to - from;
|
|
|
|
/* If not last line or if it ends in a newline */
|
|
if (to != from) {
|
|
if (to < last || (to > from && line[einfo.mats[0].rm_eo - 1] == '\n'))
|
|
--einfo.mats[0].rm_eo;
|
|
|
|
ecode = reexec(&einfo.regex, line,
|
|
einfo.sref + 1, &einfo.mats[0], flags);
|
|
|
|
if (replace && einfo.mats[0].rm_so == einfo.mats[0].rm_eo)
|
|
/* Ignore empty matches */
|
|
ecode = RE_NOMATCH;
|
|
|
|
if (ecode == 0 && confirm &&
|
|
(einfo.soff == O_ALL || nth == einfo.soff)) {
|
|
einfo.end = from + einfo.mats[0].rm_eo;
|
|
einfo.start = from + einfo.mats[0].rm_so;
|
|
XawTextSetInsertionPoint(w, einfo.end);
|
|
XawTextSetSelection(w, einfo.start, einfo.end);
|
|
|
|
einfo.state = SubstituteAsk;
|
|
einfo.from = from;
|
|
einfo.to = to;
|
|
einfo.first = first;
|
|
einfo.last = last;
|
|
einfo.text_line = line;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
/* Check bellow will update offsets */
|
|
ecode = RE_NOMATCH;
|
|
|
|
substitute_label:
|
|
if (ecode == 0) {
|
|
from += einfo.mats[0].rm_so;
|
|
len = einfo.mats[0].rm_eo - einfo.mats[0].rm_so;
|
|
|
|
/* Found match */
|
|
if (replace) {
|
|
/* If not replacing all ocurrences, or if not
|
|
* at the correct offset */
|
|
if (einfo.soff != O_ALL && nth < einfo.soff) {
|
|
from += len;
|
|
++nth;
|
|
continue;
|
|
}
|
|
|
|
/* Do the substitution */
|
|
block.firstPos = 0;
|
|
block.format = FMT8BIT;
|
|
if (einfo.sref) {
|
|
/* Hard way */
|
|
int i, ref, xlen;
|
|
|
|
for (i = length = 0; i < einfo.slen; i++) {
|
|
if (length + 2 >= einfo.bsize) {
|
|
einfo.bsize = einfo.bsize + 1024;
|
|
einfo.buffer = XtRealloc(einfo.buffer, einfo.bsize);
|
|
}
|
|
if (einfo.subst[i] == '\\') {
|
|
++i;
|
|
if (einfo.subst[i] >= '1' && einfo.subst[i] <= '9') {
|
|
ref = einfo.subst[i] - '0';
|
|
xlen = einfo.mats[ref].rm_eo -
|
|
einfo.mats[ref].rm_so;
|
|
if (xlen < 0)
|
|
/* Oops, something went wrong... */
|
|
FAIL(T_BACKREF)
|
|
if (length + xlen >= einfo.bsize) {
|
|
einfo.bsize += xlen + 1024 - (xlen % 1024);
|
|
einfo.buffer = XtRealloc(einfo.buffer,
|
|
einfo.bsize);
|
|
}
|
|
memcpy(einfo.buffer + length,
|
|
line + einfo.mats[ref].rm_so, xlen);
|
|
length += xlen;
|
|
}
|
|
else {
|
|
einfo.buffer[length++] = einfo.subst[i - 1];
|
|
einfo.buffer[length++] = einfo.subst[i];
|
|
}
|
|
}
|
|
else
|
|
einfo.buffer[length++] = einfo.subst[i];
|
|
}
|
|
block.ptr = einfo.buffer;
|
|
block.length = length;
|
|
}
|
|
else {
|
|
block.ptr = einfo.subst;
|
|
block.length = length = einfo.slen;
|
|
}
|
|
adjust = length - len;
|
|
if (XawTextReplace(w, from, from + len, &block) != XawEditDone)
|
|
FAIL(T_EDIT)
|
|
last += adjust;
|
|
to += adjust;
|
|
from += length;
|
|
|
|
no_substitute_label:
|
|
if (einfo.soff != O_ALL) {
|
|
nth = 0;
|
|
to = RSCAN(from, 1, True);
|
|
from = LSCAN(to, 1, False);
|
|
if (to == last) {
|
|
XawTextSetInsertionPoint(w, from);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
flags |= RE_NOTBOL;
|
|
}
|
|
else {
|
|
XawTextSetInsertionPoint(w, from + len);
|
|
XawTextSetSelection(w, from, from + len);
|
|
break;
|
|
}
|
|
}
|
|
else if (ecode == RE_NOMATCH) {
|
|
nth = 0;
|
|
|
|
/* Try again in the next/previous line */
|
|
if (direction == XawsdLeft) {
|
|
from = LSCAN(to - 1, 1 + (from != to), False);
|
|
if (einfo.from <= first) {
|
|
Feep();
|
|
if (++count > 1) {
|
|
XawTextSetInsertionPoint(w, position);
|
|
XawTextUnsetSelection(w);
|
|
break;
|
|
}
|
|
from = LSCAN(last, 1, False);
|
|
}
|
|
to = RSCAN(from, 1, True);
|
|
/* Can use einfo.from because replace is only done forward */
|
|
einfo.from = from;
|
|
}
|
|
else {
|
|
if (to >= last) {
|
|
Feep();
|
|
if (replace || ++count > 1) {
|
|
XawTextSetInsertionPoint(w, position);
|
|
XawTextUnsetSelection(w);
|
|
einfo.state = SubstituteDisabled;
|
|
confirm = 0;
|
|
break;
|
|
}
|
|
to = first;
|
|
}
|
|
from = LSCAN(to + 1, 1, False);
|
|
to = RSCAN(from, 1, True);
|
|
}
|
|
|
|
/* Reset flags now */
|
|
flags = RE_STARTEND;
|
|
}
|
|
else
|
|
goto print;
|
|
}
|
|
|
|
if (redisplay)
|
|
XawTextEnableRedisplay(w);
|
|
/* If replacing not interatively return to the edit window after finished */
|
|
if (replace && !confirm) {
|
|
Arg args[1];
|
|
|
|
XtSetKeyboardFocus(topwindow, textwindow);
|
|
if (item->source != scratch)
|
|
XtSetArg(args[0], XtNstring, item->name);
|
|
else
|
|
XtSetArg(args[0], XtNstring, NULL);
|
|
XtSetValues(filenamewindow, args, 1);
|
|
line_edit = False;
|
|
}
|
|
return;
|
|
|
|
print:
|
|
if (redisplay)
|
|
XawTextEnableRedisplay(w);
|
|
|
|
strcpy(buffer, "Regex error: ");
|
|
length = 13;
|
|
reerror(ecode, &einfo.regex,
|
|
buffer + length, sizeof(buffer) - length - 2);
|
|
strcat(buffer, "\n");
|
|
XeditPrintf("%s", buffer);
|
|
refree(&einfo.regex);
|
|
einfo.state = SubstituteDisabled;
|
|
Feep();
|
|
return;
|
|
|
|
|
|
fail:
|
|
if (etype != T_NONE) {
|
|
const char *errptr;
|
|
switch (etype) {
|
|
case T_OPTION:
|
|
errptr = "Option needs a command";
|
|
break;
|
|
case T_ICASE:
|
|
errptr = "Icase needs an command defined or none for search";
|
|
break;
|
|
case T_COMMAND:
|
|
errptr = "Command incorrectly specified";
|
|
break;
|
|
case T_REPLACE:
|
|
errptr = "Can only search backwards";
|
|
break;
|
|
case T_SEARCH:
|
|
errptr = "Badly placed search/replace specifier";
|
|
break;
|
|
case T_BACKSLASH:
|
|
errptr = "A single backslash cannot be the last command character";
|
|
break;
|
|
case T_DIRECTION:
|
|
errptr = "Regular expression must be separeted by / or ? not both";
|
|
break;
|
|
case T_COMMA:
|
|
errptr = "Badly placed comma";
|
|
break;
|
|
case T_OFFSET:
|
|
errptr = "Badly placed line offset specifier";
|
|
break;
|
|
case T_INCREMENT:
|
|
errptr = "Badly placed line offset increment specifier";
|
|
break;
|
|
case T_NUMBER:
|
|
errptr = "Numeric argument not expected";
|
|
break;
|
|
case T_UNFINISHED:
|
|
errptr = "Unfinished command";
|
|
break;
|
|
case T_RANGE:
|
|
errptr = "Bad line range";
|
|
break;
|
|
case T_BACKREF:
|
|
/* This may be an internal re error, but most likely the
|
|
* user asked for something like "s/re0(re1)re2/\2/" */
|
|
errptr = "Bad backreference";
|
|
break;
|
|
case T_EDIT:
|
|
errptr = "Failed to replace text";
|
|
break;
|
|
default:
|
|
errptr = "Unknown error";
|
|
break;
|
|
}
|
|
XeditPrintf("Error: %s.\n", errptr);
|
|
}
|
|
if (redisplay)
|
|
XawTextEnableRedisplay(w);
|
|
einfo.state = SubstituteDisabled;
|
|
Feep();
|
|
}
|
|
|
|
static void
|
|
SubstituteHook(Widget w, String action, XEvent *event)
|
|
{
|
|
if (w != filenamewindow)
|
|
return;
|
|
|
|
if (line_edit && einfo.state == SubstituteAsk) {
|
|
if (strcmp(action, "newline") == 0 ||
|
|
strcmp(action, "load-file") == 0)
|
|
einfo.state = SubstituteAsk;
|
|
else if (strcmp(action, "insert-char") == 0) {
|
|
static XComposeStatus compose = {NULL, 0};
|
|
KeySym keysym;
|
|
char mb[sizeof(wchar_t)];
|
|
|
|
if (XLookupString((XKeyEvent*)event, mb, sizeof(mb),
|
|
&keysym, &compose) == 1) {
|
|
if (*mb == 'y' || *mb == 'Y')
|
|
einfo.state = SubstituteYes;
|
|
else if (*mb == 'n' || *mb == 'N')
|
|
einfo.state = SubstituteNo;
|
|
else
|
|
einfo.state = SubstituteDisabled;
|
|
|
|
if (einfo.state != SubstituteDisabled) {
|
|
einfo.callback = 1;
|
|
XtAddCallback(filenamewindow, XtNpositionCallback,
|
|
SubstituteCallback, NULL);
|
|
}
|
|
}
|
|
}
|
|
else if (strcmp(action, "cancel-find-file") == 0)
|
|
einfo.state = SubstituteDisabled;
|
|
}
|
|
if (einfo.state == SubstituteDisabled && einfo.callback) {
|
|
einfo.callback = 0;
|
|
XtRemoveCallback(filenamewindow, XtNpositionCallback,
|
|
SubstituteCallback, NULL);
|
|
}
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
SubstituteCallback(Widget w, XtPointer client_data, XtPointer call_data)
|
|
{
|
|
XawTextBlock block;
|
|
|
|
einfo.callback = 0;
|
|
XtRemoveCallback(filenamewindow, XtNpositionCallback,
|
|
SubstituteCallback, NULL);
|
|
|
|
block.firstPos = 0;
|
|
block.format = FMT8BIT;
|
|
block.ptr = einfo.command;
|
|
block.length = strlen(einfo.command);
|
|
|
|
XawTextReplace(filenamewindow, 0,
|
|
XawTextLastPosition(filenamewindow), &block);
|
|
|
|
LineEdit(einfo.widget);
|
|
}
|