* add `BF_ERROR` to identify error source buffers and use it with `next-error`.
* show `HELP` and `ERROR` buffer flags in `describe-buffer`.
* add a dedicated compilation mode with the following bindings:
* `next-error` on `n`, `M-n`
* `previous-error` on `p`, `M-p`
* `scroll-up` on `SPC`
* `scroll-down` on `DEL`
* `search-forward` on `/`
and the following new commands:
* `goto-error` on `RET`, `LF`
* `kill-compilation` on `C-c C-k`
* use `compilation` mode instead of `pager` mode for
`do-compile`, and switch interactive shell buffers to
`compilation` mode when the shell exits (if `shell_buffer_read_only` is set)
* refine popup/sidepane closing in `next-error`:
* remove unnecessary popup check, as the command cannot
be invoked if a popup window is present.
* use `do_delete_window` to close side pane to avoid
screen gap bug with `edit_close`.
3777 lines
123 KiB
C
3777 lines
123 KiB
C
/*
|
|
* QEmacs, extra commands for full version
|
|
*
|
|
* Copyright (c) 2000-2026 Charlie Gordon.
|
|
*
|
|
* 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 AUTHORS OR COPYRIGHT HOLDERS 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.
|
|
*/
|
|
|
|
#include "qe.h"
|
|
#include "variables.h"
|
|
|
|
typedef struct Equivalent {
|
|
struct Equivalent *next;
|
|
char *str1;
|
|
char *str2;
|
|
int prefix_len;
|
|
} Equivalent;
|
|
|
|
Equivalent *create_equivalent(const char *str1, const char *str2) {
|
|
Equivalent *ep;
|
|
|
|
ep = qe_mallocz(Equivalent);
|
|
ep->str1 = qe_strdup(str1);
|
|
ep->str2 = qe_strdup(str2);
|
|
ep->prefix_len = utf8_prefix_len(str1, str2);
|
|
ep->next = NULL;
|
|
return ep;
|
|
}
|
|
|
|
void delete_equivalent(Equivalent *ep) {
|
|
qe_free(&ep->str1);
|
|
qe_free(&ep->str2);
|
|
qe_free(&ep);
|
|
}
|
|
|
|
static void do_define_equivalent(EditState *s, const char *str1, const char *str2)
|
|
{
|
|
QEmacsState *qs = s->qs;
|
|
Equivalent **epp;
|
|
|
|
/* append the definition, ignore duplicates */
|
|
for (epp = &qs->first_equivalent; *epp; epp = &(*epp)->next) {
|
|
Equivalent *ep = *epp;
|
|
if (!strcmp(ep->str1, str1) && !strcmp(ep->str2, str2))
|
|
return;
|
|
}
|
|
*epp = create_equivalent(str1, str2);
|
|
}
|
|
|
|
static void do_delete_equivalent(EditState *s, const char *str)
|
|
{
|
|
QEmacsState *qs = s->qs;
|
|
Equivalent **epp;
|
|
|
|
for (epp = &qs->first_equivalent; *epp; epp = &(*epp)->next) {
|
|
Equivalent *ep = *epp;
|
|
if (!strcmp(ep->str1, str) || !strcmp(ep->str2, str)) {
|
|
*epp = ep->next;
|
|
delete_equivalent(ep);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_list_equivalents(EditState *s, int argval)
|
|
{
|
|
QEmacsState *qs = s->qs;
|
|
EditBuffer *b;
|
|
Equivalent *ep;
|
|
|
|
b = new_help_buffer(s);
|
|
if (!b)
|
|
return;
|
|
|
|
for (ep = qs->first_equivalent; ep; ep = ep->next) {
|
|
eb_print_style(b, QE_STYLE_STRING, " \"%s\"", ep->str1);
|
|
eb_printf(b, " <-> ");
|
|
eb_print_style(b, QE_STYLE_STRING, " \"%s\"", ep->str2);
|
|
eb_putc(b, '\n');
|
|
}
|
|
|
|
show_popup(s, b, "Equivalents");
|
|
}
|
|
|
|
static void qe_free_equivalents(QEmacsState *qs) {
|
|
while (qs->first_equivalent) {
|
|
Equivalent *ep = qs->first_equivalent;
|
|
qs->first_equivalent = ep->next;
|
|
delete_equivalent(ep);
|
|
}
|
|
}
|
|
|
|
static int qe_skip_equivalent(EditState *s,
|
|
EditBuffer *b1, int offset1, int *offset1p,
|
|
EditBuffer *b2, int offset2, int *offset2p)
|
|
{
|
|
QEmacsState *qs = s->qs;
|
|
Equivalent *ep;
|
|
int end1, end2;
|
|
|
|
for (ep = qs->first_equivalent; ep; ep = ep->next) {
|
|
int pos = ep->prefix_len;
|
|
if (pos > 0) {
|
|
if (!(eb_match_str_utf8_reverse(b1, offset1, ep->str1, pos, NULL)
|
|
&& eb_match_str_utf8_reverse(b2, offset2, ep->str2, pos, NULL))
|
|
&& !(eb_match_str_utf8_reverse(b1, offset1, ep->str2, pos, NULL)
|
|
&& eb_match_str_utf8_reverse(b2, offset2, ep->str1, pos, NULL)))
|
|
continue;
|
|
}
|
|
if ((eb_match_str_utf8(b1, offset1, ep->str1 + pos, &end1)
|
|
&& eb_match_str_utf8(b2, offset2, ep->str2 + pos, &end2))
|
|
|| (eb_match_str_utf8(b1, offset1, ep->str2 + pos, &end1)
|
|
&& eb_match_str_utf8(b2, offset2, ep->str1 + pos, &end2))) {
|
|
*offset1p = end1;
|
|
*offset2p = end2;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int qe_skip_style(EditState *s, int offset, int *offsetp, QETermStyle style)
|
|
{
|
|
QEColorizeContext cp[1];
|
|
int line_num, col_num, len, pos;
|
|
int offset0, offset1;
|
|
|
|
if (!s->colorize_mode && !s->b->b_styles)
|
|
return 0;
|
|
|
|
cp_initialize(cp, s);
|
|
eb_get_pos(s->b, &line_num, &col_num, offset);
|
|
offset0 = eb_goto_bol2(s->b, offset, &pos);
|
|
len = get_colorized_line(cp, offset0, &offset1, line_num);
|
|
if (pos >= len || cp->sbuf[pos] != style) {
|
|
cp_destroy(cp);
|
|
return 0;
|
|
}
|
|
while (pos < len && cp->sbuf[pos] == style) {
|
|
offset = eb_next(s->b, offset);
|
|
pos++;
|
|
}
|
|
cp_destroy(cp);
|
|
*offsetp = offset;
|
|
return 1;
|
|
}
|
|
|
|
static int eb_skip_spaces(EditBuffer *b, int offset, int *offsetp)
|
|
{
|
|
int offset0 = offset, offset1;
|
|
|
|
while (offset < b->total_size
|
|
&& qe_isspace(eb_nextc(b, offset, &offset1))) {
|
|
offset = offset1;
|
|
}
|
|
if (offset != offset0) {
|
|
*offsetp = offset;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t eb_checksum_line(EditBuffer *b, int offset, int *offsetp)
|
|
{
|
|
uint32_t checksum = 0U - (offset == b->total_size);
|
|
char32_t c;
|
|
|
|
while ((c = eb_nextc(b, offset, &offset)) != '\n') {
|
|
checksum = ((checksum << 2) + c) ^ (checksum >> 30);
|
|
}
|
|
*offsetp = offset;
|
|
return checksum;
|
|
}
|
|
|
|
static void compare_resync(EditState *s1, EditState *s2,
|
|
int save1, int save2,
|
|
int *offset1_ptr, int *offset2_ptr)
|
|
{
|
|
EditBuffer *b1 = s1->b;
|
|
EditBuffer *b2 = s2->b;
|
|
int pos1, pos2, off1, off2;
|
|
enum { MAX_BYTE_SYNC = 5, MAX_LINE_SYNC = 64 };
|
|
int p1[MAX_LINE_SYNC];
|
|
int p2[MAX_LINE_SYNC];
|
|
uint32_t chk1[MAX_LINE_SYNC];
|
|
uint32_t chk2[MAX_LINE_SYNC];
|
|
int i, j, n;
|
|
|
|
/* first try and detect a simple insertion by comparing from the end of both lines */
|
|
pos1 = eb_goto_eol(b1, save1);
|
|
pos2 = eb_goto_eol(b2, save2);
|
|
n = 0;
|
|
while (pos1 > save1 && pos2 > save2
|
|
&& eb_prevc(b1, pos1, &off1) == eb_prevc(b2, pos2, &off2)) {
|
|
pos1 = off1;
|
|
pos2 = off2;
|
|
n++;
|
|
}
|
|
if (n < MAX_BYTE_SYNC) {
|
|
// Try and find identical lines up to MAX_LINE_SYNC - 2 lines apart
|
|
off1 = eb_goto_bol(b1, save1);
|
|
off2 = eb_goto_bol(b2, save2);
|
|
for (i = 0; i < MAX_LINE_SYNC; i++) {
|
|
chk1[i] = eb_checksum_line(b1, p1[i] = off1, &off1);
|
|
chk2[i] = eb_checksum_line(b2, p2[i] = off2, &off2);
|
|
}
|
|
if (chk1[1] == chk2[1] && chk1[2] == chk2[2] && chk1[3] == chk2[3])
|
|
n = MAX_BYTE_SYNC;
|
|
}
|
|
if (n >= MAX_BYTE_SYNC) {
|
|
// Isolated line change: resync on same line
|
|
*offset1_ptr = pos1;
|
|
*offset2_ptr = pos2;
|
|
if (pos1 - save1 == pos2 - save2) {
|
|
put_status(s1, "Skipped %d bytes", pos1 - save1);
|
|
} else {
|
|
put_status(s1, "Skipped %d and %d bytes", pos1 - save1, pos2 - save2);
|
|
}
|
|
return;
|
|
}
|
|
// Compute minimum change set
|
|
for (i = 1; i < 2 * MAX_LINE_SYNC; i++) {
|
|
for (j = 0; j <= i && j < MAX_LINE_SYNC - 2; j++) {
|
|
int i1 = j;
|
|
int i2 = i - j;
|
|
if (i2 < MAX_LINE_SYNC - 2
|
|
&& chk1[i1] == chk2[i2] && chk1[i1 + 1] == chk2[i2 + 1] && chk1[i1 + 2] == chk2[i2 + 2]) {
|
|
int d = 0;
|
|
if (i1 == 0) d = save1 - p1[i1];
|
|
if (i2 == 0) d = save2 - p2[i2];
|
|
*offset1_ptr = p1[i1] + d;
|
|
*offset2_ptr = p2[i2] + d;
|
|
if (i1 == i2) {
|
|
put_status(s1, "Skipped %d lines", i1);
|
|
} else {
|
|
put_status(s1, "Skipped %d and %d lines", i1, i2);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
put_status(s1, "Could not resynchronize");
|
|
}
|
|
|
|
static char *utf8_char32_to_string(char *buf, char32_t c) {
|
|
char *p = buf;
|
|
if (qe_isaccent(c))
|
|
*p++ = ' ';
|
|
p[utf8_encode(p, c)] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
void do_compare_windows(EditState *s, int argval)
|
|
{
|
|
QEmacsState *qs = s->qs;
|
|
EditState *s1;
|
|
EditState *s2;
|
|
int offset1, offset2, size1, size2;
|
|
char32_t ch1, ch2;
|
|
int tries, resync = 0;
|
|
char buf1[MAX_CHAR_BYTES + 2], buf2[MAX_CHAR_BYTES + 2];
|
|
const char *comment1 = "";
|
|
const char *comment2 = "";
|
|
const char *comment3 = "";
|
|
|
|
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 == 0) {
|
|
qs->ignore_spaces = 0;
|
|
qs->ignore_comments = 0;
|
|
qs->ignore_case = 0;
|
|
qs->ignore_preproc = 0;
|
|
qs->ignore_equivalent = 0;
|
|
}
|
|
if (argval & 4)
|
|
qs->ignore_spaces ^= 1;
|
|
if (argval & 16)
|
|
qs->ignore_comments ^= 1;
|
|
if (argval & 64)
|
|
qs->ignore_case ^= 1;
|
|
if (argval & 256)
|
|
qs->ignore_preproc ^= 1;
|
|
if (argval & 1024)
|
|
qs->ignore_equivalent ^= 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) {
|
|
if (s2->offset >= size2) {
|
|
put_status(s, "%s%s%sNo difference",
|
|
comment1, comment2, comment3);
|
|
return;
|
|
}
|
|
offset1 = s1->offset;
|
|
ch1 = 0;
|
|
ch2 = eb_nextc(s2->b, s2->offset, &offset2);
|
|
} else {
|
|
ch1 = eb_nextc(s1->b, s1->offset, &offset1);
|
|
if (s2->offset >= size2) {
|
|
offset2 = s2->offset;
|
|
ch2 = 0;
|
|
} else {
|
|
ch2 = eb_nextc(s2->b, s2->offset, &offset2);
|
|
if (ch1 == ch2) {
|
|
s1->offset = offset1;
|
|
s2->offset = offset2;
|
|
continue;
|
|
}
|
|
if (qs->ignore_case) {
|
|
// XXX: should also ignore accents
|
|
if (qe_wtolower(ch1) == qe_wtolower(ch2)) {
|
|
s1->offset = offset1;
|
|
s2->offset = offset2;
|
|
comment3 = "Matched case, ";
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (qs->ignore_equivalent) {
|
|
if (qe_skip_equivalent(s1, s1->b, s1->offset, &s1->offset,
|
|
s2->b, s2->offset, &s2->offset))
|
|
{
|
|
comment1 = "Skipped equivalent strings, ";
|
|
continue;
|
|
}
|
|
}
|
|
if (qs->ignore_spaces) {
|
|
/* UTF-8 issue for combining code points? */
|
|
if (eb_skip_spaces(s1->b, s1->offset, &s1->offset) |
|
|
eb_skip_spaces(s2->b, s2->offset, &s2->offset))
|
|
{
|
|
comment1 = "Skipped spaces, ";
|
|
continue;
|
|
}
|
|
}
|
|
if (qs->ignore_comments) {
|
|
if (qe_skip_style(s1, s1->offset, &s1->offset, QE_STYLE_COMMENT) |
|
|
qe_skip_style(s2, s2->offset, &s2->offset, QE_STYLE_COMMENT))
|
|
{
|
|
comment2 = "Skipped comments, ";
|
|
continue;
|
|
}
|
|
}
|
|
if (qs->ignore_preproc) {
|
|
if (qe_skip_style(s1, s1->offset, &s1->offset, QE_STYLE_PREPROCESS) |
|
|
qe_skip_style(s2, s2->offset, &s2->offset, QE_STYLE_PREPROCESS))
|
|
{
|
|
comment2 = "Skipped preproc, ";
|
|
continue;
|
|
}
|
|
}
|
|
if (s1->offset >= size1 || s2->offset >= size2) {
|
|
put_status(s, "%s%s%sExtra characters",
|
|
comment1, comment2, comment3);
|
|
break;
|
|
}
|
|
if (resync) {
|
|
int save1 = s1->offset, save2 = s2->offset;
|
|
compare_resync(s1, s2, save1, save2, &s1->offset, &s2->offset);
|
|
} else {
|
|
put_status(s, "%s%s%sDifference: '%s' [0x%02X] <-> '%s' [0x%02X]",
|
|
comment1, comment2, comment3,
|
|
utf8_char32_to_string(buf1, ch1), ch1,
|
|
utf8_char32_to_string(buf2, ch2), ch2);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void do_compare_files(EditState *s, const char *filename, int bflags)
|
|
{
|
|
char buf[MAX_FILENAME_SIZE + 3];
|
|
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_error(s, "Reference file is in root directory: %s", filename);
|
|
return;
|
|
} else
|
|
if (pathlen >= MAX_FILENAME_SIZE) {
|
|
put_error(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_error(s, "Cannot access file %s: %s", filename, strerror(errno));
|
|
return;
|
|
}
|
|
if (access(buf, R_OK)) {
|
|
put_error(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->qs->active_window = e;
|
|
do_find_file(e, buf, bflags);
|
|
}
|
|
}
|
|
|
|
#define QE_AW_ENLARGE 0
|
|
#define QE_AW_SHRINK 1
|
|
#define QE_AW_VERTICAL 0
|
|
#define QE_AW_HORIZONTAL 2
|
|
|
|
static void do_adjust_window(EditState *s, int argval, int flags)
|
|
{
|
|
QEmacsState *qs = s->qs;
|
|
EditState *e;
|
|
int delta, x = -1, y = -1;
|
|
|
|
if (s->flags & WF_MINIBUF)
|
|
return;
|
|
if (argval < 0) {
|
|
flags ^= QE_AW_SHRINK;
|
|
argval = -argval;
|
|
}
|
|
if (flags & QE_AW_SHRINK)
|
|
argval = -argval;
|
|
|
|
if (!qs->first_transient_key) {
|
|
put_status(s, "Adjusting window, repeat with ^, }, {, v");
|
|
qe_register_transient_binding(qs, "enlarge-window", "^, up");
|
|
qe_register_transient_binding(qs, "shrink-window", "v, V, down");
|
|
qe_register_transient_binding(qs, "enlarge-window-horizontally", "}, right");
|
|
qe_register_transient_binding(qs, "shrink-window-horizontally", "{, left");
|
|
}
|
|
|
|
if (flags & QE_AW_HORIZONTAL) {
|
|
delta = s->char_width * argval;
|
|
if ((delta > 0 && s->x1 > delta)
|
|
|| (delta < 0 && s->x1 > 0 && s->x2 - s->x1 > -delta)) {
|
|
x = s->x1; /* adjust s->x1 */
|
|
delta = -delta;
|
|
} else
|
|
if ((delta > 0 && s->x2 + delta < qs->width)
|
|
|| (delta < 0 && s->x2 < qs->width && s->x2 - s->x1 > -delta)) {
|
|
x = s->x2; /* adjust s->x2 */
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
delta = s->line_height * argval;
|
|
if ((delta > 0 && s->y1 > delta)
|
|
|| (delta < 0 && s->y1 > 0 && s->y2 - s->y1 > -delta)) {
|
|
y = s->y1; /* adjust s->y1 */
|
|
delta = -delta;
|
|
} else
|
|
if ((delta > 0 && s->y2 + delta < qs->height - qs->status_height)
|
|
|| (delta < 0 && s->y2 < qs->height - qs->status_height && s->y2 - s->y1 > -delta)) {
|
|
y = s->y2; /* adjust s->y2 */
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (s == e || !(e->flags & (WF_POPUP | WF_MINIBUF))) {
|
|
if (e->x1 == x)
|
|
e->x1 += delta;
|
|
else if (e->x2 == x)
|
|
e->x2 += delta;
|
|
else if (e->y1 == y)
|
|
e->y1 += delta;
|
|
else if (e->y2 == y)
|
|
e->y2 += delta;
|
|
else
|
|
continue;
|
|
compute_virtual_window_size(e);
|
|
}
|
|
}
|
|
qs->complete_refresh = 1;
|
|
do_refresh(s);
|
|
}
|
|
|
|
static void do_resize_window(EditState *s) {
|
|
/* prevent resizing minibuffer */
|
|
if (s->flags & WF_MINIBUF)
|
|
return;
|
|
|
|
put_status(s, "Resize window interactively with {left}, {right}, {up}, {down}");
|
|
do_adjust_window(s, 0, 0);
|
|
}
|
|
|
|
enum {
|
|
DH_POINT = 0, /* delete blanks around point */
|
|
DH_BOL = 1, /* delete blanks at beginning of line */
|
|
DH_EOL = 2, /* delete blanks at end of line */
|
|
DH_FULL = 3, /* delete blanks at end of line on the full buffer */
|
|
};
|
|
|
|
void do_delete_horizontal_space(EditState *s, int mode)
|
|
{
|
|
int from, to, stop, offset;
|
|
EditBuffer *b = s->b;
|
|
|
|
stop = from = s->offset;
|
|
if (s->region_style) {
|
|
if (mode == DH_FULL)
|
|
mode = DH_EOL;
|
|
from = min_int(b->mark, s->offset);
|
|
stop = max_int(b->mark, s->offset);
|
|
s->region_style = 0;
|
|
} else
|
|
if (mode == DH_FULL) {
|
|
from = 0;
|
|
stop = b->total_size;
|
|
}
|
|
|
|
if (mode == DH_BOL)
|
|
from = eb_goto_bol(b, from);
|
|
|
|
for (;;) {
|
|
if (mode & DH_EOL)
|
|
from = eb_goto_eol(b, from);
|
|
|
|
to = from;
|
|
while (qe_isblank(eb_prevc(b, from, &offset)))
|
|
from = offset;
|
|
|
|
while (qe_isblank(eb_nextc(b, to, &offset)))
|
|
to = offset;
|
|
|
|
if (stop < to)
|
|
stop = to;
|
|
stop -= eb_delete_range(b, from, to);
|
|
from = eb_next_line(b, from);
|
|
if (from >= stop)
|
|
break;
|
|
}
|
|
if (mode == DH_FULL) {
|
|
/* delete trailing newlines */
|
|
// XXX: make this a separate function
|
|
to = b->total_size;
|
|
if (to > 0 && eb_prevc(b, to, &to) == '\n') {
|
|
from = to;
|
|
while (from > 0 && eb_prevc(b, from, &offset) == '\n')
|
|
from = offset;
|
|
if (from < to)
|
|
eb_delete_range(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.
|
|
*/
|
|
int p0, p1, p2, p3;
|
|
EditBuffer *b = s->b;
|
|
|
|
p0 = p1 = eb_goto_bol(b, s->offset);
|
|
if (eb_is_blank_line(b, p1, &p2)) {
|
|
while (p0 > 0) {
|
|
int offset0 = eb_prev_line(b, p0);
|
|
if (!eb_is_blank_line(b, offset0, NULL))
|
|
break;
|
|
p0 = offset0;
|
|
}
|
|
} else {
|
|
p0 = p1 = p2 = eb_next_line(b, s->offset);
|
|
}
|
|
|
|
p3 = p2;
|
|
while (p3 < s->b->total_size) {
|
|
if (!eb_is_blank_line(b, p3, &p3))
|
|
break;
|
|
}
|
|
if (p0 < p1 || p2 < p3) {
|
|
/* delete the second block first so p0 and p1 stay correct */
|
|
eb_delete_range(b, p2, p3);
|
|
eb_delete_range(b, p0, p1);
|
|
} else {
|
|
eb_delete_range(b, p1, p2);
|
|
}
|
|
}
|
|
|
|
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_offset(0, min_offset(p1, p2));
|
|
int stop = min_offset(b->total_size, max_offset(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) {
|
|
char32_t c = eb_nextc(b, offset, &offset1);
|
|
if (c == '\r' || c == '\n') {
|
|
col = 0;
|
|
continue;
|
|
}
|
|
if (c == '\t') {
|
|
col += tw - col % tw;
|
|
continue;
|
|
}
|
|
col += qe_wcwidth(c);
|
|
if (c != ' ' || offset < start || col % tw == 0)
|
|
continue;
|
|
while (offset1 < stop) {
|
|
c = eb_nextc(b, offset1, &offset2);
|
|
if (c == ' ') {
|
|
col += 1;
|
|
offset1 = offset2;
|
|
if (col % tw == 0) {
|
|
delta = -eb_delete_range(b, offset, offset1);
|
|
delta += eb_insert_char32(b, offset, '\t');
|
|
offset1 += delta;
|
|
stop += delta;
|
|
break;
|
|
}
|
|
continue;
|
|
} else
|
|
if (c == '\t') {
|
|
col += tw - col % tw;
|
|
delta = -eb_delete_range(b, offset, offset1);
|
|
offset1 = offset2 + 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_offset(0, min_offset(p1, p2));
|
|
int stop = min_offset(b->total_size, max_offset(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) {
|
|
char32_t c = eb_nextc(b, offset, &offset1);
|
|
if (c == '\r' || c == '\n') {
|
|
col = 0;
|
|
continue;
|
|
}
|
|
if (c != '\t') {
|
|
col += qe_wcwidth(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);
|
|
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
|
|
|
|
/*---------------- indentation ----------------*/
|
|
|
|
static void do_indent_rigidly(EditState *s, int start, int end, int argval)
|
|
{
|
|
if (argval == NO_ARG) {
|
|
/* enter interactive mode */
|
|
QEmacsState *qs = s->qs;
|
|
if (!qs->first_transient_key) {
|
|
s->region_style = QE_STYLE_REGION_HILITE;
|
|
put_status(s, "Indent the region interactively with TAB, left, right, S-left, S-right");
|
|
qe_register_transient_binding(qs, "indent-rigidly-left", "left");
|
|
qe_register_transient_binding(qs, "indent-rigidly-right", "right");
|
|
qe_register_transient_binding(qs, "indent-rigidly-left-to-tab-stop", "S-left, S-TAB");
|
|
qe_register_transient_binding(qs, "indent-rigidly-right-to-tab-stop", "S-right, TAB");
|
|
}
|
|
} else {
|
|
do_indent_rigidly_by(s, start, end, argval);
|
|
}
|
|
}
|
|
|
|
static void do_indent_region(EditState *s, int start, int end, int argval)
|
|
{
|
|
int col_num, line, line1, line2;
|
|
|
|
/* deactivate region hilite */
|
|
s->region_style = 0;
|
|
|
|
if (argval < 0 || !s->mode->indent_func
|
|
|| s->qs->last_cmd_func == (CmdFunc)do_indent_region) {
|
|
do_indent_rigidly_to_tab_stop(s, start, end, argval);
|
|
return;
|
|
}
|
|
|
|
/* Swap point and mark so mark <= point */
|
|
if (end < start) {
|
|
int tmp = start;
|
|
start = end;
|
|
end = tmp;
|
|
}
|
|
/* We do it with lines to avoid offset variations during indenting */
|
|
eb_get_pos(s->b, &line1, &col_num, start);
|
|
if (start == end) {
|
|
line2 = line1;
|
|
} else {
|
|
if (col_num > 0)
|
|
line1++;
|
|
eb_get_pos(s->b, &line2, &col_num, end);
|
|
if (col_num == 0)
|
|
line2--;
|
|
}
|
|
/* Iterate over all lines inside block */
|
|
for (line = line1; line <= line2; line++) {
|
|
int offset = eb_goto_pos(s->b, line, 0);
|
|
if (eb_peekc(s->b, offset) != '\n')
|
|
(s->mode->indent_func)(s, offset);
|
|
}
|
|
}
|
|
|
|
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 char32_t matching_delimiter(char32_t c) {
|
|
const u8 *pairs = (const u8 *)"(){}[]<>";
|
|
int i;
|
|
|
|
for (i = 0; pairs[i]; i++) {
|
|
if (pairs[i] == c)
|
|
return pairs[i ^ 1];
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static int style_normalize(int style) {
|
|
// XXX: if style is absolute color, try and match comment and string color
|
|
switch (style) {
|
|
case QE_STYLE_COMMENT:
|
|
return QE_STYLE_COMMENT;
|
|
case QE_STYLE_STRING:
|
|
case QE_STYLE_STRING_Q:
|
|
return QE_STYLE_STRING;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Is the character at `offset` preceeded by a backslash that is not itself
|
|
// escaped: count the number of backslashes, must be odd.
|
|
// this handles strings such as "\\\"" but would not be sufficient to handle
|
|
// pathological cases of escaped newlines in C and C++. If colorization is
|
|
// active, these cases are handled transparently.
|
|
static int is_escaped_backward(EditBuffer *b, int offset) {
|
|
int count = 0;
|
|
while (eb_prevc(b, offset, &offset) == '\\')
|
|
count++;
|
|
return count & 1;
|
|
}
|
|
|
|
static void forward_block(EditState *s, int dir, int stop_at_parent)
|
|
{
|
|
// We skip characters, balancing blocks delimited by (){}[]
|
|
// * we use the colorizer to determine the token type.
|
|
// * If starting in a string or in a comment, we stop at the end of the
|
|
// string or comment otherwise, we skip comments and strings.
|
|
// * we prioritize string skipping when starting at a quote.
|
|
// * block delimiters are balanced inside the region and skipping stops
|
|
// when a closing delimiter is found.
|
|
// * we prioritize bracket skipping inside comment/string when starting
|
|
// at a bracket.
|
|
// if a nesting error occurs, point and mark are set around the mismatched
|
|
// delimiters. use C-x C-x to skip between them
|
|
|
|
QEColorizeContext cp[1];
|
|
char32_t balance[MAX_LEVEL];
|
|
int delim_offset[MAX_LEVEL];
|
|
int line_num, col_num;
|
|
int use_colors; /* set if using styles from colorizer */
|
|
int level; /* block nesting level */
|
|
int style; /* style of current character */
|
|
int style0; /* style of the starting point */
|
|
int pos; /* index of the current character on line */
|
|
int len; /* number of colorized positions */
|
|
int offset; /* offset of the current character */
|
|
int bol_offset; /* offset of the beginning of line */
|
|
int pending_offset; /* end of string or comment if skipping whitespace */
|
|
int dummy_offset;
|
|
char32_t c;
|
|
|
|
cp_initialize(cp, s);
|
|
|
|
offset = s->offset;
|
|
eb_get_pos(s->b, &line_num, &col_num, offset);
|
|
bol_offset = eb_goto_bol2(s->b, offset, &pos);
|
|
use_colors = s->colorize_mode || s->b->b_styles;
|
|
pending_offset = -1;
|
|
style0 = 0;
|
|
len = 0;
|
|
if (use_colors) {
|
|
len = get_colorized_line(cp, bol_offset, &dummy_offset, line_num);
|
|
if (cp->truncated) {
|
|
/* very long line detected, do not use styles */
|
|
use_colors = 0;
|
|
len = 0;
|
|
} else {
|
|
if (dir < 0) {
|
|
if (pos <= len)
|
|
style0 = style_normalize(cp->sbuf[pos - (pos > 0)]);
|
|
} else {
|
|
if (pos < len) {
|
|
style0 = style_normalize(cp->sbuf[pos]);
|
|
} else {
|
|
// XXX if point is at end of line, should check if
|
|
// inside a block of comments or strings
|
|
}
|
|
}
|
|
}
|
|
}
|
|
level = 0;
|
|
|
|
if (dir < 0) {
|
|
char32_t c0; /* character before cursor */
|
|
int is_delim; /* true if c0 is a bracket delimiter */
|
|
c0 = eb_prevc(s->b, offset, &dummy_offset);
|
|
is_delim = c0 != matching_delimiter(c0);
|
|
for (;;) {
|
|
int this_offset = offset;
|
|
c = eb_prevc(s->b, offset, &offset);
|
|
if (c == '\n') {
|
|
if (offset <= 0)
|
|
break;
|
|
line_num--;
|
|
bol_offset = eb_goto_bol2(s->b, offset, &pos);
|
|
len = 0;
|
|
if (use_colors) {
|
|
len = get_colorized_line(cp, bol_offset, &dummy_offset, line_num);
|
|
if (cp->truncated) {
|
|
/* very long line detected, use fallback version */
|
|
use_colors = 0;
|
|
len = 0;
|
|
style0 = 0;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
pos--;
|
|
style = 0;
|
|
if (use_colors) {
|
|
if (pos >= 0 && pos < len) {
|
|
style = style_normalize(cp->sbuf[pos]);
|
|
}
|
|
if (style != style0) {
|
|
if (style0 == 0) {
|
|
// encountered string or comment during regular
|
|
// scanning: skip it
|
|
continue;
|
|
}
|
|
if (style0 == QE_STYLE_COMMENT && !style) {
|
|
// skip whitespace and continue if inside a block of comments
|
|
if (qe_isspace(c))
|
|
continue;
|
|
}
|
|
// stop at string or comment boundary
|
|
break;
|
|
}
|
|
}
|
|
pending_offset = offset;
|
|
switch (c) {
|
|
case '\'':
|
|
// Single quotes can occur as digit separators in C++, this
|
|
// convention is inconsistent and nefarious to many respects
|
|
// and including it in the C Standard as of C2y is a shame.
|
|
if (!style
|
|
&& qe_isdigit(eb_peekc(s->b, this_offset))
|
|
&& qe_isdigit(eb_peek_prevc(s->b, offset)))
|
|
break;
|
|
FALLTHROUGH;
|
|
case '\"':
|
|
// We get here if scan started inside a string or a comment
|
|
// or if styles are disabled: try and skip the string backward
|
|
// using a simplistic algorithm: skip escaped quotes and stop
|
|
// at the matching quote or upon style change.
|
|
if ((!style && pos > 0) || c == c0) {
|
|
int off;
|
|
char32_t c1;
|
|
while ((c1 = eb_prevc(s->b, offset, &off)) != '\n') {
|
|
if (use_colors && pos > 0 && pos <= len) {
|
|
// stop at comment or regex boundary
|
|
int style1 = style_normalize(cp->sbuf[pos - 1]);
|
|
if (style1 != style) {
|
|
s->offset = offset;
|
|
goto done;
|
|
}
|
|
}
|
|
offset = off;
|
|
pos--;
|
|
if (c1 == c && !is_escaped_backward(s->b, offset))
|
|
break;
|
|
}
|
|
if (c == c0) {
|
|
s->offset = offset;
|
|
goto done;
|
|
}
|
|
}
|
|
break;
|
|
case ')':
|
|
case ']':
|
|
case '}':
|
|
if (level < MAX_LEVEL) {
|
|
delim_offset[level] = this_offset;
|
|
balance[level] = matching_delimiter(c);
|
|
}
|
|
level++;
|
|
break;
|
|
case '(':
|
|
case '[':
|
|
case '{':
|
|
if (level > 0) {
|
|
--level;
|
|
/* continue if started inside a string or a comment unless
|
|
starting exactly at a delimiter */
|
|
// XXX: should refine this: should balance blocks
|
|
// if scan started *inside* a comment or string
|
|
// or at a closing block delimiter.
|
|
if (style0 && !is_delim)
|
|
break;
|
|
if (level < MAX_LEVEL && balance[level] != c) {
|
|
/* XXX: should set mark and offset */
|
|
s->b->mark = delim_offset[level];
|
|
s->offset = offset;
|
|
put_error(s, "Unmatched delimiter %c <> %c",
|
|
(int)c, (int)balance[level]);
|
|
goto done;
|
|
}
|
|
if (level == 0) {
|
|
s->offset = offset;
|
|
goto done;
|
|
}
|
|
} else {
|
|
if (stop_at_parent) {
|
|
s->offset = offset;
|
|
goto done;
|
|
}
|
|
/* silently move up one level */
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
char32_t c0; /* character after cursor */
|
|
int is_delim; /* true if c0 is a bracket delimiter */
|
|
c0 = eb_nextc(s->b, offset, &dummy_offset);
|
|
is_delim = c0 != matching_delimiter(c0);
|
|
for (;;) {
|
|
int this_offset = offset;
|
|
c = eb_nextc(s->b, offset, &offset);
|
|
if (c == '\n') {
|
|
if (offset >= s->b->total_size)
|
|
break;
|
|
line_num++;
|
|
bol_offset = offset;
|
|
pos = 0;
|
|
len = 0;
|
|
if (use_colors) {
|
|
len = get_colorized_line(cp, bol_offset, &dummy_offset, line_num);
|
|
if (cp->truncated) {
|
|
/* very long line detected, use fallback version */
|
|
use_colors = 0;
|
|
len = 0;
|
|
style0 = 0;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
pos++;
|
|
style = 0;
|
|
if (use_colors) {
|
|
if (pos > 0 && pos <= len) {
|
|
style = style_normalize(cp->sbuf[pos - 1]);
|
|
}
|
|
if (style != style0) {
|
|
if (style0 == 0) {
|
|
// skip string or comment
|
|
continue;
|
|
}
|
|
if (style0 == QE_STYLE_COMMENT && !style) {
|
|
// skip whitespace and continue if inside a block of comments
|
|
if (qe_isspace(c))
|
|
continue;
|
|
}
|
|
// stop at string or comment boundary
|
|
break;
|
|
}
|
|
}
|
|
pending_offset = offset;
|
|
switch (c) {
|
|
case '\'':
|
|
// Single quotes can occur as digit separators in C++, this
|
|
// convention is inconsistent and nefarious to many respects
|
|
// and including it in the C Standard as of C2y is a shame.
|
|
if (!style
|
|
&& qe_isdigit(eb_peekc(s->b, offset))
|
|
&& qe_isdigit(eb_peek_prevc(s->b, this_offset)))
|
|
break;
|
|
FALLTHROUGH;
|
|
case '\"':
|
|
// We get here if scan started inside a string or a comment
|
|
// or if styles are disabled: try and skip the string
|
|
// using a simplistic algorithm: skip escaped quotes and stop
|
|
// at the matching quote.
|
|
if ((!style && pos >= len) || c == c0) {
|
|
int off;
|
|
char32_t c1;
|
|
while ((c1 = eb_nextc(s->b, offset, &off)) != '\n') {
|
|
if (use_colors && pos >= 0 && pos < len) {
|
|
// stop at comment or regex boundary
|
|
int style1 = style_normalize(cp->sbuf[pos]);
|
|
if (style1 != style) {
|
|
s->offset = offset;
|
|
goto done;
|
|
}
|
|
}
|
|
offset = off;
|
|
pos++;
|
|
if (c1 == c)
|
|
break;
|
|
if (c1 == '\\') {
|
|
if (eb_nextc(s->b, offset, &off) == '\n')
|
|
break;
|
|
offset = off;
|
|
pos++;
|
|
}
|
|
}
|
|
if (c1 == c && c == c0) {
|
|
s->offset = offset;
|
|
goto done;
|
|
}
|
|
}
|
|
break;
|
|
case '(':
|
|
case '[':
|
|
case '{':
|
|
if (level < MAX_LEVEL) {
|
|
delim_offset[level] = this_offset;
|
|
balance[level] = matching_delimiter(c);
|
|
}
|
|
level++;
|
|
break;
|
|
case ')':
|
|
case ']':
|
|
case '}':
|
|
if (level > 0) {
|
|
--level;
|
|
/* continue if started inside a string or a comment unless
|
|
starting exactly at a delimiter */
|
|
// XXX: should refine this: should balance blocks
|
|
// if scan started *inside* a comment or string
|
|
// or at an opening block delimiter.
|
|
if (style0 && !is_delim)
|
|
break;
|
|
if (level < MAX_LEVEL && balance[level] != c) {
|
|
/* XXX: should set mark and offset */
|
|
s->b->mark = delim_offset[level];
|
|
s->offset = offset;
|
|
put_error(s, "Unmatched delimiter %c <> %c",
|
|
(int)c, (int)balance[level]);
|
|
goto done;
|
|
}
|
|
if (level == 0) {
|
|
s->offset = offset;
|
|
goto done;
|
|
}
|
|
} else {
|
|
if (stop_at_parent) {
|
|
s->offset = offset;
|
|
goto done;
|
|
}
|
|
/* silently move up one level */
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (level != 0) {
|
|
s->b->mark = delim_offset[level - 1];
|
|
s->offset = offset;
|
|
put_error(s, "Unmatched delimiter");
|
|
} else {
|
|
s->offset = offset;
|
|
if (pending_offset >= 0)
|
|
s->offset = pending_offset;
|
|
}
|
|
done:
|
|
cp_destroy(cp);
|
|
}
|
|
|
|
static void do_forward_block(EditState *s, int n)
|
|
{
|
|
int dir = n < 0 ? -1 : 1;
|
|
|
|
for (; n != 0; n -= dir) {
|
|
forward_block(s, dir, 1);
|
|
}
|
|
}
|
|
|
|
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->qs;
|
|
int offset0, offset1, offset2, offset3;
|
|
int start_offset, end_offset;
|
|
int size0, size1, size2;
|
|
EditBuffer *b = s->b;
|
|
|
|
if (check_read_only(s))
|
|
return;
|
|
|
|
// FIXME: handle repeat count
|
|
|
|
/* 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);
|
|
offset3 = word_right(s, 0);
|
|
offset2 = word_left(s, 0);
|
|
offset1 = word_left(s, 1);
|
|
offset0 = word_left(s, 0);
|
|
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;
|
|
case CMD_TRANSPOSE_PARAGRAPHS:
|
|
start_offset = s->offset;
|
|
if (!eb_is_blank_line(b, start_offset, NULL))
|
|
start_offset = eb_next_paragraph(b, start_offset);
|
|
offset2 = eb_skip_blank_lines(b, start_offset, 1);
|
|
offset3 = eb_next_paragraph(b, offset2);
|
|
offset1 = eb_skip_blank_lines(b, start_offset, -1);
|
|
offset0 = eb_prev_paragraph(b, offset1);
|
|
if (qs->emulation_flags == 1) {
|
|
/* set position to end of first paragraph */
|
|
end_offset = offset0 + offset3 - offset2;
|
|
} else {
|
|
/* set position past last paragraph (emacs behaviour) */
|
|
end_offset = offset3;
|
|
}
|
|
break;
|
|
case CMD_TRANSPOSE_SENTENCES:
|
|
offset0 = eb_prev_sentence(b, s->offset);
|
|
offset1 = eb_next_sentence(b, offset0);
|
|
offset3 = eb_next_sentence(b, offset1);
|
|
offset2 = eb_prev_sentence(b, offset3);
|
|
if (qs->emulation_flags == 1) {
|
|
/* set position to end of first paragraph */
|
|
end_offset = offset0 + offset3 - offset2;
|
|
} else {
|
|
/* set position past last paragraph (emacs behaviour) */
|
|
end_offset = offset3;
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
size0 = offset1 - offset0;
|
|
size1 = offset2 - offset1;
|
|
size2 = offset3 - offset2;
|
|
|
|
// XXX: should have a way to move buffer contents
|
|
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 = qe_new_buffer(qs, "*tmp*", BF_SYSTEM | (b->flags & BF_STYLES));
|
|
if (!b1)
|
|
return;
|
|
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;
|
|
}
|
|
|
|
/*---------------- help ----------------*/
|
|
|
|
int qe_list_bindings(QEmacsState *qs, const CmdDef *d, ModeDef *mode, int inherit, char *buf, int size)
|
|
{
|
|
buf_t outbuf, *out;
|
|
ModeDef *mode0 = mode;
|
|
|
|
out = buf_init(&outbuf, buf, size);
|
|
for (;;) {
|
|
KeyDef *kd = mode ? mode->first_key : qs->first_key;
|
|
|
|
// XXX: method runs in quadratic time, which may be slow
|
|
for (; kd != NULL; kd = kd->next) {
|
|
/* do not list overridden bindings */
|
|
if (kd->cmd == d
|
|
&& qe_find_current_binding(qs, kd->keys, kd->nb_keys, mode0, -1) == kd) {
|
|
if (out->len > 0)
|
|
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];
|
|
QEmacsState *qs = s->qs;
|
|
const CmdDef *d;
|
|
|
|
if ((d = qe_find_cmd(qs, cmd_name)) == NULL) {
|
|
put_error(s, "No command %s", cmd_name);
|
|
return;
|
|
}
|
|
if (qe_list_bindings(qs, 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);
|
|
}
|
|
}
|
|
|
|
static void do_describe_function(EditState *s, const char *cmd_name) {
|
|
EditBuffer *b;
|
|
const CmdDef *d;
|
|
const char *desc;
|
|
|
|
if ((d = qe_find_cmd(s->qs, cmd_name)) == NULL) {
|
|
put_error(s, "No command %s", cmd_name);
|
|
return;
|
|
}
|
|
b = new_help_buffer(s);
|
|
if (!b)
|
|
return;
|
|
|
|
eb_putc(b, '\n');
|
|
desc = d->spec + strlen(d->spec) + 1;
|
|
/* print name, prototype, bindings and description */
|
|
eb_command_print_entry(b, d, s);
|
|
eb_putc(b, '\n');
|
|
if (*desc) {
|
|
/* print short description */
|
|
eb_printf(b, " %s\n", desc);
|
|
}
|
|
// XXX: should look up markdown documentation
|
|
show_popup(s, b, "Help");
|
|
}
|
|
|
|
// Sort Flags: do not use 1 for argval compatibility
|
|
#define SF_FOLD 0x02
|
|
#define SF_REVERSE 0x04
|
|
#define SF_DICT 0x08
|
|
#define SF_NUMBER 0x10
|
|
#define SF_COLUMN 0x20
|
|
#define SF_BASENAME 0x40
|
|
#define SF_PARAGRAPH 0x80
|
|
#define SF_SILENT 0x100
|
|
static int eb_sort_span(EditBuffer *b, int *pp1, int *pp2, int cur_offset, int flags);
|
|
|
|
static void print_bindings(EditBuffer *b, ModeDef *mode)
|
|
{
|
|
struct QEmacsState *qs = b->qs;
|
|
char buf[256];
|
|
const CmdDef *d;
|
|
int gfound, start, stop, i, j;
|
|
|
|
start = 0;
|
|
gfound = 0;
|
|
b->tab_width = 24;
|
|
for (i = 0; i < qs->cmd_array_count; i++) {
|
|
for (j = qs->cmd_array[i].count, d = qs->cmd_array[i].array; j-- > 0; d++) {
|
|
if (qe_list_bindings(qs, d, mode, 0, buf, sizeof(buf))) {
|
|
if (!gfound) {
|
|
if (mode) {
|
|
eb_print_style(b, DESCRIBE_STYLE_HEAD,
|
|
"\n%s mode bindings:\n\n", mode->name);
|
|
} else {
|
|
eb_print_style(b, DESCRIBE_STYLE_HEAD,
|
|
"\nGlobal bindings:\n\n");
|
|
}
|
|
start = b->offset;
|
|
gfound = 1;
|
|
}
|
|
eb_print_field(b, d->name, "%s\n", buf);
|
|
}
|
|
}
|
|
}
|
|
if (gfound) {
|
|
stop = b->offset;
|
|
eb_sort_span(b, &start, &stop, stop, SF_DICT | SF_NUMBER | SF_SILENT);
|
|
}
|
|
}
|
|
|
|
void do_describe_bindings(EditState *s, int argval)
|
|
{
|
|
EditBuffer *b;
|
|
ModeDef *m;
|
|
|
|
b = new_help_buffer(s);
|
|
if (!b)
|
|
return;
|
|
|
|
for (m = s->qs->first_mode; m; m = m->next) {
|
|
// XXX: windows/buffers should handle multiple modes
|
|
if (argval != 1 || m == s->mode)
|
|
print_bindings(b, m);
|
|
}
|
|
print_bindings(b, NULL);
|
|
|
|
show_popup(s, b, "Bindings");
|
|
}
|
|
|
|
// List all active bindings for a given mode
|
|
static void mode_wall_chart(EditBuffer *b, ModeDef *mode, ModeDef *mode0, const char *prefix, int separate) {
|
|
buf_t out[1];
|
|
char buf[32];
|
|
int head, start, stop;
|
|
KeyDef *kd = mode ? mode->first_key : b->qs->first_key;
|
|
|
|
if (!kd)
|
|
return;
|
|
|
|
head = b->offset;
|
|
if (separate) {
|
|
eb_print_style(b, DESCRIBE_STYLE_HEAD, "\n%s bindings:\n\n", mode ? mode->name : "Global");
|
|
}
|
|
start = b->offset;
|
|
for (; kd != NULL; kd = kd->next) {
|
|
QETermStyle style = QE_STYLE_STRING;
|
|
const char *p;
|
|
// Flag redefined bindings unless we construct separate lists
|
|
if (!separate) {
|
|
if (qe_find_current_binding(b->qs, kd->keys, kd->nb_keys, mode0, -1) != kd)
|
|
style = QE_STYLE_COMMENT;
|
|
}
|
|
buf_init(out, buf, countof(buf));
|
|
buf_put_keys(out, kd->keys, kd->nb_keys);
|
|
if (*prefix && (!strstart(buf, prefix, &p) || *p != ' '))
|
|
continue;
|
|
eb_print_style(b, style, " %s", buf);
|
|
eb_putc(b, '\t');
|
|
eb_print_style(b, QE_STYLE_FUNCTION, "%s\n", kd->cmd->name);
|
|
}
|
|
if (separate) {
|
|
stop = b->offset;
|
|
if (start == stop)
|
|
eb_delete_range(b, head, start);
|
|
else
|
|
eb_sort_span(b, &start, &stop, stop, SF_NUMBER | SF_SILENT);
|
|
}
|
|
}
|
|
|
|
static void wall_chart_prefix(EditState *s, const char *prefix, int argval)
|
|
{
|
|
ModeDef *mode;
|
|
EditBuffer *b;
|
|
int start, stop;
|
|
|
|
b = new_help_buffer(s);
|
|
if (!b)
|
|
return;
|
|
|
|
b->tab_width = 16;
|
|
if (argval != 1) {
|
|
// make a complete wall chart with all modes
|
|
mode_wall_chart(b, NULL, NULL, prefix, TRUE);
|
|
for (mode = s->qs->first_mode; mode; mode = mode->next)
|
|
mode_wall_chart(b, mode, NULL, prefix, TRUE);
|
|
} else {
|
|
start = 0;
|
|
for (mode = s->mode; mode; mode = mode->fallback)
|
|
mode_wall_chart(b, mode, s->mode, prefix, FALSE);
|
|
mode_wall_chart(b, NULL, s->mode, prefix, FALSE);
|
|
stop = b->offset;
|
|
eb_sort_span(b, &start, &stop, stop, SF_NUMBER | SF_SILENT);
|
|
}
|
|
show_popup(s, b, "Wall chart");
|
|
put_status(s, "prefix: {%s}", prefix);
|
|
}
|
|
|
|
static void do_wall_chart(EditState *s, int argval)
|
|
{
|
|
wall_chart_prefix(s, "", argval);
|
|
}
|
|
|
|
static void do_describe_prefix_bindings(EditState *s, const char *prefix)
|
|
{
|
|
wall_chart_prefix(s, prefix, 1);
|
|
}
|
|
|
|
void do_apropos(EditState *s, const char *str)
|
|
{
|
|
QEmacsState *qs = s->qs;
|
|
char buf[256];
|
|
EditBuffer *b;
|
|
const CmdDef *d;
|
|
VarDef *vp;
|
|
int start, stop, i, j, extra;
|
|
|
|
b = new_help_buffer(s);
|
|
if (!b)
|
|
return;
|
|
|
|
eb_putc(b, '\n');
|
|
|
|
stop = 1;
|
|
for (extra = 0; extra < 2; extra++) {
|
|
start = b->offset;
|
|
for (i = 0; i < qs->cmd_array_count; i++) {
|
|
for (j = qs->cmd_array[i].count, d = qs->cmd_array[i].array; j-- > 0; d++) {
|
|
const char *desc = d->spec + strlen(d->spec) + 1;
|
|
if ((!strstr(d->name, str)) == extra && (!extra || strstr(desc, str))) {
|
|
/* print name, prototype, bindings */
|
|
eb_command_print_entry(b, d, s);
|
|
eb_putc(b, '\n');
|
|
if (*desc) {
|
|
/* print short description */
|
|
eb_printf(b, " %s\n", desc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
stop = b->offset;
|
|
if (start < stop) {
|
|
eb_sort_span(b, &start, &stop, stop, SF_DICT | SF_NUMBER | SF_PARAGRAPH | SF_SILENT);
|
|
eb_putc(b, '\n');
|
|
}
|
|
|
|
start = b->offset;
|
|
for (vp = qs->first_variable; vp; vp = vp->next) {
|
|
const char *desc = vp->desc ? vp->desc : "";
|
|
if ((!strstr(vp->name, str)) == extra && (!extra || strstr(desc, str))) {
|
|
/* print class, name, current value and short description */
|
|
eb_variable_print_entry(b, vp, s, 1);
|
|
}
|
|
}
|
|
stop = b->offset;
|
|
if (start < stop) {
|
|
eb_sort_span(b, &start, &stop, stop, SF_DICT | SF_NUMBER | SF_PARAGRAPH | SF_SILENT);
|
|
eb_putc(b, '\n');
|
|
}
|
|
}
|
|
if (stop > 1) {
|
|
snprintf(buf, sizeof buf, "Apropos '%s'", str);
|
|
show_popup(s, b, buf);
|
|
} else {
|
|
eb_free(&b);
|
|
put_error(s, "No apropos matches for `%s'", str);
|
|
}
|
|
}
|
|
|
|
#ifndef CONFIG_CYGWIN
|
|
extern char **environ;
|
|
#endif
|
|
|
|
static void do_about_qemacs(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qs;
|
|
char buf[256];
|
|
EditBuffer *b;
|
|
ModeDef *m;
|
|
const CmdDef *d;
|
|
int start, stop, i, j;
|
|
|
|
b = qe_new_buffer(qs, "*About QEmacs*", BC_CLEAR | BF_UTF8);
|
|
if (!b)
|
|
return;
|
|
|
|
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->offset;
|
|
for (i = 0; i < qs->cmd_array_count; i++) {
|
|
for (j = qs->cmd_array[i].count, d = qs->cmd_array[i].array; j-- > 0; d++) {
|
|
qe_get_prototype(d, buf, sizeof(buf));
|
|
eb_printf(b, " %s%s\n", d->name, buf);
|
|
}
|
|
}
|
|
stop = b->offset;
|
|
eb_sort_span(b, &start, &stop, stop, SF_DICT | SF_NUMBER | SF_SILENT);
|
|
|
|
qe_list_variables(s, b);
|
|
|
|
/* list environment */
|
|
{
|
|
char **envp;
|
|
|
|
eb_printf(b, "\nEnvironment:\n\n");
|
|
start = b->offset;
|
|
for (envp = environ; *envp; envp++) {
|
|
eb_printf(b, " %s\n", *envp);
|
|
}
|
|
stop = b->offset;
|
|
eb_sort_span(b, &start, &stop, stop, SF_DICT | SF_NUMBER | SF_SILENT);
|
|
}
|
|
|
|
show_popup(s, b, "About QEmacs");
|
|
}
|
|
|
|
/* extract the next word from the ASCII string. ignore spaces, stop on '/' */
|
|
static int str_get_word7(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((u8)*p);
|
|
}
|
|
}
|
|
}
|
|
if (size > 0) {
|
|
if (len < size)
|
|
buf[len] = '\0';
|
|
else
|
|
buf[size - 1] = '\0';
|
|
}
|
|
|
|
while (*p == ' ')
|
|
p++;
|
|
|
|
if (pp)
|
|
*pp = p;
|
|
|
|
return len;
|
|
}
|
|
|
|
int qe_term_get_style(QETermStyle *style, const char *str)
|
|
{
|
|
char buf[128];
|
|
QEColor fg_color, bg_color;
|
|
unsigned int fg, bg, attr;
|
|
const char *p = str;
|
|
|
|
attr = 0;
|
|
fg_color = COLOR_TRANSPARENT;
|
|
bg_color = COLOR_TRANSPARENT;
|
|
while (str_get_word7(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;
|
|
}
|
|
if (!css_get_color(&fg_color, buf)) {
|
|
continue;
|
|
}
|
|
if (strfind("/|on", buf)) {
|
|
str_get_word7(buf, sizeof(buf), p, &p);
|
|
if (!css_get_color(&bg_color, buf))
|
|
continue;
|
|
}
|
|
return 1;
|
|
}
|
|
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;
|
|
}
|
|
|
|
int qe_styledef_string(char *dest, size_t size, const QEStyleDef *stp)
|
|
{
|
|
buf_t out[1];
|
|
char buf[16];
|
|
const char *p;
|
|
|
|
buf_init(out, dest, size);
|
|
if (stp->font_style & QE_FONT_STYLE_BOLD)
|
|
buf_put_word(out, "bold");
|
|
if (stp->font_style & QE_FONT_STYLE_ITALIC)
|
|
buf_put_word(out, "italic");
|
|
if (stp->font_style & QE_FONT_STYLE_UNDERLINE)
|
|
buf_put_word(out, "underlined");
|
|
if (stp->font_style & QE_FONT_STYLE_BLINK)
|
|
buf_put_word(out, "blinking");
|
|
p = css_get_color_name(buf, sizeof buf, stp->fg_color, TRUE);
|
|
buf_put_word(out, p);
|
|
if (stp->bg_color != COLOR_TRANSPARENT) {
|
|
p = css_get_color_name(buf, sizeof buf, stp->bg_color, TRUE);
|
|
buf_put_word(out, "on");
|
|
buf_put_word(out, p);
|
|
}
|
|
switch (stp->font_style) {
|
|
case QE_FONT_FAMILY_SERIF:
|
|
buf_put_word(out, "times");
|
|
break;
|
|
case QE_FONT_FAMILY_SANS:
|
|
buf_put_word(out, "arial");
|
|
break;
|
|
case QE_FONT_FAMILY_FIXED:
|
|
buf_put_word(out, "fixed");
|
|
break;
|
|
}
|
|
if (stp->font_size) {
|
|
snprintf(buf, sizeof(buf), "%dpt", stp->font_size);
|
|
buf_put_word(out, buf);
|
|
}
|
|
return out->len;
|
|
}
|
|
|
|
int qe_term_style_string(char *dest, size_t size, QETermStyle style)
|
|
{
|
|
buf_t out[1];
|
|
char buf[16];
|
|
const char *p;
|
|
|
|
buf_init(out, dest, size);
|
|
|
|
if (style & QE_TERM_COMPOSITE) {
|
|
QEColor fg_color, bg_color;
|
|
int fg, bg;
|
|
if (style & QE_TERM_BOLD)
|
|
buf_put_word(out, "bold");
|
|
if (style & QE_TERM_ITALIC)
|
|
buf_put_word(out, "italic");
|
|
if (style & QE_TERM_UNDERLINE)
|
|
buf_put_word(out, "underlined");
|
|
if (style & QE_TERM_BLINK)
|
|
buf_put_word(out, "blinking");
|
|
// FIXME: should support default foreground
|
|
fg = QE_TERM_GET_FG(style);
|
|
fg_color = qe_unmap_color(fg, QE_TERM_FG_COLORS);
|
|
p = css_get_color_name(buf, sizeof buf, fg_color, TRUE);
|
|
buf_put_word(out, p);
|
|
// FIXME: should support transparent background
|
|
bg = QE_TERM_GET_BG(style);
|
|
bg_color = qe_unmap_color(bg, QE_TERM_BG_COLORS);
|
|
p = css_get_color_name(buf, sizeof buf, bg_color, TRUE);
|
|
buf_put_word(out, "on");
|
|
buf_put_word(out, p);
|
|
} else {
|
|
if (style < QE_STYLE_NUM && qe_styles[style].name) {
|
|
buf_put_word(out, qe_styles[style].name);
|
|
} else {
|
|
buf_printf(out, "0x%x", style);
|
|
}
|
|
}
|
|
if (style & QE_STYLE_SEL) {
|
|
buf_put_word(out, "selection");
|
|
}
|
|
|
|
return out->len;
|
|
}
|
|
|
|
/* Note: we use the same syntax as CSS styles to ease merging */
|
|
static void do_set_style_color(EditState *e, const char *stylestr, const char *value)
|
|
{
|
|
QEStyleDef *stp;
|
|
char buf[32];
|
|
const char *p;
|
|
|
|
stp = find_style(stylestr);
|
|
if (!stp) {
|
|
put_error(e, "Unknown style '%s'", stylestr);
|
|
return;
|
|
}
|
|
|
|
/* accept "fgcolor", "[fgcolor]/bgcolor", "fgcolor on bgcolor" */
|
|
p = value + strcspn(value, " /");
|
|
if (p > value) {
|
|
pstrncpy(buf, sizeof buf, value, p - value);
|
|
if (css_get_color(&stp->fg_color, buf)) {
|
|
put_error(e, "Unknown fgcolor '%s'", buf);
|
|
return;
|
|
}
|
|
}
|
|
qe_skip_spaces(&p);
|
|
if (*p == '/') {
|
|
p++;
|
|
} else {
|
|
strstart(p, "on ", &p);
|
|
}
|
|
qe_skip_spaces(&p);
|
|
if (*p) {
|
|
if (css_get_color(&stp->bg_color, p)) {
|
|
put_error(e, "Unknown bgcolor '%s'", p);
|
|
return;
|
|
}
|
|
}
|
|
e->qs->complete_refresh = 1;
|
|
}
|
|
|
|
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(&style, str)) {
|
|
put_error(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, BF_STYLE_COMP);
|
|
eb_set_style(s->b, style, LOGOP_WRITE, offset, size);
|
|
}
|
|
}
|
|
|
|
static void do_read_color(EditState *s, const char *str, int argval)
|
|
{
|
|
char buf[32];
|
|
int len;
|
|
QEColor color;
|
|
|
|
if (!css_get_color(&color, str) && color != COLOR_TRANSPARENT) {
|
|
len = snprintf(buf, sizeof buf, "#%06x", color & 0xFFFFFF);
|
|
if (argval > 1) {
|
|
if (check_read_only(s))
|
|
return;
|
|
s->offset += eb_insert(s->b, s->offset, buf, len);
|
|
} else {
|
|
put_status(s, "-> %s", buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
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_error(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, BF_STYLE_COMP);
|
|
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(s);
|
|
if (!b1)
|
|
return;
|
|
|
|
eb_print_style(b1, DESCRIBE_STYLE_HEAD, "Buffer fields:\n");
|
|
|
|
b1->tab_width = 12;
|
|
eb_print_field(b1, "name", "%s\n", b->name);
|
|
eb_print_field(b1, "filename", "%s\n", b->filename);
|
|
eb_print_field(b1, "modified", "%d\n", b->modified);
|
|
eb_print_field(b1, "total_size", "%d\n", b->total_size);
|
|
eb_print_field(b1, "mark", "%d\n", b->mark);
|
|
eb_print_field(b1, "refcount", "%d\n", b->ref_count);
|
|
eb_print_field(b1, "s->offset", "%d\n", s->offset);
|
|
eb_print_field(b1, "b->offset", "%d\n", b->offset);
|
|
|
|
eb_print_field(b1, "tab_width", "%d\n", b->tab_width);
|
|
eb_print_field(b1, "fill_column", "%d\n", b->fill_column);
|
|
if (b->linum_mode_set) {
|
|
eb_print_field(b1, "linum_mode", "%d\n", b->linum_mode);
|
|
}
|
|
|
|
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_print_field(b1, "eol_type", "%u %s\n", b->eol_type, buf);
|
|
eb_print_field(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_SHELL)
|
|
buf_puts(desc, " SHELL");
|
|
if (b->flags & BF_HELP)
|
|
buf_puts(desc, " HELP");
|
|
if (b->flags & BF_ERROR)
|
|
buf_puts(desc, " ERROR");
|
|
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_print_field(b1, "flags", "0x%02x %s\n", (unsigned)b->flags, buf);
|
|
#if 0
|
|
eb_print_field(b1, "probed", "%d\n", b->probed);
|
|
#endif
|
|
|
|
if (b->data_mode)
|
|
eb_print_field(b1, "data_mode", "%s\n", b->data_mode->name);
|
|
if (s->mode)
|
|
eb_print_field(b1, "s->mode", "%s\n", s->mode->name);
|
|
if (b->default_mode)
|
|
eb_print_field(b1, "default_mode", "%s\n", b->default_mode->name);
|
|
if (b->saved_mode)
|
|
eb_print_field(b1, "saved_mode", "%s\n", b->saved_mode->name);
|
|
|
|
eb_print_field(b1, "data_type", "%s\n", b->data_type->name);
|
|
eb_print_field(b1, "pages", "%d\n", b->nb_pages);
|
|
|
|
if (b->map_address) {
|
|
eb_print_field(b1, "map_address", "%p (length=%d, handle=%d)\n",
|
|
b->map_address, b->map_length, b->map_handle);
|
|
}
|
|
|
|
eb_print_field(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_print_field(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 iobuf[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, iobuf, countof(iobuf));
|
|
if (size == 0)
|
|
break;
|
|
for (i = 0; i < size; i++) {
|
|
c = iobuf[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_offset(max_count, count[i]);
|
|
}
|
|
count_width = snprintf(NULL, 0, "%d", max_count);
|
|
|
|
eb_print_field(b1, "chars", "%d\n", nb_chars);
|
|
eb_print_field(b1, "words", "%d\n", word_count);
|
|
eb_print_field(b1, "lines", "%d\n", line + (column > 0));
|
|
|
|
if (b->property_list) {
|
|
QEProperty *p;
|
|
|
|
eb_print_style(b1, DESCRIBE_STYLE_HEAD, "\nBuffer property list:\n");
|
|
|
|
for (p = b->property_list; p; p = p->next) {
|
|
eb_printf(b1, " %7d %c%c%c ", p->offset,
|
|
(p->flags & QE_PROP_FREE) ? 'F' : ' ',
|
|
(p->flags & QE_PROP_KEEP) ? 'K' : ' ',
|
|
(p->flags & QE_PROP_MARK) ? 'M' : ' ');
|
|
switch (p->type) {
|
|
case QE_PROP_TAG:
|
|
eb_printf(b1, "tag %s", (char*)p->data);
|
|
break;
|
|
case QE_PROP_CWD:
|
|
eb_printf(b1, "cwd %s", (char*)p->data);
|
|
break;
|
|
default:
|
|
eb_printf(b1, "%3d %p", p->type, p->data);
|
|
break;
|
|
}
|
|
eb_putc(b1, '\n');
|
|
}
|
|
}
|
|
|
|
eb_print_style(b1, DESCRIBE_STYLE_HEAD, "\nByte stats:\n");
|
|
|
|
for (col = i = 0; i < 256; i++) {
|
|
if (count[i] == 0)
|
|
continue;
|
|
|
|
if (i > 0 && i < 0x7f) {
|
|
int len = 0;
|
|
buf[len++] = '\'';
|
|
len += strquote_byte(buf + len, countof(buf) - len, i, SQB_USE_EXTENDED);
|
|
buf[len++] = '\'';
|
|
buf[len] = '\0';
|
|
} else {
|
|
snprintf(buf, countof(buf), "0x%02x", (unsigned)i);
|
|
}
|
|
col += eb_print_style(b1, QE_STYLE_STRING, " %5s", buf);
|
|
col += eb_print_style(b1, QE_STYLE_NUMBER, " %-*d", count_width, count[i]);
|
|
if (col >= 64) {
|
|
eb_putc(b1, '\n');
|
|
col = 0;
|
|
}
|
|
}
|
|
if (col)
|
|
eb_putc(b1, '\n');
|
|
}
|
|
|
|
if (b->nb_pages) {
|
|
Page *p;
|
|
int i, j;
|
|
|
|
eb_print_style(b1, DESCRIBE_STYLE_HEAD, "\nBuffer page layout:\n");
|
|
|
|
eb_print_style(b1, QE_STYLE_VARIABLE, " 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, " %5d %4d %5x %5d %4d %5d %p ",
|
|
i, p->size, (unsigned)p->flags, p->nb_lines, p->col, p->nb_chars,
|
|
(void *)p->data);
|
|
for (j = 0; j < 32 && j < p->size; j++) {
|
|
u8 cc = p->data[j];
|
|
buf[j] = (cc >= ' ' && cc < 0x7f) ? cc : '.';
|
|
}
|
|
buf[j] = '\0';
|
|
eb_print_style(b1, QE_STYLE_COMMENT, "|%s|%s\n", buf, p->size > j ? "..." : "");
|
|
}
|
|
eb_putc(b1, '\n');
|
|
}
|
|
|
|
show_popup(s, b1, "Buffer Description");
|
|
}
|
|
|
|
static void do_describe_window(EditState *s, int argval)
|
|
{
|
|
EditBuffer *b1;
|
|
|
|
b1 = new_help_buffer(s);
|
|
if (!b1)
|
|
return;
|
|
|
|
eb_print_style(b1, DESCRIBE_STYLE_HEAD, "Window fields:\n");
|
|
|
|
b1->tab_width = 28;
|
|
eb_print_field(b1, "xleft, ytop", "%d, %d\n", s->xleft, s->ytop);
|
|
eb_print_field(b1, "width, height", "%d, %d\n", s->width, s->height);
|
|
eb_print_field(b1, "x1, y1, x2, y2", "%d, %d, %d, %d\n", s->x1, s->y1, s->x2, s->y2);
|
|
eb_print_field(b1, "xx1, yy1, xx2, yy2", "%d, %d, %d, %d\n", s->xx1, s->yy1, s->xx2, s->yy2);
|
|
eb_print_field(b1, "flags", "%#x%s%s%s%s%s%s%s\n", (unsigned)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_print_field(b1, "offset", "%d\n", s->offset);
|
|
eb_print_field(b1, "offset_top", "%d\n", s->offset_top);
|
|
eb_print_field(b1, "offset_bottom", "%d\n", s->offset_bottom);
|
|
eb_print_field(b1, "y_disp", "%d\n", s->y_disp);
|
|
eb_print_field(b1, "x_disp[]", "%d, %d\n", s->x_disp[0], s->x_disp[1]);
|
|
eb_print_field(b1, "dump_width", "%d\n", s->dump_width);
|
|
eb_print_field(b1, "hex_mode", "%d\n", s->hex_mode);
|
|
eb_print_field(b1, "unihex_mode", "%d\n", s->unihex_mode);
|
|
eb_print_field(b1, "hex_nibble", "%d\n", s->hex_nibble);
|
|
eb_print_field(b1, "overwrite", "%d\n", s->overwrite);
|
|
if (s->input_method)
|
|
eb_print_field(b1, "input_method", "%s\n", s->input_method->name);
|
|
if (s->selected_input_method)
|
|
eb_print_field(b1, "selected_input_method", "%s\n", s->selected_input_method->name);
|
|
eb_print_field(b1, "bidir", "%d\n", s->bidir);
|
|
eb_print_field(b1, "cur_rtl", "%d\n", s->cur_rtl);
|
|
eb_print_field(b1, "wrap", "%u %s\n", 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_print_field(b1, "indent_width", "%d\n", s->indent_width);
|
|
eb_print_field(b1, "indent_tabs_mode", "%d\n", s->indent_tabs_mode);
|
|
eb_print_field(b1, "interactive", "%d\n", s->interactive);
|
|
eb_print_field(b1, "force_highlight", "%d\n", s->force_highlight);
|
|
eb_print_field(b1, "mouse_force_highlight", "%d\n", s->mouse_force_highlight);
|
|
eb_print_field(b1, "colorize_mode", "%s\n", s->colorize_mode ? s->colorize_mode->name : "none");
|
|
eb_print_field(b1, "default_style", "%lld\n", (long long)s->default_style);
|
|
eb_print_field(b1, "buffer", "%s\n", s->b->name);
|
|
if (s->last_buffer)
|
|
eb_print_field(b1, "last_buffer", "%s\n", s->last_buffer->name);
|
|
eb_print_field(b1, "mode", "%s\n", s->mode->name);
|
|
eb_print_field(b1, "colorize_nb_lines", "%d\n", s->colorize_nb_lines);
|
|
eb_print_field(b1, "colorize_nb_valid_lines", "%d\n", s->colorize_nb_valid_lines);
|
|
eb_print_field(b1, "colorize_max_valid_offset", "%d\n", s->colorize_max_valid_offset);
|
|
if (s->colorize_nb_valid_lines) {
|
|
int pos = eb_print_field(b1, "colorize_states", "[%d] {", s->colorize_nb_valid_lines);
|
|
int i, from, len;
|
|
unsigned int bits = 0;
|
|
int w1;
|
|
char buf[32];
|
|
for (i = 0; i < s->colorize_nb_valid_lines; i++) {
|
|
bits |= s->colorize_states[i];
|
|
}
|
|
w1 = snprintf(buf, sizeof buf, "%x", bits);
|
|
for (i = 0; i < s->colorize_nb_valid_lines;) {
|
|
bits = s->colorize_states[i];
|
|
for (from = i++; i < s->colorize_nb_valid_lines; i++) {
|
|
if (s->colorize_states[i] != bits)
|
|
break;
|
|
}
|
|
if (pos > 60) {
|
|
eb_putc(b1, '\n');
|
|
pos = eb_print_field(b1, "", " ");
|
|
}
|
|
len = snprintf(buf, sizeof buf, "%d", from);
|
|
if (i > from + 1)
|
|
len += snprintf(buf + len, sizeof(buf) - len, "..%d", i - 1);
|
|
pos += eb_printf(b1, " %s: 0x%.*x,", buf, w1, bits);
|
|
}
|
|
eb_printf(b1, " }\n");
|
|
}
|
|
eb_print_field(b1, "busy", "%d\n", s->busy);
|
|
eb_print_field(b1, "display_invalid", "%d\n", s->display_invalid);
|
|
eb_print_field(b1, "borders_invalid", "%d\n", s->borders_invalid);
|
|
eb_print_field(b1, "show_selection", "%d\n", s->show_selection);
|
|
eb_print_field(b1, "region_style", "%d\n", s->region_style);
|
|
eb_print_field(b1, "curline_style", "%d\n", s->curline_style);
|
|
eb_putc(b1, '\n');
|
|
|
|
show_popup(s, b1, "Window Description");
|
|
}
|
|
|
|
static void do_describe_screen(EditState *e, int argval)
|
|
{
|
|
QEditScreen *s = e->screen;
|
|
EditBuffer *b1;
|
|
|
|
b1 = new_help_buffer(e);
|
|
if (!b1)
|
|
return;
|
|
|
|
eb_print_style(b1, DESCRIBE_STYLE_HEAD, "\nScreen fields:\n\n");
|
|
|
|
b1->tab_width = 20;
|
|
eb_print_field(b1, "dpy.name", "%s\n", s->dpy.name);
|
|
eb_print_field(b1, "width, height", "%d, %d\n", s->width, s->height);
|
|
eb_print_field(b1, "charset", "%s\n", s->charset->name);
|
|
if (s->unicode_version) {
|
|
eb_print_field(b1, "Unicode version", "%d.%d.0\n",
|
|
s->unicode_version / 10, s->unicode_version % 10);
|
|
}
|
|
eb_print_field(b1, "media", "%d\n", s->media);
|
|
eb_print_field(b1, "bitmap_format", "%u\n", s->bitmap_format);
|
|
eb_print_field(b1, "video_format", "%u\n\n", s->video_format);
|
|
|
|
eb_print_field(b1, "QE_TERM_STYLE_BITS", "%d\n", QE_TERM_STYLE_BITS);
|
|
eb_print_field(b1, "QE_TERM_FG_COLORS", "%#x << %d\n", (unsigned)QE_TERM_FG_COLORS, QE_TERM_FG_SHIFT);
|
|
eb_print_field(b1, "QE_TERM_BG_COLORS", "%#x << %d\n", (unsigned)QE_TERM_BG_COLORS, QE_TERM_BG_SHIFT);
|
|
|
|
dpy_describe(s, b1);
|
|
|
|
show_popup(e, b1, "Screen Description");
|
|
}
|
|
|
|
/*---------------- buffer contents sorting ----------------*/
|
|
|
|
struct chunk_ctx {
|
|
EditBuffer *b;
|
|
int flags;
|
|
int col;
|
|
int nlines;
|
|
long ncmp;
|
|
long total_cmp;
|
|
};
|
|
|
|
struct chunk {
|
|
int start, end, offset;
|
|
unsigned short c[2];
|
|
};
|
|
|
|
static int eb_skip_to_basename(EditBuffer *b, int pos) {
|
|
int base = pos;
|
|
char32_t c;
|
|
while ((c = eb_nextc(b, pos, &pos)) != '\n') {
|
|
if (c == '/' || c == '\\')
|
|
base = pos;
|
|
}
|
|
return base;
|
|
}
|
|
|
|
static int chunk_cmp(void *vp0, const void *vp1, const void *vp2) {
|
|
struct chunk_ctx *cp = vp0;
|
|
const struct chunk *p1 = vp1;
|
|
const struct chunk *p2 = vp2;
|
|
int pos1, pos2;
|
|
|
|
if ((++cp->ncmp & 8191) == 8191) {
|
|
QEmacsState *qs = cp->b->qs;
|
|
put_status(qs->active_window, "&Sorting: %d%%", (int)((cp->ncmp * 90LL) / cp->total_cmp));
|
|
}
|
|
if (cp->flags & SF_REVERSE) {
|
|
p1 = vp2;
|
|
p2 = vp1;
|
|
}
|
|
|
|
if (p1->c[0] != p2->c[0]) {
|
|
return p1->c[0] < p2->c[0] ? -1 : 1;
|
|
}
|
|
if (p1->c[1] != p2->c[1]) {
|
|
return p1->c[1] < p2->c[1] ? -1 : 1;
|
|
}
|
|
pos1 = p1->start + p1->offset;
|
|
pos2 = p2->start + p2->offset;
|
|
for (;;) {
|
|
// XXX: should compute offset to first significant character in the setup phase
|
|
char32_t c1 = 0, c2 = 0;
|
|
while (pos1 < p1->end) {
|
|
c1 = eb_nextc(cp->b, pos1, &pos1);
|
|
/* XXX: incorrect for non ASCII contents */
|
|
if (!(cp->flags & SF_DICT) || qe_isdigit(c1) || qe_iswalpha(c1))
|
|
break;
|
|
if (c1 == '\t') {
|
|
c1 = 0;
|
|
break;
|
|
}
|
|
c1 = 0;
|
|
}
|
|
while (pos2 < p2->end) {
|
|
c2 = eb_nextc(cp->b, pos2, &pos2);
|
|
/* XXX: incorrect for non ASCII contents */
|
|
if (!(cp->flags & SF_DICT) || qe_isdigit(c2) || qe_iswalpha(c2))
|
|
break;
|
|
if (c2 == '\t') {
|
|
c2 = 0;
|
|
break;
|
|
}
|
|
c2 = 0;
|
|
}
|
|
// XXX: number conversion should not occur after decimal point
|
|
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 also ignore accents
|
|
c1 = qe_wtoupper(c1);
|
|
c2 = qe_wtoupper(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, j, offset, line1, line2, col1, col2, line, col, lines;
|
|
char32_t c;
|
|
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 */
|
|
// columns of the mark and point should determine the column range
|
|
if (flags & SF_COLUMN) {
|
|
eb_get_pos(b, &line, &col, cur_offset);
|
|
ctx.col = col ? col : col1;
|
|
}
|
|
lines = line2 - line1;
|
|
if (lines <= 1) {
|
|
*pp1 = p2;
|
|
*pp2 = p2;
|
|
goto done;
|
|
}
|
|
chunk_array = qe_malloc_array(struct chunk, lines);
|
|
if (!chunk_array) {
|
|
return -1;
|
|
}
|
|
offset = p1;
|
|
for (i = 0; i < lines && offset < p2; i++) {
|
|
int pos, pos1;
|
|
pos = offset;
|
|
if (flags & SF_COLUMN) {
|
|
for (col = ctx.col; col-- > 0;) {
|
|
c = eb_nextc(b, pos, &pos1);
|
|
if (c == '\n')
|
|
break;
|
|
pos = pos1;
|
|
}
|
|
}
|
|
if (flags & SF_BASENAME) {
|
|
pos = eb_skip_to_basename(b, pos);
|
|
}
|
|
/* read first 2 significant characters into cp->c,
|
|
skipping according to SF_DICT.
|
|
*/
|
|
for (j = 0; j < countof(chunk_array[i].c); j++) {
|
|
for (;;) {
|
|
c = eb_nextc(b, pos, &pos1);
|
|
if (c == '\n') {
|
|
c = 0;
|
|
break;
|
|
}
|
|
if ((flags & SF_DICT) && !qe_isdigit(c) && !qe_iswalpha(c)) {
|
|
pos = pos1;
|
|
continue;
|
|
}
|
|
if ((flags & SF_NUMBER) && qe_isdigit(c)) {
|
|
c = '0';
|
|
break;
|
|
}
|
|
if (flags & SF_FOLD) {
|
|
c = qe_wtoupper(c);
|
|
}
|
|
if (c > 0xFFFF)
|
|
c = 0xFFFF;
|
|
else
|
|
pos = pos1;
|
|
break;
|
|
}
|
|
chunk_array[i].c[j] = c;
|
|
}
|
|
chunk_array[i].start = offset;
|
|
chunk_array[i].offset = pos - offset;
|
|
chunk_array[i].end = offset = eb_goto_eol(b, pos);
|
|
offset = eb_next(b, offset);
|
|
if (flags & SF_PARAGRAPH) {
|
|
/* paragraph sorting: skip continuation lines */
|
|
// XXX: Should ignore initial indent
|
|
while (offset < p2 && qe_isspace(eb_peekc(b, offset))) {
|
|
chunk_array[i].end = offset = eb_goto_eol(b, offset);
|
|
offset = eb_next(b, offset);
|
|
}
|
|
}
|
|
}
|
|
/* for progress meter: n.log n comparisons + n insertions */
|
|
ctx.nlines = lines = i;
|
|
ctx.ncmp = 0;
|
|
/* evaluate the total number of comparisons: n.log2(n) */
|
|
for (ctx.total_cmp = 0; i > 0; i >>= 1) {
|
|
ctx.total_cmp += lines;
|
|
}
|
|
qe_qsort_r(chunk_array, lines, sizeof(*chunk_array), &ctx, chunk_cmp);
|
|
|
|
b1 = qe_new_buffer(b->qs, "*sorted*", BF_SYSTEM | (b->flags & BF_STYLES));
|
|
if (!b1)
|
|
return -1;
|
|
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_convert(b1, b1->total_size, b, chunk_array[i].start,
|
|
chunk_array[i].end - chunk_array[i].start);
|
|
// XXX: style issue. Should include newline from source buffer
|
|
eb_putc(b1, '\n');
|
|
if ((i & 8191) == 8191 && !(flags & SF_SILENT)) {
|
|
put_status(b->qs->active_window, "&Sorting: %d%%", (int)(90 + i * 10LL / lines));
|
|
}
|
|
}
|
|
eb_delete_range(b, p1, p2);
|
|
*pp1 = p1;
|
|
*pp2 = p1 + eb_insert_buffer_convert(b, p1, b1, 0, b1->total_size);
|
|
eb_free(&b1);
|
|
qe_free(&chunk_array);
|
|
done:
|
|
if (!(flags & SF_SILENT))
|
|
put_status(b->qs->active_window, "%d lines sorted", lines);
|
|
return 0;
|
|
}
|
|
|
|
static void do_sort_span(EditState *s, int p1, int p2, int argval, int flags) {
|
|
s->region_style = 0;
|
|
if (eb_sort_span(s->b, &p1, &p2, s->offset, flags | argval) < 0) {
|
|
put_error(s, "Out of memory");
|
|
return;
|
|
}
|
|
s->b->mark = p1;
|
|
s->offset = p2;
|
|
}
|
|
|
|
static void do_sort_region(EditState *s, int argval, int flags) {
|
|
do_sort_span(s, s->b->mark, s->offset, argval, flags);
|
|
}
|
|
|
|
static void do_sort_buffer(EditState *s, int argval, int flags) {
|
|
do_sort_span(s, 0, s->b->total_size, argval, flags);
|
|
}
|
|
|
|
/*---------------- tag handling ----------------*/
|
|
|
|
static void tag_buffer(EditState *s) {
|
|
QEColorizeContext cp[1];
|
|
int offset, line_num, col_num;
|
|
|
|
if (s->colorize_mode || s->b->b_styles) {
|
|
cp_initialize(cp, s);
|
|
/* force complete buffer colorization */
|
|
eb_get_pos(s->b, &line_num, &col_num, s->b->total_size);
|
|
get_colorized_line(cp, s->b->total_size, &offset, line_num);
|
|
cp_destroy(cp);
|
|
}
|
|
}
|
|
|
|
static void tag_complete(CompleteState *cp, CompleteFunc enumerate) {
|
|
/* 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) {
|
|
(*enumerate)(cp, p->data, CT_GLOB);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_mode && cp->target->colorize_mode) {
|
|
set_colorize_mode(s, 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 void do_find_tag(EditState *s, const char *str) {
|
|
QEmacsState *qs = s->qs;
|
|
QEProperty *p;
|
|
int offset = -1;
|
|
|
|
tag_buffer(s);
|
|
|
|
if (!qs->first_transient_key)
|
|
qe_register_transient_binding(qs, "goto-tag", ",, .");
|
|
|
|
for (p = s->b->property_list; p; p = p->next) {
|
|
if (p->type == QE_PROP_TAG && strequal(p->data, str)) {
|
|
if (offset < 0)
|
|
offset = p->offset;
|
|
if (s->offset < p->offset) {
|
|
s->offset = p->offset;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (offset >= 0) {
|
|
/* target not found after point, use first match */
|
|
s->offset = offset;
|
|
return;
|
|
}
|
|
put_error(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_error(s, "Tag too large");
|
|
return;
|
|
} else
|
|
if (len == 0) {
|
|
put_error(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, int argval) {
|
|
char buf[256];
|
|
EditBuffer *b;
|
|
QEProperty *p;
|
|
EditState *e1;
|
|
|
|
b = new_help_buffer(s);
|
|
if (!b)
|
|
return;
|
|
|
|
tag_buffer(s);
|
|
|
|
snprintf(buf, sizeof buf, "Tags in file %.*s", 242, 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->offset, s->b, offset, offset1 - offset);
|
|
eb_putc(b, '\n');
|
|
}
|
|
}
|
|
|
|
e1 = show_popup(s, b, buf);
|
|
if (s->colorize_mode) {
|
|
set_colorize_mode(e1, s->colorize_mode);
|
|
}
|
|
}
|
|
|
|
static CompletionDef tag_completion = {
|
|
.name = "tag",
|
|
.enumerate = tag_complete,
|
|
.print_entry = tag_print_entry,
|
|
.flags = CF_SAVE_LIST,
|
|
};
|
|
|
|
/*---------------- Unicode character name completion ----------------*/
|
|
|
|
static void charname_complete(CompleteState *cp, CompleteFunc enumerate) {
|
|
char buf[256];
|
|
char entry[264]; // silence compiler warning on snprintf calls below
|
|
QEmacsState *qs = cp->s->qs;
|
|
FILE *fp;
|
|
|
|
/* enumerate Unicode character names from Unicode consortium data */
|
|
if ((fp = qe_open_resource_file(qs, "DerivedName-15.0.0.txt")) != NULL
|
|
|| (fp = qe_open_resource_file(qs, "DerivedName.txt")) != NULL
|
|
|| (fp = qe_open_resource_file(qs, "extracted/DerivedName.txt")) != NULL) {
|
|
while (fgets(buf, sizeof buf, fp)) {
|
|
char *p1, *p2, *p3;
|
|
if ((p1 = strchr(buf, ';')) != NULL
|
|
&& p1[1] == ' '
|
|
&& !strchr(p1 + 2, '*')
|
|
&& (p2 = strchr(p1 + 2, '\n')) != NULL) {
|
|
*p1 = '\0';
|
|
p1 += 2;
|
|
*p2 = '\0';
|
|
p3 = strchr(buf, ' ');
|
|
if (p3)
|
|
*p3 = '\0';
|
|
snprintf(entry, sizeof entry, "%s\t%5.6s", p1, buf);
|
|
(*enumerate)(cp, entry, CT_IGLOB);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
} else
|
|
if ((fp = qe_open_resource_file(qs, "UnicodeData-15.0.0.txt")) != NULL
|
|
|| (fp = qe_open_resource_file(qs, "UnicodeData.txt")) != NULL) {
|
|
while (fgets(buf, sizeof buf, fp)) {
|
|
char *p1, *p2;
|
|
if ((p1 = strchr(buf, ';')) != NULL
|
|
&& p1[1] != ';'
|
|
&& p1[1] != '<'
|
|
&& (p2 = strchr(p1 + 1, ';')) != NULL) {
|
|
*p1++ = '\0';
|
|
*p2 = '\0';
|
|
snprintf(entry, sizeof entry, "%s\t%5.6s", p1, buf);
|
|
(*enumerate)(cp, entry, CT_IGLOB);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
} else {
|
|
put_error(cp->s, "Cannot find DerivedName.txt or UnicodeData.txt in QEPATH");
|
|
}
|
|
}
|
|
|
|
static long charname_convert_entry(EditState *s, const char *str, const char **endp) {
|
|
char buf[256];
|
|
QEmacsState *qs = s->qs;
|
|
FILE *fp;
|
|
long code = strtol_c(str, endp, 0);
|
|
if (**endp == '\0')
|
|
return code;
|
|
|
|
/* enumerate Unicode character names from Unicode consortium data */
|
|
if ((fp = qe_open_resource_file(qs, "DerivedName-15.0.0.txt")) != NULL
|
|
|| (fp = qe_open_resource_file(qs, "DerivedName.txt")) != NULL
|
|
|| (fp = qe_open_resource_file(qs, "extracted/DerivedName.txt")) != NULL) {
|
|
while (fgets(buf, sizeof buf, fp)) {
|
|
char *p1, *p2;
|
|
if ((p1 = strchr(buf, ';')) != NULL
|
|
&& p1[1] == ' '
|
|
&& !strchr(p1 + 2, '*')
|
|
&& (p2 = strchr(p1 + 2, '\n')) != NULL) {
|
|
*p2 = '\0';
|
|
if (utf8_strimatch_pat(p1 + 2, str, 0)) {
|
|
*endp = strchr(str, '\0');
|
|
return strtol(buf, NULL, 16);
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
} else
|
|
if ((fp = qe_open_resource_file(qs, "UnicodeData-15.0.0.txt")) != NULL
|
|
|| (fp = qe_open_resource_file(qs, "UnicodeData.txt")) != NULL) {
|
|
while (fgets(buf, sizeof buf, fp)) {
|
|
char *p1, *p2;
|
|
if ((p1 = strchr(buf, ';')) != NULL
|
|
&& p1[1] != ';'
|
|
&& p1[1] != '<'
|
|
&& (p2 = strchr(p1 + 1, ';')) != NULL) {
|
|
*p2 = '\0';
|
|
if (utf8_strimatch_pat(p1 + 1, str, 0)) {
|
|
*endp = strchr(str, '\0');
|
|
return strtol(buf, NULL, 16);
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
*endp = str;
|
|
return 0;
|
|
}
|
|
|
|
static int charname_print_entry(CompleteState *cp, EditState *s, const char *name) {
|
|
char *p = strchr(name, '\t');
|
|
if (p != NULL) {
|
|
char cbuf[MAX_CHAR_BYTES + 1];
|
|
char32_t code = strtol(p + 1, NULL, 16);
|
|
s->b->tab_width = max_int(s->b->tab_width, min_int(60, 2 + (p - name)));
|
|
return eb_printf(s->b, "%s %s", name,
|
|
utf8_char32_to_string(cbuf, code));
|
|
} else {
|
|
return eb_puts(s->b, name);
|
|
}
|
|
}
|
|
|
|
static int charname_get_entry(EditState *s, char *dest, int size, int offset) {
|
|
char entry[256];
|
|
char *p;
|
|
int len;
|
|
|
|
eb_fgets(s->b, entry, sizeof entry, offset, &offset);
|
|
p = strchr(entry, '\t');
|
|
if (p) {
|
|
p += strspn(p, " \t");
|
|
len = strcspn(p, " \t\n");
|
|
return snprintf(dest, size, "0x%.*s", len, p);
|
|
} else {
|
|
if (size > 0)
|
|
*dest = '\0';
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static CompletionDef charname_completion = {
|
|
.name = "charname",
|
|
.enumerate = charname_complete,
|
|
.print_entry = charname_print_entry,
|
|
.get_entry = charname_get_entry,
|
|
.convert_entry = charname_convert_entry,
|
|
};
|
|
|
|
/*---------------- style and color ----------------*/
|
|
|
|
static int eb_print_color(EditBuffer *b, const char *name) {
|
|
QETermStyle style = QE_STYLE_DEFAULT;
|
|
QETermStyle style2 = QE_STYLE_DEFAULT;
|
|
QEColor color = 0, rgb = 0;
|
|
char alt_name[16];
|
|
int len, len2, fg, bg = -1, dist = 0;
|
|
|
|
*alt_name = '\0';
|
|
if (!css_get_color(&color, name) && color != COLOR_TRANSPARENT) {
|
|
if (*name != '#') {
|
|
snprintf(alt_name, sizeof alt_name, "#%06x", color & 0xFFFFFF);
|
|
}
|
|
bg = qe_map_color(color, xterm_colors, QE_TERM_BG_COLORS, &dist);
|
|
fg = (color_y(color) >= 12800) ? 16 : 231;
|
|
style = QE_TERM_COMPOSITE | QE_TERM_MAKE_COLOR(fg, bg);
|
|
style2 = QE_TERM_COMPOSITE | QE_TERM_MAKE_COLOR(bg, 16);
|
|
rgb = qe_unmap_color(bg, 8192);
|
|
}
|
|
len = eb_print_style(b, QE_STYLE_FUNCTION, "%s", name);
|
|
b->tab_width = max_int(b->tab_width, len + 2);
|
|
eb_putc(b, '\t');
|
|
len += 1;
|
|
len += eb_printf(b, " %-10s ", alt_name);
|
|
len += eb_print_style(b, style, "[ ]");
|
|
len += eb_print_style(b, style2, "[ Sample ]");
|
|
len2 = eb_printf(b, " p%-4d", bg);
|
|
if (dist != 0)
|
|
len2 += eb_printf(b, " dist=%-4d", dist);
|
|
if (color != rgb)
|
|
len2 += eb_printf(b, " #%06x", rgb & 0xFFFFFF);
|
|
if (len2 < 32)
|
|
len += eb_printf(b, "%*s", 32 - len2, "");
|
|
return len + len2;
|
|
}
|
|
|
|
int color_print_entry(CompleteState *cp, EditState *s, const char *name) {
|
|
return eb_print_color(s->b, name);
|
|
}
|
|
|
|
static int eb_print_style_entry(EditBuffer *b, const char *name, int style) {
|
|
QEStyleDef *stp = NULL;
|
|
char buf[80];
|
|
int i, len;
|
|
|
|
if (name != NULL) {
|
|
for (i = 0; i < QE_STYLE_NB; i++) {
|
|
if (!strcmp(name, qe_styles[i].name)) {
|
|
style = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (style >= 0 && style < QE_STYLE_NB) {
|
|
stp = &qe_styles[style];
|
|
name = stp->name;
|
|
}
|
|
len = eb_print_style(b, QE_STYLE_FUNCTION, "%s\t", name);
|
|
b->tab_width = max_int(b->tab_width, len + 1);
|
|
len += eb_print_style(b, style, "[ Sample ]");
|
|
|
|
*buf = '\0';
|
|
if (stp) {
|
|
qe_styledef_string(buf, sizeof buf, stp);
|
|
}
|
|
len += eb_printf(b, " %-40s", buf);
|
|
return len;
|
|
}
|
|
|
|
int style_print_entry(CompleteState *cp, EditState *s, const char *name) {
|
|
return eb_print_style_entry(s->b, name, QE_STYLE_DEFAULT);
|
|
}
|
|
|
|
static void do_list_styles(EditState *s) {
|
|
EditBuffer *b;
|
|
int i;
|
|
|
|
b = new_help_buffer(s);
|
|
if (!b)
|
|
return;
|
|
|
|
for (i = 0; i < QE_STYLE_NB; i++) {
|
|
eb_print_style_entry(b, NULL, i);
|
|
eb_putc(b, '\n');
|
|
}
|
|
show_popup(s, b, "Quick Emacs Styles");
|
|
}
|
|
|
|
static void do_list_colors(EditState *s, int argval) {
|
|
EditBuffer *b;
|
|
char buf[32];
|
|
int i;
|
|
|
|
b = qe_new_buffer(s->qs, "*Colors*", BC_CLEAR | BF_SYSTEM | BF_UTF8 | BF_STYLE8);
|
|
if (!b)
|
|
return;
|
|
|
|
for (i = 0; i < nb_qe_colors; i++) {
|
|
eb_print_color(b, qe_colors[i].name);
|
|
eb_putc(b, '\n');
|
|
}
|
|
for (i = 0; i < 8192; i++) {
|
|
snprintf(buf, sizeof buf, "p%d", i);
|
|
eb_print_color(b, buf);
|
|
eb_putc(b, '\n');
|
|
}
|
|
show_popup(s, b, "Quick Emacs Colors");
|
|
}
|
|
|
|
/*---------------- paragraph handling ----------------*/
|
|
|
|
int eb_skip_whitespace(EditBuffer *b, int offset, int dir) {
|
|
/*@API buffer
|
|
Skip whitespace in a given direction.
|
|
@argument `b` a valid pointer to an `EditBuffer`
|
|
@argument `offset` the position in bytes in the buffer
|
|
@argument `dir` the skip direction (-1, 0, 1)
|
|
@return the new buffer position
|
|
*/
|
|
int p1;
|
|
if (dir > 0) {
|
|
while (offset < b->total_size && qe_isspace(eb_nextc(b, offset, &p1)))
|
|
offset = p1;
|
|
} else
|
|
if (dir < 0) {
|
|
while (offset > 0 && qe_isspace(eb_prevc(b, offset, &p1)))
|
|
offset = p1;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
int eb_skip_blank_lines(EditBuffer *b, int offset, int dir) {
|
|
/*@API buffer
|
|
Skip blank lines in a given direction
|
|
@argument `b` a valid pointer to an `EditBuffer`
|
|
@argument `offset` the position in bytes in the buffer
|
|
@argument `dir` the skip direction (-1, 0, 1)
|
|
@return the new buffer position:
|
|
- the value of `offset` if not on a blank line
|
|
- the beginning of the first blank line if skipping backward
|
|
- the beginning of the next non-blank line if skipping forward
|
|
*/
|
|
if (dir > 0) {
|
|
while (offset < b->total_size && eb_is_blank_line(b, offset, &offset))
|
|
continue;
|
|
} else {
|
|
int pos = eb_goto_bol(b, offset);
|
|
while (eb_is_blank_line(b, pos, NULL) && (offset = pos) > 0)
|
|
pos = eb_prev_line(b, pos);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
int eb_next_paragraph(EditBuffer *b, int offset) {
|
|
/*@API buffer
|
|
Find end of paragraph around or after point.
|
|
@argument `b` a valid pointer to an `EditBuffer`
|
|
@argument `offset` the position in bytes in the buffer
|
|
@return the new buffer position: skip any blank lines, then skip
|
|
non blank lines and return start of the blank line after text.
|
|
*/
|
|
offset = eb_skip_blank_lines(b, offset, 1);
|
|
while (offset < b->total_size && !eb_is_blank_line(b, offset, NULL))
|
|
offset = eb_next_line(b, offset);
|
|
return offset;
|
|
}
|
|
|
|
int eb_prev_paragraph(EditBuffer *b, int offset) {
|
|
/*@API buffer
|
|
Find start of paragraph around or before point.
|
|
@argument `b` a valid pointer to an `EditBuffer`
|
|
@argument `offset` the position in bytes in the buffer
|
|
@return the new buffer position: skip any blank lines before
|
|
`offset`, then skip non blank lines and return start of the blank
|
|
line before text. */
|
|
offset = eb_skip_blank_lines(b, offset, -1);
|
|
while (offset > 0) {
|
|
offset = eb_prev_line(b, offset);
|
|
if (eb_is_blank_line(b, offset, NULL))
|
|
break;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
int eb_skip_paragraphs(EditBuffer *b, int offset, int n) {
|
|
/*@API buffer
|
|
Skip one or more paragraphs in a given direction.
|
|
@argument `b` a valid pointer to an `EditBuffer`
|
|
@argument `offset` the position in bytes in the buffer
|
|
@argument `n` the number of paragraphs to skip, `n` can be negative.
|
|
@return the new buffer position:
|
|
- the value of `offset` if `n` is `0`
|
|
- the beginning of the n-th previous paragraph if skipping backward:
|
|
the beginning of the paragraph text or the previous blank line
|
|
if any.
|
|
- the end of the n-th next paragraph if skipping forward:
|
|
the end of the paragraph text or the beginning of the next blank
|
|
line if any.
|
|
*/
|
|
if (n < 0) {
|
|
while (n++ < 0 && offset > 0)
|
|
offset = eb_prev_paragraph(b, offset);
|
|
} else
|
|
if (n > 0) {
|
|
while (n-- > 0 && offset < b->total_size)
|
|
offset = eb_next_paragraph(b, offset);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
void do_forward_paragraph(EditState *s, int n) {
|
|
do_maybe_set_mark(s);
|
|
s->offset = eb_skip_paragraphs(s->b, s->offset, n);
|
|
}
|
|
|
|
void do_mark_paragraph(EditState *s, int n) {
|
|
/* mark-paragraph (bound to M-h):
|
|
|
|
mark_paragraph(n = arg_val)
|
|
|
|
Put point at beginning of this paragraph, mark at end.
|
|
The paragraph marked is the one that contains point or follows point.
|
|
|
|
With argument ARG, puts mark at end of a following paragraph, so that
|
|
the number of paragraphs marked equals ARG.
|
|
|
|
If ARG is negative, point is put at end of this paragraph, mark is put
|
|
at beginning of this or a previous paragraph.
|
|
The paragraph marked is the one that contains point or precedes point.
|
|
|
|
Interactively if the current region is highlighted, it marks
|
|
the next ARG paragraphs after the ones already marked.
|
|
*/
|
|
int start = s->offset;
|
|
int end = s->b->mark;
|
|
if (!s->region_style) {
|
|
if (n < 0) {
|
|
end = eb_prev_paragraph(s->b, start);
|
|
start = eb_next_paragraph(s->b, end);
|
|
n++;
|
|
} else
|
|
if (n > 0) {
|
|
end = eb_next_paragraph(s->b, start);
|
|
start = eb_prev_paragraph(s->b, end);
|
|
n--;
|
|
}
|
|
}
|
|
end = eb_skip_paragraphs(s->b, end, n);
|
|
do_mark_region(s, end, start);
|
|
}
|
|
|
|
void do_kill_paragraph(EditState *s, int n) {
|
|
/*
|
|
kill_paragraph(n = ARG)
|
|
|
|
Kill forward to end of paragraph.
|
|
With arg N, kill forward to Nth end of paragraph;
|
|
negative arg -N means kill backward to Nth start of paragraph.
|
|
*/
|
|
if (n != 0) {
|
|
int end = eb_skip_paragraphs(s->b, s->offset, n);
|
|
do_kill(s, s->offset, end, n, 0);
|
|
}
|
|
}
|
|
|
|
/* replace the contents between p1 and p2 with a specified number
|
|
of newlines and spaces */
|
|
static int eb_respace(EditBuffer *b, int p1, int p2, int newlines, int spaces) {
|
|
int adjust = 0, nb, offset1;
|
|
char32_t c;
|
|
while (newlines > 0 && p1 < p2) {
|
|
c = eb_nextc(b, p1, &offset1);
|
|
if (c != '\n')
|
|
break;
|
|
p1 = offset1;
|
|
newlines--;
|
|
}
|
|
if (newlines > 0) {
|
|
nb = eb_insert_char32_n(b, p1, '\n', newlines);
|
|
adjust += nb;
|
|
p1 += nb;
|
|
p2 += nb;
|
|
}
|
|
while (spaces > 0 && p1 < p2) {
|
|
c = eb_nextc(b, p1, &offset1);
|
|
if (c == ' ') {
|
|
p1 = offset1;
|
|
spaces--;
|
|
} else {
|
|
nb = eb_delete(b, p1, offset1 - p1);
|
|
adjust -= nb;
|
|
p2 -= nb;
|
|
}
|
|
}
|
|
if (p1 < p2) {
|
|
nb = eb_delete(b, p1, p2 - p1);
|
|
adjust -= nb;
|
|
p2 -= nb;
|
|
}
|
|
if (spaces) {
|
|
nb = eb_insert_spaces(b, p1, spaces);
|
|
adjust += nb;
|
|
p1 += nb;
|
|
p2 += nb;
|
|
}
|
|
return adjust;
|
|
}
|
|
|
|
static int get_indent_size(EditState *s, int p1, int p2) {
|
|
int indent_size = 0;
|
|
while (p1 < p2) {
|
|
char32_t c = eb_nextc(s->b, p1, &p1);
|
|
if (!qe_isblank(c))
|
|
break;
|
|
if (c == '\t') {
|
|
int tw = s->b->tab_width > 0 ? s->b->tab_width : DEFAULT_TAB_WIDTH;
|
|
indent_size += tw - indent_size % tw;
|
|
} else {
|
|
indent_size++;
|
|
}
|
|
}
|
|
return indent_size;
|
|
}
|
|
|
|
/* mode=0: fill the current paragraph
|
|
* mode=1: fill all paragraphs in the current region
|
|
* mode=2: fill all paragraphs in the buffer
|
|
* mode=3: fill whole region as single paragraph
|
|
* if region is active, use mode=1 instead of mode=0
|
|
*/
|
|
void do_fill_paragraph(EditState *s, int mode, int argval)
|
|
{
|
|
EditBuffer *b = s->b;
|
|
/* buffer offsets, byte counts */
|
|
int par_start, par_end, offset, offset1, chunk_start, word_start, end;
|
|
/* number of characters / screen positions */
|
|
int col, indent0_size, indent_size, word_size, nb;
|
|
|
|
par_start = end = s->offset;
|
|
if (mode == 1 || mode == 3 || s->region_style) {
|
|
par_start = min_offset(b->mark, s->offset);
|
|
end = max_offset(b->mark, s->offset);
|
|
}
|
|
if (mode == 2) {
|
|
par_start = 0;
|
|
end = b->total_size;
|
|
}
|
|
|
|
/* find start & end of paragraph */
|
|
if (!eb_is_blank_line(b, end, NULL))
|
|
end = eb_next_paragraph(b, end);
|
|
par_start = eb_goto_bol(b, par_start);
|
|
if (!eb_is_blank_line(b, par_start, NULL))
|
|
par_start = eb_prev_paragraph(b, par_start);
|
|
|
|
offset = par_start;
|
|
for (;;) {
|
|
par_start = eb_skip_blank_lines(b, offset, 1);
|
|
|
|
/* did we reach the end of the region? */
|
|
if (par_start >= end)
|
|
return;
|
|
|
|
/* compute indent sizes for first and second lines */
|
|
par_end = end;
|
|
if (mode != 3)
|
|
par_end = eb_next_paragraph(b, par_start);
|
|
if (end < par_end)
|
|
end = par_end;
|
|
indent_size = indent0_size = get_indent_size(s, par_start, par_end);
|
|
offset = eb_next_line(b, par_start);
|
|
if (offset < par_end)
|
|
indent_size = get_indent_size(s, offset, par_end);
|
|
|
|
/* reflow words to fill lines */
|
|
col = 0;
|
|
offset = par_start;
|
|
// XXX: should justify with spaces if C-u prefix
|
|
while (offset < par_end) {
|
|
/* skip spaces */
|
|
chunk_start = offset;
|
|
while (offset < par_end) {
|
|
char32_t c = eb_nextc(b, offset, &offset1);
|
|
if (!qe_isspace(c))
|
|
break;
|
|
offset = offset1;
|
|
}
|
|
/* skip word */
|
|
word_start = offset;
|
|
word_size = 0;
|
|
while (offset < par_end) {
|
|
char32_t c = eb_nextc(b, offset, &offset1);
|
|
if (qe_isspace(c))
|
|
break;
|
|
offset = offset1;
|
|
/* handle variable width and combining glyphs */
|
|
word_size += qe_wcwidth(c);
|
|
}
|
|
|
|
if (chunk_start == par_start) {
|
|
/* preserve spaces at paragraph start */
|
|
col += indent0_size + word_size;
|
|
} else {
|
|
if (word_start == offset) {
|
|
/* space at end of paragraph: replace with a newline */
|
|
nb = eb_respace(b, chunk_start, word_start, 1, 0);
|
|
offset += nb;
|
|
par_end += nb;
|
|
end += nb;
|
|
break;
|
|
}
|
|
if (col + 1 + word_size > b->fill_column) {
|
|
/* insert newline and indentation */
|
|
col = indent_size + word_size;
|
|
nb = eb_respace(b, chunk_start, word_start, 1, indent_size);
|
|
} else {
|
|
/* single space the word */
|
|
col += 1 + word_size;
|
|
nb = eb_respace(b, chunk_start, word_start, 0, 1);
|
|
}
|
|
offset += nb;
|
|
par_end += nb;
|
|
end += nb;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*---------------- sentence handling ----------------*/
|
|
|
|
/* Sentence end: "[.?!\u2026\u203d][]\"'\u201d\u2019)}\u00bb\u203a]*" */
|
|
|
|
int eb_next_sentence(EditBuffer *b, int offset) {
|
|
/*@API buffer
|
|
Find end of sentence after point.
|
|
@argument `b` a valid pointer to an `EditBuffer`
|
|
@argument `offset` the position in bytes in the buffer
|
|
@return the new buffer position: search for the sentence-end
|
|
pattern and skip it.
|
|
*/
|
|
int p1, p2;
|
|
char32_t c, c2;
|
|
int start = offset;
|
|
int end = eb_next_paragraph(b, offset);
|
|
while (offset < end) {
|
|
c = eb_nextc(b, offset, &offset);
|
|
if (!(c == '.' || c == '?' || c == '!'))
|
|
continue;
|
|
for (;;) {
|
|
c = eb_nextc(b, offset, &p1);
|
|
if (c == ']' || c == '"' || c == '\'' || c == ')' || c == '}')
|
|
offset = p1;
|
|
else
|
|
break;
|
|
}
|
|
c2 = eb_nextc(b, p1, &p2);
|
|
if (c == ' ' && qe_isspace(c2))
|
|
return offset;
|
|
if (c == '\n' && p2 >= end)
|
|
return offset;
|
|
}
|
|
return max_int(start, eb_skip_whitespace(b, offset, -1));
|
|
}
|
|
|
|
int eb_prev_sentence(EditBuffer *b, int offset) {
|
|
/*@API buffer
|
|
Find start of sentence before point.
|
|
@argument `b` a valid pointer to an `EditBuffer`
|
|
@argument `offset` the position in bytes in the buffer
|
|
@return the new buffer position: first non blank character after end
|
|
of previous sentence.
|
|
*/
|
|
int p1, p2, p3, end;
|
|
char32_t c, c2;
|
|
|
|
while (offset > 0) {
|
|
c = eb_prevc(b, offset, &offset);
|
|
if (!qe_findchar(".?! \t\n", c))
|
|
break;
|
|
}
|
|
end = eb_prev_paragraph(b, offset);
|
|
end = eb_skip_blank_lines(b, end, 1);
|
|
|
|
while ((p1 = offset) > end) {
|
|
c = eb_prevc(b, offset, &offset);
|
|
if (!(c == '.' || c == '?' || c == '!'))
|
|
continue;
|
|
for (;;) {
|
|
c = eb_nextc(b, p1, &p2);
|
|
if (c == ']' || c == '"' || c == '\'' || c == ')' || c == '}')
|
|
p1 = p2;
|
|
else
|
|
break;
|
|
}
|
|
c2 = eb_nextc(b, p2, &p3);
|
|
if (c == ' ' && qe_isspace(c2))
|
|
break;
|
|
}
|
|
return eb_skip_whitespace(b, p1, 1);
|
|
}
|
|
|
|
int eb_skip_sentences(EditBuffer *b, int offset, int n) {
|
|
/*@API buffer
|
|
Skip one or more sentences in a given direction.
|
|
@argument `b` a valid pointer to an `EditBuffer`
|
|
@argument `offset` the position in bytes in the buffer
|
|
@argument `n` the number of sentences to skip, `n` can be negative.
|
|
@return the new buffer position:
|
|
- the value of `offset` if `n` is `0`
|
|
- the beginning of the n-th previous sentence if skipping backward:
|
|
the beginning of the sentence text or the previous blank line
|
|
if any.
|
|
- the end of the n-th next sentence if skipping forward:
|
|
the end of the sentence text or the beginning of the next blank
|
|
line if any.
|
|
*/
|
|
if (n < 0) {
|
|
while (n++ < 0 && offset > 0)
|
|
offset = eb_prev_sentence(b, offset);
|
|
} else
|
|
if (n > 0) {
|
|
while (n-- > 0 && offset < b->total_size)
|
|
offset = eb_next_sentence(b, offset);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
void do_forward_sentence(EditState *s, int n) {
|
|
do_maybe_set_mark(s);
|
|
s->offset = eb_skip_sentences(s->b, s->offset, n);
|
|
}
|
|
|
|
void do_mark_sentence(EditState *s, int n) {
|
|
/* mark-sentence:
|
|
|
|
mark_sentence(n = arg_val)
|
|
|
|
Put point at point, mark at end of sentence.
|
|
The sentence marked is the one that contains point or follows point.
|
|
|
|
With argument ARG, puts mark at end of a following sentence, so that
|
|
the number of sentences marked equals ARG.
|
|
|
|
If ARG is negative, point is put at point, mark is put
|
|
at beginning of this or a previous sentence.
|
|
The sentence marked is the one that contains point or precedes point.
|
|
|
|
If the current region is highlighted, it marks
|
|
the next ARG sentences after the ones already marked.
|
|
*/
|
|
int start = s->offset;
|
|
int end = s->region_style ? s->b->mark : s->offset;
|
|
end = eb_skip_sentences(s->b, end, n);
|
|
do_mark_region(s, end, start);
|
|
}
|
|
|
|
void do_kill_sentence(EditState *s, int n) {
|
|
/*
|
|
kill_sentence(n = ARG)
|
|
|
|
Kill forward to end of sentence.
|
|
With arg N, kill forward to Nth end of sentence;
|
|
negative arg -N means kill backward to Nth start of sentence.
|
|
*/
|
|
if (n != 0) {
|
|
int end = eb_skip_sentences(s->b, s->offset, n);
|
|
do_kill(s, s->offset, end, n, 0);
|
|
}
|
|
}
|
|
|
|
/*---------------- command and binding definitions ----------------*/
|
|
|
|
static const CmdDef extra_commands[] = {
|
|
|
|
CMD2( "compare-windows", "M-=",
|
|
"Compare windows, optionally ignoring white space, comments and case",
|
|
do_compare_windows, ESi, "#" "p")
|
|
CMD3( "compare-files", "C-x RET C-l",
|
|
"Compare file and other version in parent directory",
|
|
do_compare_files, ESsi,
|
|
"#" "s{Compare file: }[file]|file|"
|
|
"v", 0) /* p? */
|
|
CMD2( "define-equivalent", "",
|
|
"Define equivalent strings for compare-windows",
|
|
do_define_equivalent, ESss,
|
|
"s{Enter string: }|equivalent|s{Equivalent to: }|equivalent|")
|
|
CMD2( "delete-equivalent", "",
|
|
"Delete a pair of equivalent strings",
|
|
do_delete_equivalent, ESs,
|
|
"s{Enter string: }|equivalent|")
|
|
CMD2( "list-equivalents", "",
|
|
"List equivalent strings",
|
|
do_list_equivalents, ESi, "p")
|
|
|
|
CMD3( "enlarge-window", "C-x ^",
|
|
"Enlarge window vertically",
|
|
do_adjust_window, ESii, "p" "v", QE_AW_ENLARGE | QE_AW_VERTICAL)
|
|
CMD3( "shrink-window", "C-x v, C-x V",
|
|
"Shrink window vertically",
|
|
do_adjust_window, ESii, "p" "v", QE_AW_SHRINK | QE_AW_VERTICAL)
|
|
CMD3( "enlarge-window-horizontally", "C-x }",
|
|
"Enlarge window horizontally",
|
|
do_adjust_window, ESii, "p" "v", QE_AW_ENLARGE | QE_AW_HORIZONTAL)
|
|
CMD3( "shrink-window-horizontally", "C-x {",
|
|
"Shrink window horizontally",
|
|
do_adjust_window, ESii, "p" "v", QE_AW_SHRINK | QE_AW_HORIZONTAL)
|
|
CMD0( "resize-window", "C-x +",
|
|
"Resize window interactively using the cursor keys",
|
|
do_resize_window)
|
|
|
|
CMD3( "delete-leading-space", "", // (mg)
|
|
"Delete any leading whitespace on the current line",
|
|
do_delete_horizontal_space, ESi, "*" "v", DH_BOL)
|
|
CMD3( "delete-trailing-space", "", // (mg)
|
|
"Delete any trailing whitespace on the current line",
|
|
do_delete_horizontal_space, ESi, "*" "v", DH_EOL)
|
|
CMD3( "delete-trailing-whitespace", "", // (emacs)
|
|
"Delete all the trailing whitespace across the current buffer",
|
|
do_delete_horizontal_space, ESi, "*" "v", DH_FULL)
|
|
CMD3( "delete-horizontal-space", "M-\\",
|
|
"Delete blanks around point",
|
|
do_delete_horizontal_space, ESi, "*", DH_POINT)
|
|
CMD2( "delete-blank-lines", "C-x C-o",
|
|
"Delete blank lines on and/or around point",
|
|
do_delete_blank_lines, ES, "*")
|
|
CMD2( "tabify-region", "",
|
|
"Convert multiple spaces in region to tabs when possible",
|
|
do_tabify, ESii, "*" "md")
|
|
CMD2( "untabify-region", "",
|
|
"Convert all tabs in region to spaces, preserving columns",
|
|
do_untabify, ESii, "*" "md")
|
|
CMD2( "tabify-buffer", "",
|
|
"Convert multiple spaces in buffer to tabs when possible",
|
|
do_tabify, ESii, "*" "ze")
|
|
CMD2( "untabify-buffer", "",
|
|
"Convert all tabs in buffer to multiple spaces, preserving columns",
|
|
do_untabify, ESii, "*" "ze")
|
|
|
|
CMD2( "indent-region", "M-C-\\",
|
|
"Indent each nonblank line that starts inside the region",
|
|
do_indent_region, ESiii, "*" "mdp")
|
|
CMD2( "unindent-region", "M-C-|",
|
|
"Unindent each nonblank line that starts inside the region",
|
|
do_indent_region, ESiii, "*" "mdq")
|
|
CMD2( "indent-buffer", "",
|
|
"Indent each nonblank line in the buffer",
|
|
do_indent_region, ESiii, "*" "zep")
|
|
CMD2( "unindent-buffer", "",
|
|
"Unindent each nonblank line in the buffer",
|
|
do_indent_region, ESiii, "*" "zeq")
|
|
CMD2( "indent-rigidly", "C-x TAB",
|
|
"Indent the region interactively",
|
|
do_indent_rigidly, ESiii, "*" "md" "P")
|
|
CMD3( "indent-rigidly-left", "",
|
|
"Decrease the region indentation",
|
|
do_indent_rigidly_by, ESiii, "*" "md" "v", -1)
|
|
CMD3( "indent-rigidly-right", "",
|
|
"Increase the region indentation",
|
|
do_indent_rigidly_by, ESiii, "*" "md" "v", +1)
|
|
CMD3( "indent-rigidly-left-to-tab-stop", "",
|
|
"Decrease the region indentation by one tab width",
|
|
do_indent_rigidly_to_tab_stop, ESiii, "*" "md" "v", -1)
|
|
CMD3( "indent-rigidly-right-to-tab-stop", "",
|
|
"Increase the region indentation by one tab width",
|
|
do_indent_rigidly_to_tab_stop, ESiii, "*" "md" "v", +1)
|
|
|
|
CMD2( "show-date-and-time", "C-x t",
|
|
"Show current date and time",
|
|
do_show_date_and_time, ESi, "P")
|
|
|
|
CMD2( "backward-block", "M-C-b, ESC C-left",
|
|
"Move backwards past parenthesized expression, ignoring comments and strings",
|
|
do_forward_block, ESi, "q")
|
|
CMD2( "forward-block", "M-C-f, ESC C-right",
|
|
"Move past parenthesized expression, ignoring comments and strings",
|
|
do_forward_block, ESi, "p")
|
|
CMD2( "backward-kill-block", "ESC delete",
|
|
"Kill from point to the beginning of the previous block",
|
|
do_kill_block, ESi, "q")
|
|
CMD2( "kill-block", "M-C-k",
|
|
"Kill from point to the end of the next block",
|
|
do_kill_block, ESi, "p")
|
|
/* Should also have mark-block on M-C-@ */
|
|
|
|
CMD3( "transpose-chars", "C-t",
|
|
"Swap character at point and before it",
|
|
do_transpose, ESi, "*" "v", CMD_TRANSPOSE_CHARS)
|
|
CMD3( "transpose-lines", "C-x C-t",
|
|
"Swap line at point and previous line",
|
|
do_transpose, ESi, "*" "v", CMD_TRANSPOSE_LINES)
|
|
CMD3( "transpose-words", "M-t",
|
|
"Swap words before and after point",
|
|
do_transpose, ESi, "*" "v", CMD_TRANSPOSE_WORDS)
|
|
|
|
CMD2( "show-bindings", "C-h B, f5",
|
|
"Show current bindings for a given command",
|
|
do_show_bindings, ESs,
|
|
"s{Show bindings of command: }[command]|command|")
|
|
CMD3( "global-unset-key", "",
|
|
"Remove global key binding",
|
|
do_unset_key, ESsi,
|
|
"s{Unset key globally: }[key]"
|
|
"v", 0)
|
|
CMD3( "local-unset-key", "",
|
|
"Remove local (mode specific) key binding",
|
|
do_unset_key, ESsi,
|
|
"s{Unset key locally: }[key]"
|
|
"v", 1)
|
|
|
|
// XXX: the commands below should use do_load_file_from_path()
|
|
CMDx( "qemacs-hello", "C-h h",
|
|
"Create a test buffer with various charsets",
|
|
do_qemacs_hello)
|
|
CMDx( "qemacs-manual", "C-h m",
|
|
"Show the Quick Emacs manual",
|
|
do_qemacs_manual)
|
|
CMDx( "qemacs-faq", "C-h C-f",
|
|
"Show the Quick Emacs FAQ",
|
|
do_qemacs_faq)
|
|
|
|
CMD0( "about-qemacs", "C-h q",
|
|
"Display information about Quick Emacs",
|
|
do_about_qemacs)
|
|
CMD2( "apropos", "C-h a, C-h C-a",
|
|
"List commands and variables matching a topic",
|
|
do_apropos, ESs,
|
|
"s{Apropos: }[symbol]|apropos|")
|
|
CMD2( "wall-chart", "C-h w",
|
|
"List local and global key bindings as a wall chart",
|
|
do_wall_chart, ESi, "p")
|
|
CMD2( "describe-bindings", "C-h b",
|
|
"List local and global key bindings",
|
|
do_describe_bindings, ESi, "p")
|
|
CMD2( "describe-buffer", "C-h C-b",
|
|
"Show information about the current buffer",
|
|
do_describe_buffer, ESi, "p")
|
|
CMD2( "describe-function", "C-h f",
|
|
"Show information and bindings for a command",
|
|
do_describe_function, ESs,
|
|
"s{Describe function: }[command]|command|")
|
|
CMD2( "describe-screen", "C-h s, C-h C-s",
|
|
"Show information about the current screen",
|
|
do_describe_screen, ESi, "p")
|
|
CMD2( "describe-window", "C-h C-w",
|
|
"Show information about the current window",
|
|
do_describe_window, ESi, "p")
|
|
CMD2( "describe-prefix-bindings", "C-h p",
|
|
"Display a list of commands with a common binding prefix",
|
|
do_describe_prefix_bindings, ESs, "s{Binding prefix: }")
|
|
CMD2( "describe-C-h", "C-h ?",
|
|
"Display a summary of C-h commands",
|
|
do_describe_prefix_bindings, ESs, "@{C-h}")
|
|
CMD2( "describe-C-c", "C-c ?, C-c C-h",
|
|
"Display a summary of C-c commands",
|
|
do_describe_prefix_bindings, ESs, "@{C-c}")
|
|
CMD2( "describe-C-x", "C-x ?, C-x C-h",
|
|
"Display a summary of C-x commands",
|
|
do_describe_prefix_bindings, ESs, "@{C-x}")
|
|
CMD2( "describe-C-x-RET", "C-x RET ?, C-x RET C-h",
|
|
"Display a summary of C-x RET commands",
|
|
do_describe_prefix_bindings, ESs, "@{C-x RET}")
|
|
|
|
/* XXX: should take region as argument, implicit from keyboard */
|
|
CMD2( "set-region-color", "C-c c",
|
|
"Set the color for the current region",
|
|
do_set_region_color, ESs,
|
|
"s{Select color: }[.color]|color|")
|
|
CMD2( "set-region-style", "C-c s",
|
|
"Set the style for the current region",
|
|
do_set_region_style, ESs,
|
|
"s{Select style: }[style]|style|")
|
|
CMD0( "drop-styles", "",
|
|
"Remove all styles for the current buffer",
|
|
do_drop_styles)
|
|
CMD2( "set-style-color", "C-c x",
|
|
"Set the color(s) for a named style",
|
|
do_set_style_color, ESss,
|
|
"s{Style: }[style]|style|"
|
|
"s{Style color: }[.color]|color|")
|
|
CMD2( "read-color", "C-c #",
|
|
"Read a color and produce its hex RGB value",
|
|
do_read_color, ESsi,
|
|
"s{Color: }[color]|color|" "p")
|
|
CMD2( "list-colors", "C-c C",
|
|
"List available colors",
|
|
do_list_colors, ESi, "p")
|
|
CMD2( "list-styles", "C-c S",
|
|
"List available styles and their definitions",
|
|
do_list_styles, ES, "")
|
|
|
|
CMD2( "set-eol-type", "",
|
|
"Set the end of line style: [0=Unix, 1=Dos, 2=Mac]",
|
|
do_set_eol_type, ESi,
|
|
"N{EOL Type [0=Unix, 1=Dos, 2=Mac]: }")
|
|
|
|
// XXX: should take region as argument, implicit from keyboard
|
|
// XXX: should have sort-fields and sort-numeric-fields
|
|
CMD3( "sort-buffer", "",
|
|
"Sort the buffer contents according to sorting options",
|
|
do_sort_buffer, ESii, "*" "p" "v", 0)
|
|
CMD3( "sort-columns", "",
|
|
"Sort the lines in the region from the current column",
|
|
do_sort_region, ESii, "*" "p" "v", SF_COLUMN)
|
|
CMD3( "sort-lines", "",
|
|
"Sort the lines in the region according to sorting options",
|
|
do_sort_region, ESii, "*" "p" "v", 0)
|
|
CMD3( "sort-region", "",
|
|
"Sort the lines in the region according to sorting options",
|
|
do_sort_region, ESii, "*" "p" "v", 0)
|
|
CMD3( "sort-numbers", "",
|
|
"Sort the lines in the region as numbers",
|
|
do_sort_region, ESii, "*" "p" "v", SF_NUMBER)
|
|
CMD3( "sort-paragraphs", "",
|
|
"Sort the paragraphs in the region alphabetically",
|
|
do_sort_region, ESii, "*" "p" "v", SF_PARAGRAPH)
|
|
|
|
CMD2( "list-tags", "",
|
|
"List the buffer tags detected automatically",
|
|
do_list_tags, ESi, "p")
|
|
CMD0( "goto-tag", "C-x ,, M-f1",
|
|
"Move point to the tag for the word at point",
|
|
do_goto_tag)
|
|
CMD2( "find-tag", "C-x .",
|
|
"Move point to a given tag",
|
|
do_find_tag, ESs,
|
|
"s{Find tag: }[tag]|tag|")
|
|
|
|
/*---------------- Paragraph handling ----------------*/
|
|
|
|
CMD2( "mark-paragraph", "M-h",
|
|
"Mark the paragraph at or after point",
|
|
do_mark_paragraph, ESi, "p")
|
|
CMD2( "backward-paragraph", "M-{, C-up, C-S-up",
|
|
"Move point to the beginning of the paragraph at or before point",
|
|
do_forward_paragraph, ESi, "q")
|
|
CMD2( "forward-paragraph", "M-}, C-down, C-S-down",
|
|
"Move point to the end of the paragraph at or after point",
|
|
do_forward_paragraph, ESi, "p")
|
|
CMD3( "fill-paragraph", "M-q",
|
|
"Fill the current paragraph, preserving indentation of the first 2 lines",
|
|
do_fill_paragraph, ESii, "*" "v" "p", 0)
|
|
CMD3( "fill-region", "",
|
|
"Fill all paragraphs in the current region",
|
|
do_fill_paragraph, ESii, "*" "v" "p", 1)
|
|
CMD3( "fill-buffer", "",
|
|
"Fill all paragraphs in the buffer",
|
|
do_fill_paragraph, ESii, "*" "v" "p", 2)
|
|
CMD3( "fill-region-as-paragraph", "",
|
|
"Fill all paragraphs in the current region as a single paragraph",
|
|
do_fill_paragraph, ESii, "*" "v" "p", 3)
|
|
CMD2( "backward-kill-paragraph", "",
|
|
"Kill back to start of paragraph",
|
|
do_kill_paragraph, ESi, "*" "q")
|
|
CMD2( "kill-paragraph", "",
|
|
"Kill forward to end of paragraph",
|
|
do_kill_paragraph, ESi, "*" "p")
|
|
CMD3( "transpose-paragraphs", "",
|
|
"Interchange the current paragraph with the next one",
|
|
do_transpose, ESi, "*" "v", CMD_TRANSPOSE_PARAGRAPHS)
|
|
CMDx( "center-paragraph", "",
|
|
"Center each nonblank line in the paragraph at or after point",
|
|
do_center_paragraph, ES, "*")
|
|
|
|
CMD2( "backward-kill-sentence", "C-x DEL",
|
|
"Kill back from point to start of sentence",
|
|
do_kill_sentence, ESi, "*" "q")
|
|
CMD2( "backward-sentence", "M-a",
|
|
"Move backward to start of sentence",
|
|
do_forward_sentence, ESi, "q")
|
|
CMD2( "forward-sentence", "M-e",
|
|
"Move forward to next end of sentence. With argument, repeat",
|
|
do_forward_sentence, ESi, "p")
|
|
CMD2( "kill-sentence", "M-k",
|
|
"Kill from point to end of sentence",
|
|
do_kill_sentence, ESi, "*" "p")
|
|
CMD2( "mark-end-of-sentence", "",
|
|
"Put mark at end of sentence",
|
|
do_mark_sentence, ESi, "q")
|
|
CMDx( "repunctuate-sentences", "",
|
|
"Put two spaces at the end of sentences from point to the end of buffer",
|
|
do_repunctuate_sentences, ES, "*")
|
|
CMD3( "transpose-sentences", "",
|
|
"Interchange the current sentence with the next one",
|
|
do_transpose, ESi, "*" "v", CMD_TRANSPOSE_SENTENCES)
|
|
};
|
|
|
|
static int extras_init(QEmacsState *qs) {
|
|
qe_register_commands(qs, NULL, extra_commands, countof(extra_commands));
|
|
qe_register_completion(qs, &tag_completion);
|
|
qe_register_completion(qs, &charname_completion);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void extras_exit(QEmacsState *qs) {
|
|
qe_free_equivalents(qs);
|
|
}
|
|
|
|
qe_module_init(extras_init);
|
|
qe_module_exit(extras_exit);
|