evil-qemacs/extras.c
Charlie Gordon a3fac89268 Add standard bindings, improve consistency
- `C-g` invokes keyboard-quit
- bind `ESC ESC ESC` to keyboard-quit
- prevent ESC key combination in `combine_keys()` if not first
- rename `abort` as `keyboard-quit` and do_break() as do_keyboard_quit()
- rename do_combine_char() as do_combine_accent()
- rename do_word_right() as do_word_left_right()
- pass numeric argument to do_scroll_left_right()
- pass numeric argument to do_minibuffer_history()
2020-11-08 00:47:24 +01:00

2016 lines
61 KiB
C

/*
* QEmacs, extra commands non full version
*
* Copyright (c) 2000-2020 Charlie Gordon.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <time.h>
#include "qe.h"
#include "qfribidi.h"
#include "variables.h"
static int qe_skip_comments(EditState *s, int offset, int *offsetp)
{
unsigned int buf[COLORED_MAX_LINE_SIZE];
QETermStyle sbuf[COLORED_MAX_LINE_SIZE];
int line_num, col_num, len, pos;
int offset0, offset1;
if (!s->colorize_func && !s->b->b_styles)
return 0;
eb_get_pos(s->b, &line_num, &col_num, offset);
offset0 = eb_goto_bol2(s->b, offset, &pos);
len = get_colorized_line(s, buf, countof(buf), sbuf,
offset0, &offset1, line_num);
if (len > countof(buf))
len = countof(buf);
if (pos >= len)
return 0;
if (sbuf[pos] != QE_STYLE_COMMENT)
return 0;
while (pos < len && sbuf[pos] == QE_STYLE_COMMENT) {
offset = eb_next(s->b, offset);
pos++;
}
*offsetp = offset;
return 1;
}
static int qe_skip_spaces(EditState *s, int offset, int *offsetp)
{
int offset0 = offset, offset1;
while (offset < s->b->total_size
&& qe_isspace(eb_nextc(s->b, offset, &offset1))) {
offset = offset1;
}
if (offset != offset0) {
*offsetp = offset;
return 1;
}
return 0;
}
static void compare_resync(EditState *s1, EditState *s2,
int save1, int save2,
int *offset1_ptr, int *offset2_ptr)
{
int pos1, off1, pos2, off2;
int ch1, ch2;
off1 = save1;
off2 = save2;
/* try skipping blanks */
while (qe_isblank(ch1 = eb_nextc(s1->b, pos1 = off1, &off1)))
continue;
while (qe_isblank(ch2 = eb_nextc(s2->b, pos2 = off2, &off2)))
continue;
/* XXX: should try and detect a simple insertion first
by comparing from the end of both lines */
if (ch1 != ch2) {
/* try skipping current words and subsequent blanks */
off1 = pos1;
off2 = pos2;
while (!qe_isspace(ch1 = eb_nextc(s1->b, pos1 = off1, &off1)))
continue;
while (!qe_isspace(ch2 = eb_nextc(s2->b, pos2 = off2, &off2)))
continue;
while (qe_isblank(ch1 = eb_nextc(s1->b, pos1 = off1, &off1)))
continue;
while (qe_isblank(ch2 = eb_nextc(s2->b, pos2 = off2, &off2)))
continue;
if (ch1 != ch2) {
/* Try to resync from end of line */
pos1 = eb_goto_eol(s1->b, save1);
pos2 = eb_goto_eol(s2->b, save2);
}
}
while (pos1 > save1 && pos2 > save2
&& eb_prevc(s1->b, pos1, &off1) == eb_prevc(s2->b, pos2, &off2)) {
pos1 = off1;
pos2 = off2;
}
*offset1_ptr = pos1;
*offset2_ptr = pos2;
}
void do_compare_windows(EditState *s, int argval)
{
QEmacsState *qs = s->qe_state;
EditState *s1;
EditState *s2;
int offset1, offset2, size1, size2, ch1, ch2;
int tries, resync = 0;
char buf1[MAX_CHAR_BYTES + 2], buf2[MAX_CHAR_BYTES + 2];
const char *comment = "";
s1 = s;
/* Should use same internal function as for next_window */
for (s2 = s1;;) {
s2 = s2->next_window;
if (s2 == NULL)
s2 = qs->first_window;
if (s2 == s1) {
/* single window: bail out */
return;
}
if (s2->b->flags & BF_DIRED)
continue;
break;
}
if (argval != NO_ARG) {
if (argval & 4)
qs->ignore_spaces ^= 1;
if (argval & 16)
qs->ignore_comments ^= 1;
}
size1 = s1->b->total_size;
size2 = s2->b->total_size;
if (qs->last_cmd_func == (CmdFunc)do_compare_windows) {
resync = 1;
}
for (tries = 0;; resync = 0) {
if (++tries >= 100000) {
tries = 0;
//if (check_input())
// return;
}
if (s1->offset >= size1) {
offset1 = s1->offset;
ch1 = EOF;
} else {
ch1 = eb_nextc(s1->b, s1->offset, &offset1);
}
if (s2->offset >= size2) {
offset2 = s2->offset;
ch2 = EOF;
} else {
ch2 = eb_nextc(s2->b, s2->offset, &offset2);
}
if (ch1 != ch2) {
if (qs->ignore_spaces) {
/* UTF-8 issue */
if (qe_isspace(ch1) || qe_isspace(ch2)) {
qe_skip_spaces(s1, s1->offset, &s1->offset);
qe_skip_spaces(s2, s2->offset, &s2->offset);
if (!*comment)
comment = "Skipped spaces, ";
continue;
}
}
if (qs->ignore_comments) {
if (qe_skip_comments(s1, s1->offset, &s1->offset) |
qe_skip_comments(s2, s2->offset, &s2->offset)) {
comment = "Skipped comments, ";
continue;
}
}
if (ch1 == EOF || ch2 == EOF) {
put_status(s, "%sExtra characters", comment);
break;
}
if (resync) {
int save1 = s1->offset, save2 = s2->offset;
compare_resync(s1, s2, save1, save2, &s1->offset, &s2->offset);
put_status(s, "Skipped %d and %d bytes",
s1->offset - save1, s2->offset - save2);
break;
}
put_status(s, "%sDifference: '%s' [0x%02X] <-> '%s' [0x%02X]", comment,
utf8_char_to_string(buf1, ch1), ch1,
utf8_char_to_string(buf2, ch2), ch2);
break;
}
if (ch1 != EOF) {
s1->offset = offset1;
s2->offset = offset2;
continue;
}
put_status(s, "%sNo difference", comment);
break;
}
}
void do_compare_files(EditState *s, const char *filename, int bflags)
{
char buf[MAX_FILENAME_SIZE];
char dir[MAX_FILENAME_SIZE];
int pathlen, parent_pathlen;
const char *tail;
EditState *e;
pathlen = get_basename_offset(filename);
get_default_path(s->b, s->offset, dir, sizeof(dir));
if (strstart(filename, dir, &tail)) {
snprintf(buf, sizeof(buf), "%s../%s", dir, tail);
} else
if (pathlen == 0) {
snprintf(buf, sizeof(buf), "../%s", filename);
} else
if (pathlen == 1) {
put_status(s, "Reference file is in root directory: %s", filename);
return;
} else
if (pathlen >= MAX_FILENAME_SIZE) {
put_status(s, "Filename too long: %s", filename);
return;
} else {
pstrcpy(buf, sizeof(buf), filename);
buf[pathlen - 1] = '\0'; /* overwite the path separator */
parent_pathlen = get_basename_offset(buf);
pstrcpy(buf + parent_pathlen, sizeof(buf) - parent_pathlen, filename + pathlen);
}
// XXX: should check for regular file
if (access(filename, R_OK)) {
put_status(s, "Cannot access file %s: %s",
filename, strerror(errno));
return;
}
if (access(buf, R_OK)) {
put_status(s, "Cannot access file %s: %s",
buf, strerror(errno));
return;
}
do_find_file(s, filename, bflags);
do_delete_other_windows(s, 0);
e = qe_split_window(s, SW_STACKED, 50);
if (e) {
s->qe_state->active_window = e;
do_find_file(e, buf, bflags);
}
}
void do_delete_horizontal_space(EditState *s)
{
int from, to, offset;
/* boundary check unnecessary because eb_prevc returns '\n'
* at bof and eof and qe_isblank return true only on SPC and TAB.
*/
from = to = s->offset;
while (qe_isblank(eb_prevc(s->b, from, &offset)))
from = offset;
while (qe_isblank(eb_nextc(s->b, to, &offset)))
to = offset;
eb_delete_range(s->b, from, to);
}
static void do_delete_blank_lines(EditState *s)
{
/* Delete blank lines:
* On blank line, delete all surrounding blank lines, leaving just one.
* On isolated blank line, delete that one.
* On nonblank line, delete any immediately following blank lines.
*/
/* XXX: should simplify */
int from, offset, offset0, offset1, all = 0;
EditBuffer *b = s->b;
offset = eb_goto_bol(b, s->offset);
if (eb_is_blank_line(b, offset, &offset1)) {
if ((offset == 0 || !eb_is_blank_line(b,
eb_prev_line(b, offset), NULL))
&& (offset1 >= b->total_size || !eb_is_blank_line(b,
offset1, NULL))) {
all = 1;
}
} else {
offset = eb_next_paragraph(b, offset);
all = 1;
}
from = offset;
while (from > 0) {
offset0 = eb_prev_line(b, from);
if (!eb_is_blank_line(b, offset0, NULL))
break;
from = offset0;
}
if (!all) {
eb_delete_range(b, from, offset);
/* Keep current blank line */
from = offset = eb_next_line(b, from);
}
while (offset < s->b->total_size) {
if (!eb_is_blank_line(b, offset, &offset))
break;
}
eb_delete_range(b, from, offset);
}
static void do_tabify(EditState *s, int p1, int p2)
{
/* We implement a complete analysis of the region instead of
* scanning for certain space patterns (such as / [ \t]/). It is
* fast enough, sometimes even faster, more concise and more
* correct for pathological case where initial space falls on tab
* column.
*/
/* XXX: should extend for language modes to not replace spaces
* inside character constants, strings, regex, comments,
* preprocessor, etc. Implementation is not too difficult with a
* new buffer reader eb_nextc_style() using colorizer and a
* one line cache.
*/
EditBuffer *b = s->b;
int tw = b->tab_width > 0 ? b->tab_width : 8;
int start = max(0, min(p1, p2));
int stop = min(b->total_size, max(p1, p2));
int col;
int offset, offset1, offset2, delta;
/* deactivate region hilite */
s->region_style = 0;
col = 0;
offset = eb_goto_bol(b, start);
for (; offset < stop; offset = offset1) {
int c = eb_nextc(b, offset, &offset1);
if (c == '\r' || c == '\n') {
col = 0;
continue;
}
if (c == '\t') {
col += tw - col % tw;
continue;
}
col += unicode_tty_glyph_width(c);
if (c != ' ' || offset < start || col % tw == 0)
continue;
while (offset1 < stop) {
c = eb_nextc(b, offset1, &offset2);
if (c == ' ') {
col += unicode_tty_glyph_width(c);
offset1 = offset2;
if (col % tw == 0) {
delta = eb_delete_range(b, offset, offset1);
delta = eb_insert_uchar(b, offset, '\t') - delta;
offset1 += delta;
stop += delta;
break;
}
continue;
} else
if (c == '\t') {
col += tw - col % tw;
delta = -eb_delete_range(b, offset, offset1);
offset1 = offset2;
offset1 += delta;
stop += delta;
}
break;
}
}
}
#if 0
static void do_tabify_buffer(EditState *s)
{
/* deactivate region hilite */
s->region_style = 0;
eb_tabify(s->b, 0, s->b->total_size);
}
static void do_tabify_region(EditState *s)
{
/* deactivate region hilite */
s->region_style = 0;
eb_tabify(s->b, s->b->mark, s->offset);
}
#endif
static void do_untabify(EditState *s, int p1, int p2)
{
/* We implement a complete analysis of the region instead of
* potentially faster scan for '\t'. It is fast enough and even
* faster if there are lots of tabs.
*/
EditBuffer *b = s->b;
int tw = b->tab_width > 0 ? b->tab_width : 8;
int start = max(0, min(p1, p2));
int stop = min(b->total_size, max(p1, p2));
int col, col0;
int offset, offset1, offset2, delta;
/* deactivate region hilite */
s->region_style = 0;
col = 0;
offset = eb_goto_bol(b, start);
for (; offset < stop; offset = offset1) {
int c = eb_nextc(b, offset, &offset1);
if (c == '\r' || c == '\n') {
col = 0;
continue;
}
if (c != '\t') {
col += unicode_tty_glyph_width(c);
continue;
}
col0 = col;
col += tw - col % tw;
if (offset < start)
continue;
while (eb_nextc(b, offset1, &offset2) == '\t') {
col += tw;
offset1 = offset2;
}
delta = eb_delete_range(b, offset, offset1);
delta = eb_insert_spaces(b, offset, col - col0) - delta;
offset1 += delta;
stop += delta;
}
}
#if 0
static void do_untabify_buffer(EditState *s)
{
/* deactivate region hilite */
s->region_style = 0;
eb_untabify(s->b, 0, s->b->total_size);
}
static void do_untabify_region(EditState *s)
{
/* deactivate region hilite */
s->region_style = 0;
eb_untabify(s->b, s->b->mark, s->offset);
}
#endif
static void do_indent_region(EditState *s)
{
int col_num, line1, line2;
/* deactivate region hilite */
s->region_style = 0;
/* Swap point and mark so mark <= point */
if (s->offset < s->b->mark) {
int tmp = s->b->mark;
s->b->mark = s->offset;
s->offset = tmp;
}
/* We do it with lines to avoid offset variations during indenting */
eb_get_pos(s->b, &line1, &col_num, s->b->mark);
eb_get_pos(s->b, &line2, &col_num, s->offset);
if (col_num == 0)
line2--;
/* Iterate over all lines inside block */
for (; line1 <= line2; line1++) {
if (s->mode->indent_func) {
(s->mode->indent_func)(s, eb_goto_pos(s->b, line1, 0));
} else {
s->offset = eb_goto_pos(s->b, line1, 0);
do_tab(s, 1);
}
}
}
void do_show_date_and_time(EditState *s, int argval)
{
time_t t = argval;
if (argval == NO_ARG)
time(&t);
put_status(s, "%.24s", ctime(&t));
}
/* forward / backward block */
#define MAX_LEVEL 32
/* Return the matching delimiter for all pairs */
static int matching_delimiter(int c) {
const char *pairs = "(){}[]<>";
int i;
for (i = 0; pairs[i]; i++) {
if (pairs[i] == c)
return pairs[i ^ 1];
}
return c;
}
static void forward_block(EditState *s, int dir)
{
unsigned int buf[COLORED_MAX_LINE_SIZE];
QETermStyle sbuf[COLORED_MAX_LINE_SIZE];
char balance[MAX_LEVEL];
int use_colors;
int line_num, col_num, style, style0, c, level;
int pos; /* position of the current character on line */
int len; /* number of colorized positions */
int offset; /* offset of the current character */
int offset0; /* offset of the beginning of line */
int offset1; /* offset of the beginning of the next line */
offset = s->offset;
eb_get_pos(s->b, &line_num, &col_num, offset);
offset1 = offset0 = eb_goto_bol2(s->b, offset, &pos);
use_colors = s->colorize_func || s->b->b_styles;
style0 = 0;
len = 0;
if (use_colors) {
len = get_colorized_line(s, buf, countof(buf), sbuf,
offset1, &offset1, line_num);
if (len < countof(buf) - 2) {
if (pos > 0
&& ((c = buf[pos - 1]) == ']' || c == '}' || c == ')')) {
style0 = sbuf[pos - 1];
} else
if (pos < len) {
style0 = sbuf[pos];
}
} else {
/* very long line detected, use fallback version */
use_colors = 0;
len = 0;
}
}
level = 0;
if (dir < 0) {
for (;;) {
c = eb_prevc(s->b, offset, &offset);
if (c == '\n') {
if (offset <= 0)
break;
line_num--;
offset1 = offset0 = eb_goto_bol2(s->b, offset, &pos);
len = 0;
if (use_colors) {
len = get_colorized_line(s, buf, countof(buf), sbuf,
offset1, &offset1, line_num);
if (len >= countof(buf) - 2) {
/* very long line detected, use fallback version */
use_colors = 0;
len = 0;
style0 = 0;
}
}
continue;
}
style = 0;
--pos;
if (pos >= 0 && pos < len) {
style = sbuf[pos];
}
if (style != style0 && style != QE_STYLE_KEYWORD && style != QE_STYLE_FUNCTION) {
if (style0 == 0)
continue;
style0 = 0;
if (style != 0)
continue;
}
switch (c) {
case '\"':
case '\'':
if (pos >= len) {
/* simplistic string skip with escape char */
int c1, off;
while ((c1 = eb_prevc(s->b, offset, &off)) != '\n') {
offset = off;
pos--;
if (c1 == c && eb_prevc(s->b, offset, &off) != '\\')
break;
}
}
break;
case ')':
case ']':
case '}':
if (level < MAX_LEVEL) {
balance[level] = matching_delimiter(c);
}
level++;
break;
case '(':
case '[':
case '{':
if (level > 0) {
--level;
if (level < MAX_LEVEL && balance[level] != c) {
/* XXX: should set mark and offset */
put_status(s, "Unmatched delimiter %c <> %c",
c, balance[level]);
return;
}
if (level == 0) {
s->offset = offset;
return;
}
} else {
/* silently move up one level */
}
break;
}
}
} else {
for (;;) {
c = eb_nextc(s->b, offset, &offset);
if (c == '\n') {
line_num++;
if (offset >= s->b->total_size)
break;
offset1 = offset0 = offset;
len = 0;
if (use_colors) {
len = get_colorized_line(s, buf, countof(buf), sbuf,
offset1, &offset1, line_num);
if (len >= countof(buf) - 2) {
/* very long line detected, use fallback version */
use_colors = 0;
len = 0;
style0 = 0;
}
}
pos = 0;
continue;
}
style = 0;
if (pos < len) {
style = sbuf[pos];
}
pos++;
if (style0 != style && style != QE_STYLE_KEYWORD && style != QE_STYLE_FUNCTION) {
if (style0 == 0)
continue;
style0 = 0;
if (style != 0)
continue;
}
switch (c) {
case '\"':
case '\'':
if (pos >= len) {
/* simplistic string skip with escape char */
int c1, off;
while ((c1 = eb_nextc(s->b, offset, &off)) != '\n') {
offset = off;
pos++;
if (c1 == '\\') {
if (eb_nextc(s->b, offset, &off) == '\n')
break;
offset = off;
pos++;
} else
if (c1 == c) {
break;
}
}
}
break;
case '(':
case '[':
case '{':
if (level < MAX_LEVEL) {
balance[level] = matching_delimiter(c);
}
level++;
break;
case ')':
case ']':
case '}':
if (level > 0) {
--level;
if (level < MAX_LEVEL && balance[level] != c) {
/* XXX: should set mark and offset */
put_status(s, "Unmatched delimiter %c <> %c",
c, balance[level]);
return;
}
if (level == 0) {
s->offset = offset;
return;
}
} else {
/* silently move up one level */
}
break;
}
}
}
if (level != 0) {
/* XXX: should set mark and offset */
put_status(s, "Unmatched delimiter");
} else {
s->offset = offset;
}
}
static void do_forward_block(EditState *s, int n)
{
int dir = n < 0 ? -1 : 1;
for (; n != 0; n -= dir) {
forward_block(s, dir);
}
}
static void do_kill_block(EditState *s, int n)
{
int start = s->offset;
if (n != 0) {
do_forward_block(s, n);
do_kill(s, start, s->offset, n, 0);
}
}
void do_transpose(EditState *s, int cmd)
{
QEmacsState *qs = s->qe_state;
int offset0, offset1, offset2, offset3, end_offset;
int size0, size1, size2;
EditBuffer *b = s->b;
if (check_read_only(s))
return;
/* compute positions of ranges to swap:
offset0..offset1 and offset2..offset3
end_offset is the ending position after the swap
*/
switch (cmd) {
case CMD_TRANSPOSE_CHARS:
/* at end of line, transpose previous 2 characters,
* otherwise transpose characters before and after point.
*/
offset1 = offset2 = s->offset;
offset0 = eb_prev(b, offset1);
if (eb_nextc(b, offset2, &offset3) == '\n') {
end_offset = offset3 = offset2;
offset1 = offset2 = offset0;
offset0 = eb_prev(b, offset1);
} else {
/* XXX: should have specific flag */
if (qs->emulation_flags == 1) {
/* keep current position between characters */
end_offset = offset0 + offset3 - offset2;
} else {
/* set position past second character (emacs behaviour) */
end_offset = offset3;
}
}
break;
case CMD_TRANSPOSE_WORDS:
word_right(s, 1);
word_right(s, 0);
offset3 = s->offset;
word_left(s, 0);
offset2 = s->offset;
word_left(s, 1);
offset1 = s->offset;
word_left(s, 0);
offset0 = s->offset;
if (qs->emulation_flags == 1) {
/* set position to end of first word */
end_offset = offset0 + offset3 - offset2;
} else {
/* set position past last word (emacs behaviour) */
end_offset = offset3;
}
break;
case CMD_TRANSPOSE_LINES:
do_eol(s);
offset3 = s->offset;
do_bol(s);
offset2 = s->offset;
offset1 = eb_prev(b, offset2); /* skip line feed */
s->offset = offset1;
do_bol(s);
offset0 = s->offset;
if (qs->emulation_flags == 1) {
/* set position to start of second line */
end_offset = offset0 + offset3 - offset1;
} else {
/* set position past second line (emacs behaviour) */
end_offset = offset3;
}
break;
default:
return;
}
size0 = offset1 - offset0;
size1 = offset2 - offset1;
size2 = offset3 - offset2;
if (!b->b_styles && size0 + size1 + size2 <= 1024) {
u8 buf[1024];
/* Use fast method and generate single undo record */
eb_read(b, offset2, buf, size2);
eb_read(b, offset1, buf + size2, size1);
eb_read(b, offset0, buf + size2 + size1, size0);
eb_write(b, offset0, buf, size0 + size1 + size2);
} else {
EditBuffer *b1 = eb_new("*tmp*", BF_SYSTEM | (b->flags & BF_STYLES));
eb_set_charset(b1, b->charset, b->eol_type);
/* Use eb_insert_buffer_convert to copy styles.
* This conversion should not change sizes */
eb_insert_buffer_convert(b1, 0, b, offset2, size2);
eb_insert_buffer_convert(b1, size2, b, offset1, size1);
eb_insert_buffer_convert(b1, size2 + size1, b, offset0, size0);
/* XXX: This will create 2 undo records */
eb_delete(b, offset0, size0 + size1 + size2);
eb_insert_buffer_convert(b, offset0, b1, 0, b1->total_size);
eb_free(&b1);
}
s->offset = end_offset;
}
/* remove a key binding from mode or globally */
static int qe_unregister_binding1(unsigned int *keys, int nb_keys, ModeDef *m)
{
QEmacsState *qs = &qe_state;
KeyDef **lp, *p;
lp = m ? &m->first_key : &qs->first_key;
while (*lp) {
if ((*lp)->nb_keys == nb_keys
&& !memcmp((*lp)->keys, keys, nb_keys * sizeof(*keys)))
{
p = *lp;
*lp = (*lp)->next;
qe_free(&p);
return 1;
}
lp = &(*lp)->next;
}
return 0;
}
static void do_unset_key(EditState *s, const char *keystr, int local)
{
unsigned int keys[MAX_KEYS];
int nb_keys;
nb_keys = strtokeys(keystr, keys, MAX_KEYS);
if (!nb_keys)
return;
qe_unregister_binding1(keys, nb_keys, local ? s->mode : NULL);
}
/*---------------- help ----------------*/
int qe_list_bindings(CmdDef *d, ModeDef *mode, int inherit, char *buf, int size)
{
int pos;
buf_t outbuf, *out;
out = buf_init(&outbuf, buf, size);
pos = 0;
for (;;) {
KeyDef *kd = mode ? mode->first_key : qe_state.first_key;
for (; kd != NULL; kd = kd->next) {
if (kd->cmd == d
&& qe_find_current_binding(kd->keys, kd->nb_keys, mode) == kd) {
if (out->len > pos)
buf_puts(out, ", ");
buf_put_keys(out, kd->keys, kd->nb_keys);
}
}
if (!inherit || !mode)
break;
/* Move up to base mode */
mode = mode->fallback;
}
return out->len;
}
void do_show_bindings(EditState *s, const char *cmd_name)
{
char buf[256];
CmdDef *d;
if ((d = qe_find_cmd(cmd_name)) == NULL) {
put_status(s, "No command %s", cmd_name);
return;
}
if (qe_list_bindings(d, s->mode, 1, buf, sizeof(buf))) {
put_status(s, "%s is bound to %s", cmd_name, buf);
} else {
put_status(s, "%s is not bound to any key", cmd_name);
}
}
#define SF_REVERSE 1
#define SF_FOLD 2
#define SF_DICT 4
#define SF_NUMBER 8
#define SF_COLUMN 16
#define SF_BASENAME 32
#define SF_PARAGRAPH 64
static int eb_sort_span(EditBuffer *b, int *pp1, int *pp2, int cur_offset, int flags);
static void print_bindings(EditBuffer *b, ModeDef *mode)
{
char buf[256];
CmdDef *d;
int gfound;
int start = b->total_size;
int stop;
gfound = 0;
d = qe_state.first_cmd;
while (d != NULL) {
while (d->name != NULL) {
if (qe_list_bindings(d, mode, 0, buf, sizeof(buf))) {
if (!gfound) {
if (mode) {
eb_printf(b, "\n%s mode bindings:\n\n", mode->name);
} else {
eb_printf(b, "\nGlobal bindings:\n\n");
}
start = b->total_size;
gfound = 1;
}
eb_printf(b, "%24s : %s\n", d->name, buf);
}
d++;
}
d = d->action.next;
}
if (gfound) {
stop = b->total_size;
eb_sort_span(b, &start, &stop, stop, SF_DICT);
}
}
void do_describe_bindings(EditState *s)
{
EditBuffer *b;
b = new_help_buffer();
if (!b)
return;
print_bindings(b, s->mode);
print_bindings(b, NULL);
b->flags |= BF_READONLY;
show_popup(s, b, "Bindings");
}
void do_apropos(EditState *s, const char *str)
{
QEmacsState *qs = s->qe_state;
char buf[256];
EditBuffer *b;
CmdDef *d;
VarDef *vp;
int found, start, stop;
b = new_help_buffer();
if (!b)
return;
eb_putc(b, '\n');
start = b->total_size;
found = 0;
d = qs->first_cmd;
while (d != NULL) {
while (d->name != NULL) {
if (strstr(d->name, str)) {
const char *desc;
/* print name and prototype */
qe_get_prototype(d, buf, sizeof(buf));
eb_printf(b, "command: %s%s", d->name, buf);
if (qe_list_bindings(d, s->mode, 1, buf, sizeof(buf)))
eb_printf(b, " bound to %s", buf);
eb_putc(b, '\n');
if (*(desc = d->spec + strlen(d->spec) + 1)) {
/* print short description */
eb_printf(b, " %s", desc);
}
eb_putc(b, '\n');
found = 1;
}
d++;
}
d = d->action.next;
}
stop = b->total_size;
eb_sort_span(b, &start, &stop, stop, SF_DICT | SF_PARAGRAPH);
start = b->total_size;
for (vp = qs->first_variable; vp; vp = vp->next) {
if (strstr(vp->name, str)) {
/* print class, name and current value */
qe_get_variable(s, vp->name, buf, sizeof(buf), NULL, 1);
eb_printf(b, "%s variable: %s -> %s\n",
var_domain[vp->domain], vp->name, buf);
if (vp->desc && *vp->desc) {
/* print short description */
eb_printf(b, " %s", vp->desc);
}
eb_putc(b, '\n');
found = 1;
}
}
stop = b->total_size;
eb_sort_span(b, &start, &stop, stop, SF_DICT | SF_PARAGRAPH);
if (found) {
b->flags |= BF_READONLY;
snprintf(buf, sizeof buf, "Apropos '%s'", str);
show_popup(s, b, buf);
} else {
eb_free(&b);
put_status(s, "No apropos matches for `%s'", str);
}
}
#ifndef CONFIG_CYGWIN
extern char **environ;
#endif
static void do_about_qemacs(EditState *s)
{
QEmacsState *qs = s->qe_state;
char buf[256];
EditBuffer *b;
ModeDef *m;
CmdDef *d;
int start, stop;
b = eb_scratch("*About QEmacs*", BF_UTF8);
eb_printf(b, "\n %s\n\n%s\n", str_version, str_credits);
/* list current bindings */
print_bindings(b, s->mode);
print_bindings(b, NULL);
/* other mode bindings */
for (m = qs->first_mode; m; m = m->next) {
if (m != s->mode)
print_bindings(b, m);
}
/* list commands */
eb_printf(b, "\nCommands:\n\n");
start = b->total_size;
d = qs->first_cmd;
while (d != NULL) {
while (d->name != NULL) {
qe_get_prototype(d, buf, sizeof(buf));
eb_printf(b, " %s%s\n", d->name, buf);
d++;
}
d = d->action.next;
}
stop = b->total_size;
eb_sort_span(b, &start, &stop, stop, SF_DICT);
qe_list_variables(s, b);
/* list environment */
{
char **envp;
eb_printf(b, "\nEnvironment:\n\n");
start = b->total_size;
for (envp = environ; *envp; envp++) {
eb_printf(b, " %s\n", *envp);
}
stop = b->total_size;
eb_sort_span(b, &start, &stop, stop, SF_DICT);
}
b->offset = 0;
b->flags |= BF_READONLY;
show_popup(s, b, "About QEmacs");
}
/* extract the next word from the string. ignore spaces, stop on '/' */
static int str_get_word(char *buf, int size, const char *p, const char **pp)
{
int len = 0;
while (*p == ' ')
p++;
if (*p == '/') {
/* special case meta character '/' */
if (len + 1 < size) {
buf[len] = *p;
}
p++;
len++;
} else {
for (; *p != '\0' && *p != ' ' && *p != '/'; p++, len++) {
if (len + 1 < size) {
buf[len] = qe_tolower((unsigned char)*p);
}
}
}
if (size > 0) {
if (len < size)
buf[len] = '\0';
else
buf[size - 1] = '\0';
}
while (*p == ' ')
p++;
if (pp)
*pp = p;
return len;
}
static int qe_term_get_style(const char *str, QETermStyle *style)
{
char buf[128];
QEColor fg_color, bg_color;
unsigned int fg, bg, attr;
int len;
const char *p = str;
attr = 0;
for (;;) {
len = str_get_word(buf, sizeof(buf), p, &p);
if (strfind("bold|strong", buf)) {
attr |= QE_TERM_BOLD;
continue;
}
if (strfind("italic|italics", buf)) {
attr |= QE_TERM_ITALIC;
continue;
}
if (strfind("underlined|underline", buf)) {
attr |= QE_TERM_UNDERLINE;
continue;
}
if (strfind("blinking|blink", buf)) {
attr |= QE_TERM_BLINK;
continue;
}
break;
}
fg_color = QERGB(0xbb, 0xbb, 0xbb);
bg_color = QERGB(0x00, 0x00, 0x00);
if (len > 0) {
if (css_get_color(&fg_color, buf))
return 1;
len = str_get_word(buf, sizeof(buf), p, &p);
if (strfind("/|on", buf)) {
str_get_word(buf, sizeof(buf), p, &p);
if (css_get_color(&bg_color, buf))
return 2;
}
}
fg = qe_map_color(fg_color, xterm_colors, QE_TERM_FG_COLORS, NULL);
bg = qe_map_color(bg_color, xterm_colors, QE_TERM_BG_COLORS, NULL);
*style = QE_TERM_COMPOSITE | attr | QE_TERM_MAKE_COLOR(fg, bg);
return 0;
}
static void do_set_region_color(EditState *s, const char *str)
{
int offset, size;
QETermStyle style;
/* deactivate region hilite */
s->region_style = 0;
if (qe_term_get_style(str, &style)) {
put_status(s, "Invalid color '%s'", str);
return;
}
offset = s->b->mark;
size = s->offset - offset;
if (size < 0) {
offset += size;
size = -size;
}
if (size > 0) {
eb_create_style_buffer(s->b, QE_TERM_STYLE_BITS <= 16 ? BF_STYLE2 :
QE_TERM_STYLE_BITS <= 32 ? BF_STYLE4 : BF_STYLE8);
eb_set_style(s->b, style, LOGOP_WRITE, offset, size);
}
}
static void do_set_region_style(EditState *s, const char *str)
{
int offset, size;
QETermStyle style;
QEStyleDef *st;
/* deactivate region hilite */
s->region_style = 0;
st = find_style(str);
if (!st) {
put_status(s, "Invalid style '%s'", str);
return;
}
style = st - qe_styles;
offset = s->b->mark;
size = s->offset - offset;
if (size < 0) {
offset += size;
size = -size;
}
if (size > 0) {
eb_create_style_buffer(s->b, QE_TERM_STYLE_BITS <= 16 ? BF_STYLE2 :
QE_TERM_STYLE_BITS <= 32 ? BF_STYLE4 : BF_STYLE8);
eb_set_style(s->b, style, LOGOP_WRITE, offset, size);
}
}
static void do_drop_styles(EditState *s)
{
eb_free_style_buffer(s->b);
s->b->flags &= ~BF_STYLES;
}
static void do_set_eol_type(EditState *s, int eol_type)
{
eb_set_charset(s->b, s->b->charset, eol_type);
}
static void do_describe_buffer(EditState *s, int argval)
{
char buf[256];
buf_t descbuf, *desc;
EditBuffer *b = s->b;
EditBuffer *b1;
b1 = new_help_buffer();
if (!b1)
return;
eb_putc(b1, '\n');
eb_printf(b1, " name: %s\n", b->name);
eb_printf(b1, " filename: %s\n", b->filename);
eb_printf(b1, " modified: %d\n", b->modified);
eb_printf(b1, " total_size: %d\n", b->total_size);
eb_printf(b1, " mark: %d\n", b->mark);
eb_printf(b1, " s->offset: %d\n", s->offset);
eb_printf(b1, " b->offset: %d\n", b->offset);
eb_printf(b1, " tab_width: %d\n", b->tab_width);
eb_printf(b1, " fill_column: %d\n", b->fill_column);
desc = buf_init(&descbuf, buf, countof(buf));
if (b->eol_type == EOL_UNIX)
buf_puts(desc, " unix");
if (b->eol_type == EOL_DOS)
buf_puts(desc, " dos");
if (b->eol_type == EOL_MAC)
buf_puts(desc, " mac");
eb_printf(b1, " eol_type: %d %s\n", b->eol_type, buf);
eb_printf(b1, " charset: %s (bytes=%d, shift=%d)\n",
b->charset->name, b->char_bytes, b->char_shift);
desc = buf_init(&descbuf, buf, countof(buf));
if (b->flags & BF_SAVELOG)
buf_puts(desc, " SAVELOG");
if (b->flags & BF_SYSTEM)
buf_puts(desc, " SYSTEM");
if (b->flags & BF_READONLY)
buf_puts(desc, " READONLY");
if (b->flags & BF_PREVIEW)
buf_puts(desc, " PREVIEW");
if (b->flags & BF_LOADING)
buf_puts(desc, " LOADING");
if (b->flags & BF_SAVING)
buf_puts(desc, " SAVING");
if (b->flags & BF_DIRED)
buf_puts(desc, " DIRED");
if (b->flags & BF_UTF8)
buf_puts(desc, " UTF8");
if (b->flags & BF_RAW)
buf_puts(desc, " RAW");
if (b->flags & BF_TRANSIENT)
buf_puts(desc, " TRANSIENT");
if (b->flags & BF_STYLES)
buf_puts(desc, " STYLES");
eb_printf(b1, " flags: 0x%02x %s\n", b->flags, buf);
#if 0
eb_printf(b1, " probed: %d\n", b->probed);
#endif
if (b->data_mode)
eb_printf(b1, " data_mode: %s\n", b->data_mode->name);
if (b->syntax_mode)
eb_printf(b1, " syntax_mode: %s\n", b->syntax_mode->name);
if (s->mode)
eb_printf(b1, " s->mode: %s\n", s->mode->name);
if (b->default_mode)
eb_printf(b1, "default_mode: %s\n", b->default_mode->name);
if (b->saved_mode)
eb_printf(b1, " saved_mode: %s\n", b->saved_mode->name);
eb_printf(b1, " data_type: %s\n", b->data_type->name);
eb_printf(b1, " pages: %d\n", b->nb_pages);
if (b->map_address) {
eb_printf(b1, " map_address: %p (length=%d, handle=%d)\n",
b->map_address, b->map_length, b->map_handle);
}
eb_printf(b1, " save_log: %d (new_index=%d, current=%d, nb_logs=%d)\n",
b->save_log, b->log_new_index, b->log_current, b->nb_logs);
eb_printf(b1, " styles: %d (cur_style=%lld, bytes=%d, shift=%d)\n",
!!b->b_styles, (long long)b->cur_style,
b->style_bytes, b->style_shift);
if (b->total_size > 0) {
u8 buf[4096];
int count[256];
int total_size = b->total_size;
int offset, c, i, col, max_count, count_width;
int word_char, word_count, nb_chars, line, column;
eb_get_pos(b, &line, &column, total_size);
nb_chars = eb_get_char_offset(b, total_size);
memset(count, 0, sizeof(count));
word_count = 0;
word_char = 0;
for (offset = 0; offset < total_size;) {
int size = eb_read(b, offset, buf, countof(buf));
if (size == 0)
break;
for (i = 0; i < size; i++) {
c = buf[i];
count[c] += 1;
if (c <= 32) {
word_count += word_char;
word_char = 0;
} else {
word_char = 1;
}
}
offset += size;
}
max_count = 0;
for (i = 0; i < 256; i++) {
max_count = max(max_count, count[i]);
}
count_width = snprintf(NULL, 0, "%d", max_count);
eb_printf(b1, " chars: %d\n", nb_chars);
eb_printf(b1, " words: %d\n", word_count);
eb_printf(b1, " lines: %d\n", line + (column > 0));
eb_printf(b1, "\nByte stats:\n");
for (col = i = 0; i < 256; i++) {
if (count[i] == 0)
continue;
switch (i) {
case '\b': c = 'b'; break;
case '\f': c = 'f'; break;
case '\t': c = 't'; break;
case '\r': c = 'r'; break;
case '\n': c = 'n'; break;
case '\\': c = '\\'; break;
case '\'': c = '\''; break;
default: c = 0; break;
}
col += eb_printf(b1, " %*d", count_width, count[i]);
if (c != 0)
col += eb_printf(b1, " '\\%c'", c);
else
if (i >= ' ' && i < 0x7f)
col += eb_printf(b1, " '%c' ", i);
else
col += eb_printf(b1, " 0x%02x", i);
if (col >= 60) {
eb_putc(b1, '\n');
col = 0;
}
}
if (col) {
eb_putc(b1, '\n');
}
}
if (b->nb_pages) {
Page *p;
const u8 *pc;
int i, c, n;
eb_printf(b1, "\nBuffer page layout:\n");
eb_printf(b1, " page size flags lines col chars addr\n");
for (i = 0, p = b->page_table; i < b->nb_pages && i < 100; i++, p++) {
eb_printf(b1, " %4d %4d %5x %5d %4d %5d %p |",
i, p->size, p->flags, p->nb_lines, p->col, p->nb_chars, p->data);
pc = p->data;
n = min(p->size, 16);
while (n-- > 0) {
switch (c = *pc++) {
case '\r': c = 'r'; break;
case '\n': c = 'n'; break;
case '\t': c = 't'; break;
case '\\': break;
default:
if (c < 32 || c >= 127)
eb_printf(b1, "\\%03o", c);
else
eb_putc(b1, c);
continue;
}
eb_printf(b1, "\\%c", c);
}
eb_printf(b1, "|%s\n", p->size > 16 ? "..." : "");
}
eb_putc(b1, '\n');
}
b1->flags |= BF_READONLY;
show_popup(s, b1, "Buffer Description");
}
static void do_describe_window(EditState *s, int argval)
{
EditBuffer *b1;
int w;
b1 = new_help_buffer();
if (!b1)
return;
eb_putc(b1, '\n');
w = 28;
eb_printf(b1, "%*s: %d, %d\n", w, "xleft, ytop", s->xleft, s->ytop);
eb_printf(b1, "%*s: %d, %d\n", w, "width, height", s->width, s->height);
eb_printf(b1, "%*s: %d, %d, %d, %d\n", w, "x1, y1, x2, y2", s->x1, s->y1, s->x2, s->y2);
eb_printf(b1, "%*s: %#x%s%s%s%s%s%s%s\n", w, "flags", s->flags,
(s->flags & WF_POPUP) ? " POPUP" : "",
(s->flags & WF_MODELINE) ? " MODELINE" : "",
(s->flags & WF_RSEPARATOR) ? " RSEPARATOR" : "",
(s->flags & WF_POPLEFT) ? " POPLEFT" : "",
(s->flags & WF_MINIBUF) ? " MINIBUF" : "",
(s->flags & WF_HIDDEN) ? " HIDDEN" : "",
(s->flags & WF_FILELIST) ? " FILELIST" : "");
eb_printf(b1, "%*s: %d\n", w, "offset", s->offset);
eb_printf(b1, "%*s: %d\n", w, "offset_top", s->offset_top);
eb_printf(b1, "%*s: %d\n", w, "offset_bottom", s->offset_bottom);
eb_printf(b1, "%*s: %d\n", w, "y_disp", s->y_disp);
eb_printf(b1, "%*s: %d, %d\n", w, "x_disp[]", s->x_disp[0], s->x_disp[1]);
eb_printf(b1, "%*s: %d\n", w, "dump_width", s->dump_width);
eb_printf(b1, "%*s: %d\n", w, "hex_mode", s->hex_mode);
eb_printf(b1, "%*s: %d\n", w, "unihex_mode", s->unihex_mode);
eb_printf(b1, "%*s: %d\n", w, "hex_nibble", s->hex_nibble);
eb_printf(b1, "%*s: %d\n", w, "insert", s->insert);
eb_printf(b1, "%*s: %d\n", w, "bidir", s->bidir);
eb_printf(b1, "%*s: %d\n", w, "cur_rtl", s->cur_rtl);
eb_printf(b1, "%*s: %d %s\n", w, "wrap", s->wrap,
s->wrap == WRAP_AUTO ? "AUTO" :
s->wrap == WRAP_TRUNCATE ? "TRUNCATE" :
s->wrap == WRAP_LINE ? "LINE" :
s->wrap == WRAP_TERM ? "TERM" :
s->wrap == WRAP_WORD ? "WORD" : "???");
eb_printf(b1, "%*s: %d\n", w, "line_numbers", s->line_numbers);
eb_printf(b1, "%*s: %d\n", w, "indent_size", s->indent_size);
eb_printf(b1, "%*s: %d\n", w, "indent_tabs_mode", s->indent_tabs_mode);
eb_printf(b1, "%*s: %d\n", w, "interactive", s->interactive);
eb_printf(b1, "%*s: %d\n", w, "force_highlight", s->force_highlight);
eb_printf(b1, "%*s: %d\n", w, "mouse_force_highlight", s->mouse_force_highlight);
eb_printf(b1, "%*s: %p\n", w, "colorize_func", (void*)s->colorize_func);
eb_printf(b1, "%*s: %lld\n", w, "default_style", (long long)s->default_style);
eb_printf(b1, "%*s: %s\n", w, "buffer", s->b->name);
if (s->last_buffer)
eb_printf(b1, "%*s: %s\n", w, "last_buffer", s->last_buffer->name);
eb_printf(b1, "%*s: %s\n", w, "mode", s->mode->name);
eb_printf(b1, "%*s: %d\n", w, "colorize_nb_lines", s->colorize_nb_lines);
eb_printf(b1, "%*s: %d\n", w, "colorize_nb_valid_lines", s->colorize_nb_valid_lines);
eb_printf(b1, "%*s: %d\n", w, "colorize_max_valid_offset", s->colorize_max_valid_offset);
eb_printf(b1, "%*s: %d\n", w, "busy", s->busy);
eb_printf(b1, "%*s: %d\n", w, "display_invalid", s->display_invalid);
eb_printf(b1, "%*s: %d\n", w, "borders_invalid", s->borders_invalid);
eb_printf(b1, "%*s: %d\n", w, "show_selection", s->show_selection);
eb_printf(b1, "%*s: %d\n", w, "region_style", s->region_style);
eb_printf(b1, "%*s: %d\n", w, "curline_style", s->curline_style);
eb_putc(b1, '\n');
b1->flags |= BF_READONLY;
show_popup(s, b1, "Window Description");
}
static void do_describe_screen(EditState *e, int argval)
{
QEditScreen *s = e->screen;
EditBuffer *b1;
int w;
b1 = new_help_buffer();
if (!b1)
return;
eb_putc(b1, '\n');
w = 16;
eb_printf(b1, "%*s: %s\n", w, "dpy.name", s->dpy.name);
eb_printf(b1, "%*s: %d, %d\n", w, "width, height", s->width, s->height);
eb_printf(b1, "%*s: %s\n", w, "charset", s->charset->name);
eb_printf(b1, "%*s: %d\n", w, "media", s->media);
eb_printf(b1, "%*s: %d\n", w, "bitmap_format", s->bitmap_format);
eb_printf(b1, "%*s: %d\n\n", w, "video_format", s->video_format);
eb_printf(b1, "%*s: %d\n", w, "QE_TERM_STYLE_BITS", QE_TERM_STYLE_BITS);
eb_printf(b1, "%*s: %x << %d\n", w, "QE_TERM_FG_COLORS", QE_TERM_FG_COLORS, QE_TERM_FG_SHIFT);
eb_printf(b1, "%*s: %x << %d\n\n", w, "QE_TERM_BG_COLORS", QE_TERM_BG_COLORS, QE_TERM_BG_SHIFT);
dpy_describe(s, b1);
b1->flags |= BF_READONLY;
show_popup(e, b1, "Screen Description");
}
/*---------------- buffer contents sorting ----------------*/
struct chunk_ctx {
EditBuffer *b;
int flags;
int col;
};
struct chunk {
int start, end;
};
static int eb_skip_to_basename(EditBuffer *b, int pos) {
int base = pos;
int c;
while ((c = eb_nextc(b, pos, &pos)) != EOF && c != '\n') {
if (c == '/' || c == '\\')
base = pos;
}
return base;
}
static int chunk_cmp(void *vp0, const void *vp1, const void *vp2) {
const struct chunk_ctx *cp = vp0;
const struct chunk *p1 = vp1;
const struct chunk *p2 = vp2;
int pos1, pos2, col;
if (cp->flags & SF_REVERSE) {
p1 = vp2;
p2 = vp1;
}
pos1 = p1->start;
pos2 = p2->start;
if (cp->flags & SF_BASENAME) {
pos1 = eb_skip_to_basename(cp->b, pos1);
pos2 = eb_skip_to_basename(cp->b, pos2);
}
if (cp->col) {
for (col = cp->col; col-- > 0 && pos1 < p1->end;)
eb_nextc(cp->b, pos1, &pos1);
for (col = cp->col; col-- > 0 && pos2 < p2->end;)
eb_nextc(cp->b, pos2, &pos2);
}
for (;;) {
int c1 = 0, c2 = 0;
while (pos1 < p1->end) {
c1 = eb_nextc(cp->b, pos1, &pos1);
if (!(cp->flags & SF_DICT) || qe_isalpha(c1))
break;
c1 = 0;
}
while (pos2 < p2->end) {
c2 = eb_nextc(cp->b, pos2, &pos2);
if (!(cp->flags & SF_DICT) || qe_isalpha(c2))
break;
c2 = 0;
}
if ((cp->flags & SF_NUMBER) && qe_isdigit(c1) && qe_isdigit(c2)) {
unsigned long long n1 = c1 - '0';
unsigned long long n2 = c2 - '0';
c1 = 0;
while (pos1 < p1->end) {
c1 = eb_nextc(cp->b, pos1, &pos1);
if (!qe_isdigit(c1))
break;
n1 = n1 * 10 + c1 - '0';
c1 = 0;
}
c2 = 0;
while (pos2 < p2->end) {
c2 = eb_nextc(cp->b, pos2, &pos2);
if (!qe_isdigit(c2))
break;
n2 = n2 * 10 + c2 - '0';
c2 = 0;
}
if (n1 < n2)
return -1;
if (n1 > n2)
return +1;
}
if (cp->flags & SF_FOLD) {
// XXX: should support unicode case folding
c1 = qe_toupper(c1);
c2 = qe_toupper(c2);
}
if (c1 < c2)
return -1;
if (c1 > c2)
return +1;
if (c1 == 0)
break;
}
/* make sort stable by comparing offsets of equal elements */
return (p1->start > p2->start) - (p1->start < p2->start);
}
static int eb_sort_span(EditBuffer *b, int *pp1, int *pp2, int cur_offset, int flags) {
struct chunk_ctx ctx;
EditBuffer *b1;
int p1 = *pp1, p2 = *pp2;
int i, offset, line1, line2, col1, col2, line, col, lines;
struct chunk *chunk_array;
if (p1 > p2) {
int tmp = p1;
p1 = p2;
p2 = tmp;
}
ctx.b = b;
ctx.flags = flags;
ctx.col = 0;
eb_get_pos(b, &line1, &col1, p1); /* line1 is included */
eb_get_pos(b, &line2, &col2, p2); /* line2 is excluded */
if (col1 > 0) {
p1 = eb_goto_bol(b, p1);
}
if (col2 > 0) { /* include incomplete end line */
line2++;
p2 = eb_next_line(b, p2);
}
/* XXX: should also support rectangular selection */
if (flags & SF_COLUMN) {
eb_get_pos(b, &line, &col, cur_offset);
ctx.col = col ? col : col1;
}
lines = line2 - line1;
chunk_array = qe_malloc_array(struct chunk, lines);
if (!chunk_array) {
return -1;
}
offset = p1;
for (i = 0; i < lines && offset < p2; i++) {
chunk_array[i].start = offset;
chunk_array[i].end = offset = eb_goto_eol(b, offset);
offset = eb_next(b, offset);
if (flags & SF_PARAGRAPH) {
int offset1;
/* paragraph sorting: skip continuation lines */
while (offset < p2 && qe_isspace(eb_nextc(b, offset, &offset1))) {
chunk_array[i].end = offset = eb_goto_eol(b, offset);
offset = eb_next(b, offset);
}
}
}
lines = i;
qe_qsort_r(chunk_array, lines, sizeof(*chunk_array), &ctx, chunk_cmp);
b1 = eb_new("*sorted*", BF_SYSTEM);
eb_set_charset(b1, b->charset, b->eol_type);
for (i = 0; i < lines; i++) {
/* XXX: should keep track of point if sorting full buffer */
eb_insert_buffer(b1, b1->total_size, b, chunk_array[i].start,
chunk_array[i].end - chunk_array[i].start);
eb_putc(b1, '\n');
}
eb_delete_range(b, p1, p2);
*pp1 = p1;
*pp2 = p1 + eb_insert_buffer(b, p1, b1, 0, b1->total_size);
eb_free(&b1);
qe_free(&chunk_array);
return 0;
}
static void do_sort_span(EditState *s, int p1, int p2, int flags, int argval) {
s->region_style = 0;
if (argval != NO_ARG)
flags |= argval;
if (eb_sort_span(s->b, &p1, &p2, s->offset, flags) < 0) {
put_status(s, "Out of memory");
return;
}
s->b->mark = p1;
s->offset = p2;
}
static void do_sort_region(EditState *s, int flags, int argval) {
do_sort_span(s, s->b->mark, s->offset, flags, argval);
}
static void do_sort_buffer(EditState *s, int flags, int argval) {
do_sort_span(s, 0, s->b->total_size, flags, argval);
}
static void tag_buffer(EditState *s) {
unsigned int buf[COLORED_MAX_LINE_SIZE];
QETermStyle sbuf[COLORED_MAX_LINE_SIZE];
int offset, line_num, col_num;
if (s->colorize_func || s->b->b_styles) {
/* force complete buffer colorization */
eb_get_pos(s->b, &line_num, &col_num, s->b->total_size);
get_colorized_line(s, buf, countof(buf), sbuf,
s->b->total_size, &offset, line_num);
}
}
static void tag_complete(CompleteState *cp) {
/* XXX: only support current buffer */
QEProperty *p;
if (cp->target) {
tag_buffer(cp->target);
for (p = cp->target->b->property_list; p; p = p->next) {
if (p->type == QE_PROP_TAG) {
complete_test(cp, p->data);
}
}
}
}
static int tag_print_entry(CompleteState *cp, EditState *s, const char *name) {
if (cp->target) {
EditBuffer *b = cp->target->b;
QEProperty *p;
if (!s->colorize_func && cp->target->colorize_func) {
set_colorize_func(s, cp->target->colorize_func, cp->target->colorize_mode);
}
for (p = b->property_list; p; p = p->next) {
if (p->type == QE_PROP_TAG && strequal(p->data, name)) {
int offset = eb_goto_bol(b, p->offset);
int offset1 = eb_goto_eol(b, p->offset);
return eb_insert_buffer_convert(s->b, s->b->total_size,
b, offset, offset1 - offset);
}
}
}
return eb_puts(s->b, name);
}
static int tag_get_entry(EditState *s, char *dest, int size, int offset)
{
int len = eb_fgets(s->b, dest, size, offset, &offset);
int p2 = strcspn(dest, "=[{(,;");
int p1;
while (p2 > 0 && !qe_isalnum_(dest[p2 - 1]))
p2--;
p1 = p2;
while (p1 > 0 && (qe_isalnum_(dest[p1 - 1]) || dest[p1 - 1] == '-'))
p1--;
memmove(dest, dest + p1, len = p2 - p1);
dest[len] = '\0'; /* strip the prototype or trailing newline if any */
return len;
}
static void do_find_tag(EditState *s, const char *str) {
QEProperty *p;
tag_buffer(s);
for (p = s->b->property_list; p; p = p->next) {
if (p->type == QE_PROP_TAG && strequal(p->data, str)) {
s->offset = p->offset;
return;
}
}
put_status(s, "Tag not found: %s", str);
}
static void do_goto_tag(EditState *s) {
char buf[80];
size_t len;
len = qe_get_word(s, buf, sizeof buf, s->offset, NULL);
if (len >= sizeof(buf)) {
put_status(s, "Tag too large");
return;
} else
if (len == 0) {
put_status(s, "No tag");
return;
} else {
do_find_tag(s, buf);
}
}
/* XXX: should have next-tag and previous-tag */
static void do_list_tags(EditState *s) {
char buf[256];
EditBuffer *b;
QEProperty *p;
EditState *e1;
b = new_help_buffer();
if (!b)
return;
tag_buffer(s);
snprintf(buf, sizeof buf, "Tags in file %s", s->b->filename);
for (p = s->b->property_list; p; p = p->next) {
if (p->type == QE_PROP_TAG) {
//eb_printf(b, "%12d %s\n", p->offset, (char*)p->data);
int offset = eb_goto_bol(s->b, p->offset);
int offset1 = eb_goto_eol(s->b, p->offset);
eb_insert_buffer_convert(b, b->total_size, s->b, offset, offset1 - offset);
eb_putc(b, '\n');
}
}
b->flags |= BF_READONLY;
e1 = show_popup(s, b, buf);
if (s->colorize_func) {
set_colorize_func(e1, s->colorize_func, s->colorize_mode);
}
}
static CompletionDef tag_completion = {
"tag", tag_complete, tag_print_entry, tag_get_entry
};
static CmdDef extra_commands[] = {
CMD2( KEY_META('='), KEY_NONE,
"compare-windows", do_compare_windows, ESi, "p" , "")
CMD3( KEY_CTRLX(KEY_CTRL('l')), KEY_NONE,
"compare-files", do_compare_files, ESsi, 0,
"s{Compare file: }[file]|file|"
"v", "") /* u? */
CMD2( KEY_META('\\'), KEY_NONE,
"delete-horizontal-space", do_delete_horizontal_space, ES, "*", "")
CMD2( KEY_CTRLX(KEY_CTRL('o')), KEY_NONE,
"delete-blank-lines", do_delete_blank_lines, ES, "*", "")
CMD3( KEY_NONE, KEY_NONE,
"tabify-region", do_tabify, ESii, 0, "*" "md",
"Convert multiple spaces in region to tabs when possible")
CMD3( KEY_NONE, KEY_NONE,
"untabify-region", do_untabify, ESii, 0, "*" "md",
"Convert all tabs in region to multiple spaces, preserving columns")
CMD3( KEY_NONE, KEY_NONE,
"tabify-buffer", do_tabify, ESii, 0, "*" "ze",
"Convert multiple spaces in buffer to tabs when possible")
CMD3( KEY_NONE, KEY_NONE,
"untabify-buffer", do_untabify, ESii, 0, "*" "ze",
"Convert all tabs in buffer to multiple spaces, preserving columns")
/* XXX: should take region as argument, implicit from keyboard */
CMD2( KEY_META(KEY_CTRL('\\')), KEY_NONE,
"indent-region", do_indent_region, ES, "*",
"Indent each nonblank line in the region")
CMD2( KEY_CTRLX('t'), KEY_NONE,
"show-date-and-time", do_show_date_and_time, ESi, "p", "")
/* Should map to KEY_META + KEY_CTRL_LEFT */
CMD3( KEY_META(KEY_CTRL('b')), KEY_NONE,
"backward-block", do_forward_block, ESi, -1, "P", "")
/* Should map to KEY_META + KEY_CTRL_RIGHT */
CMD3( KEY_META(KEY_CTRL('f')), KEY_NONE,
"forward-block", do_forward_block, ESi, +1, "P", "")
CMD3( KEY_ESC, KEY_DELETE,
"backward-kill-block", do_kill_block, ESi, -1, "P", "")
CMD3( KEY_META(KEY_CTRL('k')), KEY_NONE,
"kill-block", do_kill_block, ESi, +1, "P", "")
/* Should also have mark-block on C-M-@ */
CMD3( KEY_CTRL('t'), KEY_NONE,
"transpose-chars", do_transpose, ESi, CMD_TRANSPOSE_CHARS, "*v", "")
CMD3( KEY_CTRLX(KEY_CTRL('t')), KEY_NONE,
"transpose-lines", do_transpose, ESi, CMD_TRANSPOSE_LINES, "*v", "")
CMD3( KEY_META('t'), KEY_NONE,
"transpose-words", do_transpose, ESi, CMD_TRANSPOSE_WORDS, "*v", "")
CMD3( KEY_NONE, KEY_NONE,
"global-unset-key", do_unset_key, ESsi, 0,
"s{Unset key globally: }[key]"
"v", "")
CMD3( KEY_NONE, KEY_NONE,
"local-unset-key", do_unset_key, ESsi, 1,
"s{Unset key locally: }[key]"
"v", "")
CMD0( KEY_CTRLH('?'), KEY_F1,
"about-qemacs", do_about_qemacs, "")
CMD2( KEY_CTRLH('a'), KEY_CTRLH(KEY_CTRL('A')),
"apropos", do_apropos, ESs,
"s{Apropos: }[symbol]|apropos|", "")
CMD0( KEY_CTRLH('b'), KEY_NONE,
"describe-bindings", do_describe_bindings, "")
CMD2( KEY_CTRLH('B'), KEY_NONE,
"show-bindings", do_show_bindings, ESs,
"s{Show bindings of command: }[command]|command|", "")
CMD2( KEY_CTRLH(KEY_CTRL('B')), KEY_NONE,
"describe-buffer", do_describe_buffer, ESi, "p", "")
CMD2( KEY_CTRLH('w'), KEY_CTRLH(KEY_CTRL('W')),
"describe-window", do_describe_window, ESi, "p", "")
CMD2( KEY_CTRLH('s'), KEY_CTRLH(KEY_CTRL('S')),
"describe-screen", do_describe_screen, ESi, "p", "")
/* XXX: should take region as argument, implicit from keyboard */
CMD2( KEY_CTRLC('c'), KEY_NONE,
"set-region-color", do_set_region_color, ESs,
"s{Select color: }[color]|color|", "")
CMD2( KEY_CTRLC('s'), KEY_NONE,
"set-region-style", do_set_region_style, ESs,
"s{Select style: }[style]|style|", "")
CMD0( KEY_NONE, KEY_NONE,
"drop-styles", do_drop_styles, "")
CMD2( KEY_NONE, KEY_NONE,
"set-eol-type", do_set_eol_type, ESi,
"p{EOL Type [0=Unix, 1=Dos, 2=Mac]: }", "")
CMD3( KEY_NONE, KEY_NONE,
"sort-buffer", do_sort_buffer, ESii, 0, "*vp", "")
CMD3( KEY_NONE, KEY_NONE,
"reverse-sort-buffer", do_sort_buffer, ESii, SF_REVERSE, "*vp", "")
/* XXX: should take region as argument, implicit from keyboard */
/* XXX: should have sort-lines, sort-numeric-fields, sort-paragraphs */
/* XXX: numeric argument means reverse sort */
CMD3( KEY_NONE, KEY_NONE,
"sort-region", do_sort_region, ESii, 0, "*vp", "")
CMD3( KEY_NONE, KEY_NONE,
"reverse-sort-region", do_sort_region, ESii, SF_REVERSE, "*vp", "")
CMD0( KEY_NONE, KEY_NONE,
"list-tags", do_list_tags, "")
CMD0( KEY_CTRLX(','), KEY_META(KEY_F1),
"goto-tag", do_goto_tag, "")
CMD2( KEY_CTRLX('.'), KEY_NONE,
"find-tag", do_find_tag, ESs,
"s{Find tag: }[tag]|tag|", "")
CMD_DEF_END,
};
static int extras_init(void)
{
int key;
qe_register_cmd_table(extra_commands, NULL);
for (key = KEY_META('0'); key <= KEY_META('9'); key++) {
qe_register_binding(key, "numeric-argument", NULL);
}
qe_register_completion(&tag_completion);
return 0;
}
qe_module_init(extras_init);