- add flags to find_file_open(): - FF_PATH: enumerate path argument - FF_NODIR: only match regular files - FF_NOXXDIR: do not match . or .. - FF_ONLYDIR: do not match non directories - 0..15: max recursion depth - add recursion level in find_file_next() - add completion pointer in CompleteState - add CF_DIRNAME and CF_RESOURCE CompletionDef flags - pass filename to load_ligatures() and load_input_methods() - remove find_resource_file() dummy definition in html2png.c - add dir and resource completion methods - allow makepath() to modify path in place - enable completion in `load-resource-file` and `load-file-from-path`
9502 lines
269 KiB
C
9502 lines
269 KiB
C
/*
|
|
* QEmacs, tiny but powerful multimode editor
|
|
*
|
|
* Copyright (c) 2000-2002 Fabrice Bellard.
|
|
* Copyright (c) 2000-2020 Charlie Gordon.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "qe.h"
|
|
|
|
#ifdef CONFIG_TINY
|
|
#undef CONFIG_DLL
|
|
#undef CONFIG_ALL_KMAPS
|
|
#undef CONFIG_UNICODE_JOIN
|
|
#endif
|
|
|
|
#include "qfribidi.h"
|
|
|
|
#include "variables.h"
|
|
|
|
#ifdef CONFIG_DLL
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
/* each history list */
|
|
typedef struct HistoryEntry {
|
|
struct HistoryEntry *next;
|
|
StringArray history;
|
|
char name[32];
|
|
} HistoryEntry;
|
|
|
|
#ifdef CONFIG_INIT_CALLS
|
|
static int (*qe__initcall_first)(void) qe__init_call = NULL;
|
|
static void (*qe__exitcall_first)(void) qe__exit_call = NULL;
|
|
#endif
|
|
|
|
void print_at_byte(QEditScreen *screen,
|
|
int x, int y, int width, int height,
|
|
const char *str, QETermStyle style);
|
|
static EditBuffer *predict_switch_to_buffer(EditState *s);
|
|
static StringArray *get_history(const char *name);
|
|
static void qe_key_process(int key);
|
|
|
|
static int generic_save_window_data(EditState *s);
|
|
static int generic_mode_init(EditState *s);
|
|
static void generic_mode_close(EditState *s);
|
|
static void generic_text_display(EditState *s);
|
|
static void display1(DisplayState *ds);
|
|
#ifndef CONFIG_TINY
|
|
static void save_selection(void);
|
|
#endif
|
|
|
|
QEmacsState qe_state;
|
|
/* should handle multiple screens, and multiple sessions */
|
|
static QEditScreen global_screen;
|
|
static int screen_width = 0;
|
|
static int screen_height = 0;
|
|
static int no_init_file;
|
|
static int single_window;
|
|
int force_tty;
|
|
int disable_crc;
|
|
int use_session_file;
|
|
int use_html = 1;
|
|
#ifndef CONFIG_TINY
|
|
static int free_everything;
|
|
#endif
|
|
|
|
/* mode handling */
|
|
|
|
static int default_mode_init(EditState *s, EditBuffer *b, int flags) { return 0; }
|
|
|
|
static int generic_mode_probe(ModeDef *mode, ModeProbeData *p)
|
|
{
|
|
if (match_extension(p->filename, mode->extensions)
|
|
|| match_shell_handler(cs8(p->buf), mode->shell_handlers)) {
|
|
return 80;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
ModeDef *qe_find_mode(const char *name, int flags)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
ModeDef *m;
|
|
|
|
for (m = qs->first_mode; m; m = m->next) {
|
|
if ((m->flags & flags) == flags) {
|
|
if ((m->name && !strcasecmp(m->name, name))
|
|
|| (m->alt_name && !strcasecmp(m->alt_name, name))
|
|
|| strfind(m->extensions, name))
|
|
break;
|
|
}
|
|
}
|
|
return m;
|
|
}
|
|
|
|
ModeDef *qe_find_mode_filename(const char *filename, int flags)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
ModeDef *m;
|
|
|
|
for (m = qs->first_mode; m; m = m->next) {
|
|
if ((m->flags & flags) == flags
|
|
&& match_extension(filename, m->extensions)) {
|
|
break;
|
|
}
|
|
}
|
|
return m;
|
|
}
|
|
|
|
void qe_register_mode(ModeDef *m, int flags)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
ModeDef **p;
|
|
|
|
/* register mode in mode list (at end) */
|
|
for (p = &qs->first_mode;; p = &(*p)->next) {
|
|
if (*p == m) {
|
|
/* mode is already registered, do nothing */
|
|
return;
|
|
}
|
|
if (*p == NULL) {
|
|
m->next = NULL;
|
|
*p = m;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m->flags |= flags;
|
|
|
|
if (m->flags & MODEF_SYNTAX) {
|
|
/* default to text handling */
|
|
/* should follow the fallback chain */
|
|
if (!m->display_line)
|
|
m->display_line = text_display_line;
|
|
if (!m->backward_offset)
|
|
m->backward_offset = text_backward_offset;
|
|
if (!m->move_up_down)
|
|
m->move_up_down = text_move_up_down;
|
|
if (!m->move_left_right)
|
|
m->move_left_right = text_move_left_right_visual;
|
|
if (!m->move_bol)
|
|
m->move_bol = text_move_bol;
|
|
if (!m->move_eol)
|
|
m->move_eol = text_move_eol;
|
|
if (!m->move_bof)
|
|
m->move_bof = text_move_bof;
|
|
if (!m->move_eof)
|
|
m->move_eof = text_move_eof;
|
|
if (!m->move_word_left_right)
|
|
m->move_word_left_right = text_move_word_left_right;
|
|
if (!m->scroll_up_down)
|
|
m->scroll_up_down = text_scroll_up_down;
|
|
if (!m->mouse_goto)
|
|
m->mouse_goto = text_mouse_goto;
|
|
if (!m->write_char)
|
|
m->write_char = text_write_char;
|
|
}
|
|
|
|
/* add missing functions */
|
|
if (!m->mode_init)
|
|
m->mode_init = default_mode_init;
|
|
/* if no syntax probing function, use extension matcher */
|
|
if (!m->mode_probe && m->extensions)
|
|
m->mode_probe = generic_mode_probe;
|
|
if (!m->display)
|
|
m->display = generic_text_display;
|
|
if (!m->data_type)
|
|
m->data_type = &raw_data_type;
|
|
if (!m->get_mode_line)
|
|
m->get_mode_line = text_mode_line;
|
|
|
|
/* add a new command to switch to that mode */
|
|
if (!(m->flags & MODEF_NOCMD)) {
|
|
char name[64];
|
|
char spec[64];
|
|
int spec_len;
|
|
CmdDef *def;
|
|
const char *mode_name = m->alt_name ? m->alt_name : m->name;
|
|
|
|
/* lower case convert for C mode, Perl... */
|
|
qe_strtolower(name, sizeof(name) - 10, mode_name);
|
|
pstrcat(name, sizeof(name), "-mode");
|
|
|
|
/* constant immediate string parameter */
|
|
spec_len = snprintf(spec, sizeof(spec),
|
|
"S{%s}%cselect the %s mode for the current buffer",
|
|
mode_name, 0, mode_name);
|
|
def = qe_mallocz_array(CmdDef, 2);
|
|
def->name = qe_strdup(name);
|
|
def->spec = qe_malloc_dup(spec, spec_len + 1);
|
|
def->key = def->alt_key = KEY_NONE;
|
|
def->sig = CMD_ESs;
|
|
def->val = 0;
|
|
def->action.ESs = do_set_mode;
|
|
def[1].val = 1; /* flag as allocated for free-all */
|
|
qe_register_cmd_table(def, NULL);
|
|
}
|
|
}
|
|
|
|
void mode_complete(CompleteState *cp)
|
|
{
|
|
QEmacsState *qs = cp->s->qe_state;
|
|
ModeDef *m;
|
|
|
|
for (m = qs->first_mode; m != NULL; m = m->next) {
|
|
complete_test(cp, m->name);
|
|
if (m->alt_name && !strequal(m->name, m->alt_name))
|
|
complete_test(cp, m->alt_name);
|
|
}
|
|
}
|
|
|
|
static CompletionDef mode_completion = {
|
|
"mode", mode_complete
|
|
};
|
|
|
|
/* commands handling */
|
|
|
|
CmdDef *qe_find_cmd(const char *cmd_name)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
CmdDef *d;
|
|
|
|
d = qs->first_cmd;
|
|
while (d != NULL) {
|
|
while (d->name != NULL) {
|
|
if (strequal(cmd_name, d->name))
|
|
return d;
|
|
d++;
|
|
}
|
|
d = d->action.next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void command_complete(CompleteState *cp)
|
|
{
|
|
QEmacsState *qs = cp->s->qe_state;
|
|
CmdDef *d;
|
|
|
|
d = qs->first_cmd;
|
|
while (d != NULL) {
|
|
while (d->name != NULL) {
|
|
complete_test(cp, d->name);
|
|
d++;
|
|
}
|
|
d = d->action.next;
|
|
}
|
|
}
|
|
|
|
int command_print_entry(CompleteState *cp, EditState *s, const char *name)
|
|
{
|
|
EditBuffer *b = s->b;
|
|
CmdDef *d = qe_find_cmd(name);
|
|
if (d) {
|
|
char buf[256];
|
|
int len;
|
|
|
|
b->cur_style = QE_STYLE_FUNCTION;
|
|
len = eb_puts(b, d->name);
|
|
b->cur_style = QE_STYLE_DEFAULT;
|
|
qe_get_prototype(d, buf, sizeof buf);
|
|
len += eb_puts(b, buf);
|
|
#ifndef CONFIG_TINY
|
|
if (qe_list_bindings(d, s->mode, 1, buf, sizeof buf)) {
|
|
b->cur_style = QE_STYLE_COMMENT;
|
|
if (2 + len < 40) {
|
|
b->tab_width = max(2 + len, b->tab_width);
|
|
len += eb_putc(b, '\t');
|
|
} else {
|
|
b->tab_width = 40;
|
|
}
|
|
len += eb_printf(b, " bound to %s", buf);
|
|
b->cur_style = QE_STYLE_DEFAULT;
|
|
}
|
|
#endif
|
|
return len;
|
|
} else {
|
|
return eb_puts(b, name);
|
|
}
|
|
}
|
|
|
|
int command_get_entry(EditState *s, char *dest, int size, int offset)
|
|
{
|
|
int len;
|
|
eb_fgets(s->b, dest, size, offset, &offset);
|
|
len = strcspn(dest, " \t\n(");
|
|
dest[len] = '\0'; /* strip the TAB or trailing newline if any */
|
|
return len;
|
|
}
|
|
|
|
static CompletionDef command_completion = {
|
|
"command", command_complete, command_print_entry, command_get_entry
|
|
};
|
|
|
|
static int qe_register_binding1(unsigned int *keys, int nb_keys,
|
|
CmdDef *d, ModeDef *m)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
KeyDef **lp, *p;
|
|
int i;
|
|
|
|
if (!d)
|
|
return -1;
|
|
|
|
/* add key */
|
|
p = qe_malloc_hack(KeyDef, (nb_keys - 1) * sizeof(p->keys[0]));
|
|
if (!p)
|
|
return -1;
|
|
p->cmd = d;
|
|
p->nb_keys = nb_keys;
|
|
for (i = 0; i < nb_keys; i++) {
|
|
p->keys[i] = keys[i];
|
|
}
|
|
lp = m ? &m->first_key : &qs->first_key;
|
|
/* Bindings must be prepended to override previous bindings */
|
|
/* XXX: should avoid multiple redundant bindings */
|
|
#if 0
|
|
while (*lp != NULL && (*lp)->mode != NULL)
|
|
lp = &(*lp)->next;
|
|
#endif
|
|
p->next = *lp;
|
|
*lp = p;
|
|
return 0;
|
|
}
|
|
|
|
/* convert compressed mappings to real ones */
|
|
static int qe_register_binding2(int key, CmdDef *d, ModeDef *m)
|
|
{
|
|
int nb_keys;
|
|
unsigned int keys[3];
|
|
|
|
nb_keys = 0;
|
|
if (key >= KEY_CTRLX(0) && key <= KEY_CTRLX(0xff)) {
|
|
keys[nb_keys++] = KEY_CTRL('x');
|
|
keys[nb_keys++] = key & 0xff;
|
|
} else
|
|
if (key >= KEY_CTRLXRET(0) && key <= KEY_CTRLXRET(0xff)) {
|
|
keys[nb_keys++] = KEY_CTRL('x');
|
|
keys[nb_keys++] = KEY_RET;
|
|
keys[nb_keys++] = key & 0xff;
|
|
} else
|
|
if (key >= KEY_CTRLH(0) && key <= KEY_CTRLH(0xff)) {
|
|
keys[nb_keys++] = KEY_CTRL('h');
|
|
keys[nb_keys++] = key & 0xff;
|
|
} else
|
|
if (key >= KEY_CTRLC(0) && key <= KEY_CTRLC(0xff)) {
|
|
keys[nb_keys++] = KEY_CTRL('c');
|
|
keys[nb_keys++] = key & 0xff;
|
|
} else {
|
|
keys[nb_keys++] = key;
|
|
}
|
|
return qe_register_binding1(keys, nb_keys, d, m);
|
|
}
|
|
|
|
/* if mode is non NULL, the defined keys are only active in this mode */
|
|
void qe_register_cmd_table(CmdDef *cmds, ModeDef *m)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
CmdDef **ld, *d;
|
|
|
|
/* find last command table */
|
|
for (ld = &qs->first_cmd;;) {
|
|
d = *ld;
|
|
if (d == NULL) {
|
|
/* link new command table */
|
|
*ld = cmds;
|
|
break;
|
|
}
|
|
if (d == cmds) {
|
|
/* Command table already registered, still do the binding
|
|
* phase to allow multiple mode bindings.
|
|
*/
|
|
break;
|
|
}
|
|
while (d->name != NULL) {
|
|
d++;
|
|
}
|
|
ld = &d->action.next;
|
|
}
|
|
|
|
/* add default bindings */
|
|
for (d = cmds; d->name != NULL; d++) {
|
|
if (d->key == KEY_CTRL('x') || d->key == KEY_CTRL('c')
|
|
|| d->key == KEY_ESC) {
|
|
unsigned int keys[2];
|
|
keys[0] = d->key;
|
|
keys[1] = d->alt_key;
|
|
qe_register_binding1(keys, 2, d, m);
|
|
} else {
|
|
if (d->key != KEY_NONE)
|
|
qe_register_binding2(d->key, d, m);
|
|
if (d->alt_key != KEY_NONE)
|
|
qe_register_binding2(d->alt_key, d, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* key binding handling */
|
|
|
|
int qe_register_binding(int key, const char *cmd_name, ModeDef *m)
|
|
{
|
|
return qe_register_binding2(key, qe_find_cmd(cmd_name), m);
|
|
}
|
|
|
|
int qe_mode_set_key(ModeDef *m, const char *keystr, const char *cmd_name)
|
|
{
|
|
unsigned int keys[MAX_KEYS];
|
|
int nb_keys;
|
|
|
|
nb_keys = strtokeys(keystr, keys, MAX_KEYS);
|
|
if (!nb_keys)
|
|
return -2;
|
|
|
|
return qe_register_binding1(keys, nb_keys, qe_find_cmd(cmd_name), m);
|
|
}
|
|
|
|
void do_set_key(EditState *s, const char *keystr,
|
|
const char *cmd_name, int local)
|
|
{
|
|
ModeDef *m = local ? s->mode : NULL;
|
|
int res = qe_mode_set_key(m, keystr, cmd_name);
|
|
|
|
if (res == -2)
|
|
put_status(s, "Invalid keys: %s", keystr);
|
|
if (res == -1)
|
|
put_status(s, "Invalid command: %s", cmd_name);
|
|
}
|
|
|
|
void do_toggle_control_h(EditState *s, int set)
|
|
{
|
|
/* Achtung Minen! do_toggle_control_h can be called from tty_init
|
|
* with a NULL EditState.
|
|
*/
|
|
QEmacsState *qs = s ? s->qe_state : &qe_state;
|
|
ModeDef *m;
|
|
KeyDef *kd;
|
|
int i;
|
|
|
|
if (set)
|
|
set = (set > 0);
|
|
else
|
|
set = !qs->backspace_is_control_h;
|
|
|
|
if (qs->backspace_is_control_h == set)
|
|
return;
|
|
|
|
qs->backspace_is_control_h = set;
|
|
|
|
/* CG: This hack in incompatible with support for multiple
|
|
* concurrent input consoles.
|
|
*/
|
|
for (m = qs->first_mode;; m = m->next) {
|
|
for (kd = m ? m->first_key : qs->first_key; kd; kd = kd->next) {
|
|
for (i = 0; i < kd->nb_keys; i++) {
|
|
switch (kd->keys[i]) {
|
|
case KEY_CTRL('h'):
|
|
kd->keys[i] = set ? KEY_META('h') : KEY_DEL;
|
|
break;
|
|
case KEY_DEL:
|
|
if (set)
|
|
kd->keys[i] = KEY_CTRL('h');
|
|
break;
|
|
case KEY_META('h'):
|
|
if (!set)
|
|
kd->keys[i] = KEY_CTRL('h');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!m)
|
|
break;
|
|
}
|
|
}
|
|
|
|
void do_set_emulation(EditState *s, const char *name)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
|
|
if (strequal(name, "epsilon")) {
|
|
qs->emulation_flags = 1;
|
|
qs->flag_split_window_change_focus = 1;
|
|
} else
|
|
if (strequal(name, "emacs") || strequal(name, "xemacs")) {
|
|
qs->emulation_flags = 0;
|
|
qs->flag_split_window_change_focus = 0;
|
|
} else
|
|
if (strequal(name, "vi") || strequal(name, "vim")) {
|
|
put_status(s, "Emulation '%s' not available yet", name);
|
|
} else {
|
|
put_status(s, "Unknown emulation '%s'", name);
|
|
}
|
|
}
|
|
|
|
void do_start_trace_mode(EditState *s)
|
|
{
|
|
do_set_trace_options(s, "all");
|
|
}
|
|
|
|
void do_set_trace_options(EditState *s, const char *options)
|
|
{
|
|
char buf[80];
|
|
const char *p = options;
|
|
QEmacsState *qs = s->qe_state;
|
|
int last_flags = qs->trace_flags;
|
|
|
|
while (*p) {
|
|
p += strspn(p, " \t,");
|
|
if (strstart(p, "none", &p) || strstart(p, "off", &p))
|
|
qs->trace_flags = 0;
|
|
else
|
|
if (strstart(p, "all", &p) || strstart(p, "on", &p))
|
|
qs->trace_flags = EB_TRACE_ALL;
|
|
else
|
|
if (strstart(p, "tty", &p)) {
|
|
qs->trace_flags |= EB_TRACE_TTY;
|
|
} else
|
|
if (strstart(p, "shell", &p)) {
|
|
qs->trace_flags |= EB_TRACE_SHELL;
|
|
} else
|
|
if (strstart(p, "pty", &p)) {
|
|
qs->trace_flags |= EB_TRACE_PTY;
|
|
} else
|
|
if (strstart(p, "emulate", &p)) {
|
|
qs->trace_flags |= EB_TRACE_EMULATE;
|
|
} else
|
|
if (strstart(p, "command", &p)) {
|
|
qs->trace_flags |= EB_TRACE_COMMAND;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (qs->trace_flags) {
|
|
if (!qs->trace_buffer) {
|
|
qs->trace_buffer = eb_new("*trace*", BF_SYSTEM);
|
|
}
|
|
if (!last_flags) {
|
|
EditState *e = qe_split_window(s, SW_STACKED, 75);
|
|
if (e) {
|
|
do_switch_to_buffer(e, "*trace*");
|
|
}
|
|
}
|
|
*buf = '\0';
|
|
if (qs->trace_flags & EB_TRACE_TTY) {
|
|
strcat(buf, ", tty");
|
|
}
|
|
if (qs->trace_flags & EB_TRACE_SHELL) {
|
|
strcat(buf, ", shell");
|
|
}
|
|
if (qs->trace_flags & EB_TRACE_PTY) {
|
|
strcat(buf, ", pty");
|
|
}
|
|
if (qs->trace_flags & EB_TRACE_EMULATE) {
|
|
strcat(buf, ", emulate");
|
|
}
|
|
if (qs->trace_flags & EB_TRACE_COMMAND) {
|
|
strcat(buf, ", command");
|
|
}
|
|
put_status(s, "Tracing enabled for %s", buf + 2);
|
|
} else {
|
|
put_status(s, "Tracing disabled");
|
|
}
|
|
}
|
|
|
|
void do_cd(EditState *s, const char *path)
|
|
{
|
|
char buf[MAX_FILENAME_SIZE];
|
|
|
|
canonicalize_absolute_path(s, buf, sizeof(buf), path);
|
|
|
|
if (chdir(buf)) {
|
|
put_status(s, "Cannot change directory to '%s'", buf);
|
|
} else {
|
|
if (!getcwd(buf, sizeof(buf)))
|
|
put_status(s, "Cannot get current directory");
|
|
else
|
|
put_status(s, "Current directory: %s", buf);
|
|
}
|
|
}
|
|
|
|
/* basic editing functions */
|
|
|
|
void do_bof(EditState *s)
|
|
{
|
|
if (s->mode->move_bof)
|
|
s->mode->move_bof(s);
|
|
}
|
|
|
|
void do_eof(EditState *s)
|
|
{
|
|
if (s->mode->move_eof)
|
|
s->mode->move_eof(s);
|
|
}
|
|
|
|
void do_bol(EditState *s)
|
|
{
|
|
if (s->mode->move_bol)
|
|
s->mode->move_bol(s);
|
|
}
|
|
|
|
void do_eol(EditState *s)
|
|
{
|
|
if (s->mode->move_eol)
|
|
s->mode->move_eol(s);
|
|
}
|
|
|
|
void do_word_left_right(EditState *s, int n)
|
|
{
|
|
int dir = n < 0 ? -1 : 1;
|
|
|
|
for (; n != 0; n -= dir) {
|
|
if (s->mode->move_word_left_right)
|
|
s->mode->move_word_left_right(s, dir);
|
|
}
|
|
}
|
|
|
|
void text_move_bof(EditState *s)
|
|
{
|
|
s->offset = 0;
|
|
}
|
|
|
|
void text_move_eof(EditState *s)
|
|
{
|
|
s->offset = s->b->total_size;
|
|
}
|
|
|
|
void text_move_bol(EditState *s)
|
|
{
|
|
s->offset = eb_goto_bol(s->b, s->offset);
|
|
}
|
|
|
|
void text_move_eol(EditState *s)
|
|
{
|
|
s->offset = eb_goto_eol(s->b, s->offset);
|
|
}
|
|
|
|
void word_right(EditState *s, int w)
|
|
{
|
|
int c, offset1;
|
|
|
|
for (;;) {
|
|
if (s->offset >= s->b->total_size)
|
|
break;
|
|
c = eb_nextc(s->b, s->offset, &offset1);
|
|
if (qe_isword(c) == w)
|
|
break;
|
|
s->offset = offset1;
|
|
}
|
|
}
|
|
|
|
void word_left(EditState *s, int w)
|
|
{
|
|
int c, offset1;
|
|
|
|
for (;;) {
|
|
if (s->offset == 0)
|
|
break;
|
|
c = eb_prevc(s->b, s->offset, &offset1);
|
|
if (qe_isword(c) == w)
|
|
break;
|
|
s->offset = offset1;
|
|
}
|
|
}
|
|
|
|
void text_move_word_left_right(EditState *s, int dir)
|
|
{
|
|
if (dir > 0) {
|
|
word_right(s, 1);
|
|
word_right(s, 0);
|
|
} else {
|
|
word_left(s, 1);
|
|
word_left(s, 0);
|
|
}
|
|
}
|
|
|
|
int qe_get_word(EditState *s, char *buf, int buf_size,
|
|
int offset, int *offset_ptr)
|
|
{
|
|
EditBuffer *b = s->b;
|
|
buf_t outbuf, *out;
|
|
int offset1;
|
|
int c;
|
|
|
|
out = buf_init(&outbuf, buf, buf_size);
|
|
|
|
/* XXX: the qe_isword pattern should depend on the current mode */
|
|
if (qe_isword(eb_nextc(b, offset, &offset1))) {
|
|
while (qe_isword(eb_prevc(b, offset, &offset1))) {
|
|
offset = offset1;
|
|
}
|
|
} else {
|
|
while ((offset = offset1) < b->total_size) {
|
|
if (!qe_isword(eb_nextc(b, offset, &offset1)))
|
|
break;
|
|
}
|
|
}
|
|
while (offset < b->total_size) {
|
|
if (!qe_isword(c = eb_nextc(b, offset, &offset1)))
|
|
break;
|
|
buf_putc_utf8(out, c);
|
|
offset = offset1;
|
|
}
|
|
if (offset_ptr) {
|
|
*offset_ptr = offset;
|
|
}
|
|
return out->len;
|
|
}
|
|
|
|
void do_mark_region(EditState *s, int mark, int offset)
|
|
{
|
|
/* CG: Should have local and global mark rings */
|
|
s->b->mark = clamp(mark, 0, s->b->total_size);
|
|
s->offset = clamp(offset, 0, s->b->total_size);
|
|
/* activate region hilite */
|
|
if (s->qe_state->hilite_region)
|
|
s->region_style = QE_STYLE_REGION_HILITE;
|
|
}
|
|
|
|
/* paragraph handling */
|
|
|
|
int eb_next_paragraph(EditBuffer *b, int offset)
|
|
{
|
|
int text_found;
|
|
|
|
offset = eb_goto_bol(b, offset);
|
|
/* find end of paragraph */
|
|
text_found = 0;
|
|
for (;;) {
|
|
if (offset >= b->total_size)
|
|
break;
|
|
if (eb_is_blank_line(b, offset, NULL)) {
|
|
if (text_found)
|
|
break;
|
|
} else {
|
|
text_found = 1;
|
|
}
|
|
offset = eb_next_line(b, offset);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
int eb_start_paragraph(EditBuffer *b, int offset)
|
|
{
|
|
for (;;) {
|
|
offset = eb_goto_bol(b, offset);
|
|
if (offset <= 0)
|
|
break;
|
|
/* check if only spaces */
|
|
if (eb_is_blank_line(b, offset, &offset)) {
|
|
break;
|
|
}
|
|
offset = eb_prev(b, offset);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
void do_mark_paragraph(EditState *s)
|
|
{
|
|
int start = eb_start_paragraph(s->b, s->offset);
|
|
int end = eb_next_paragraph(s->b, s->offset);
|
|
|
|
do_mark_region(s, start, end);
|
|
}
|
|
|
|
int eb_prev_paragraph(EditBuffer *b, int offset)
|
|
{
|
|
/* skip empty lines */
|
|
while (offset > 0) {
|
|
offset = eb_goto_bol(b, offset);
|
|
if (!eb_is_blank_line(b, offset, NULL))
|
|
break;
|
|
/* line just before */
|
|
offset = eb_prev(b, offset);
|
|
}
|
|
|
|
offset = eb_start_paragraph(b, offset);
|
|
|
|
/* line just before */
|
|
return eb_prev_line(b, offset);
|
|
}
|
|
|
|
void do_forward_paragraph(EditState *s, int n)
|
|
{
|
|
for (; n < 0 && s->offset > 0; n++) {
|
|
s->offset = eb_prev_paragraph(s->b, s->offset);
|
|
}
|
|
for (; n > 0 && s->offset < s->b->total_size; n--) {
|
|
s->offset = eb_next_paragraph(s->b, s->offset);
|
|
}
|
|
}
|
|
|
|
void do_kill_paragraph(EditState *s, int n)
|
|
{
|
|
int start = s->offset;
|
|
|
|
if (n != 0) {
|
|
do_forward_paragraph(s, n);
|
|
do_kill(s, start, s->offset, n, 0);
|
|
}
|
|
}
|
|
|
|
void do_fill_paragraph(EditState *s)
|
|
{
|
|
/* buffer offsets, byte counts */
|
|
int par_start, par_end, offset, offset1, chunk_start, word_start;
|
|
/* number of characters */
|
|
int col, indent_size, word_size, space_size;
|
|
/* other counts */
|
|
int n, word_count;
|
|
/* character */
|
|
int c;
|
|
|
|
/* find start & end of paragraph */
|
|
par_start = eb_start_paragraph(s->b, s->offset);
|
|
par_end = eb_next_paragraph(s->b, par_start);
|
|
|
|
/* compute indent size */
|
|
indent_size = 0;
|
|
offset = eb_next_line(s->b, par_start);
|
|
if (!eb_is_blank_line(s->b, offset, NULL)) {
|
|
while (offset < par_end) {
|
|
c = eb_nextc(s->b, offset, &offset);
|
|
if (!qe_isblank(c))
|
|
break;
|
|
indent_size++;
|
|
}
|
|
}
|
|
|
|
/* suppress any spaces in between */
|
|
col = 0;
|
|
offset = par_start;
|
|
word_count = 0;
|
|
while (offset < par_end) {
|
|
/* skip spaces */
|
|
chunk_start = offset;
|
|
space_size = 0;
|
|
while (offset < par_end) {
|
|
c = eb_nextc(s->b, offset, &offset1);
|
|
if (!qe_isspace(c))
|
|
break;
|
|
offset = offset1;
|
|
space_size++;
|
|
}
|
|
/* skip word */
|
|
word_start = offset;
|
|
word_size = 0;
|
|
while (offset < par_end) {
|
|
c = eb_nextc(s->b, offset, &offset1);
|
|
if (qe_isspace(c))
|
|
break;
|
|
offset = offset1;
|
|
word_size++;
|
|
}
|
|
|
|
if (word_count == 0) {
|
|
/* first word: preserve spaces */
|
|
col += space_size + word_size;
|
|
} else {
|
|
/* insert space single space the word */
|
|
if (offset == par_end
|
|
|| (col + 1 + word_size > s->b->fill_column)) {
|
|
/* XXX: should check if separator is a newline */
|
|
eb_delete_uchar(s->b, chunk_start);
|
|
chunk_start += eb_insert_uchar(s->b, chunk_start, '\n');
|
|
if (offset < par_end) {
|
|
/* indent */
|
|
int nb = eb_insert_spaces(s->b, offset, indent_size);
|
|
chunk_start += nb;
|
|
word_start += nb;
|
|
offset += nb;
|
|
par_end += nb;
|
|
}
|
|
col = word_size + indent_size;
|
|
} else {
|
|
/* XXX: should check if separator is a space */
|
|
eb_delete_uchar(s->b, chunk_start);
|
|
chunk_start += eb_insert_uchar(s->b, chunk_start, ' ');
|
|
col += 1 + word_size;
|
|
}
|
|
|
|
/* remove all other spaces if needed */
|
|
n = word_start - chunk_start;
|
|
if (n > 0) {
|
|
eb_delete(s->b, chunk_start, n);
|
|
offset -= n;
|
|
par_end -= n;
|
|
}
|
|
}
|
|
word_count++;
|
|
}
|
|
}
|
|
|
|
/* Upper / lower / capital case functions. Update offset, return isword */
|
|
/* arg: -1=lower-case, +1=upper-case, +2=capital-case */
|
|
static int eb_changecase(EditBuffer *b, int *offsetp, int arg)
|
|
{
|
|
int offset0, ch, ch1, len;
|
|
char buf[MAX_CHAR_BYTES];
|
|
|
|
offset0 = *offsetp;
|
|
ch = eb_nextc(b, offset0, offsetp);
|
|
if (!qe_isword(ch))
|
|
return 0;
|
|
|
|
if (arg > 0)
|
|
ch1 = qe_toupper(ch);
|
|
else
|
|
ch1 = qe_tolower(ch);
|
|
|
|
if (ch != ch1) {
|
|
len = eb_encode_uchar(b, buf, ch1);
|
|
/* replaced char may have a different encoding len from
|
|
* original char, such as dotless i in Turkish. */
|
|
eb_replace(b, offset0, *offsetp - offset0, buf, len);
|
|
*offsetp = offset0 + len;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void do_changecase_word(EditState *s, int arg)
|
|
{
|
|
int offset;
|
|
|
|
word_right(s, 1);
|
|
for (offset = s->offset;;) {
|
|
if (offset >= s->b->total_size)
|
|
break;
|
|
if (!eb_changecase(s->b, &offset, arg))
|
|
break;
|
|
s->offset = offset;
|
|
if (arg == 2)
|
|
arg = -2;
|
|
}
|
|
}
|
|
|
|
void do_changecase_region(EditState *s, int arg)
|
|
{
|
|
int offset;
|
|
|
|
/* deactivate region hilite */
|
|
s->region_style = 0;
|
|
|
|
/* WARNING: during case change, the region offsets can change, so
|
|
it is not so simple ! */
|
|
/* XXX: if last char of region changes width, offset will move */
|
|
offset = min(s->offset, s->b->mark);
|
|
for (;;) {
|
|
if (offset >= max(s->offset, s->b->mark))
|
|
break;
|
|
if (eb_changecase(s->b, &offset, arg)) {
|
|
if (arg == 2)
|
|
arg = -arg;
|
|
} else {
|
|
if (arg == -2)
|
|
arg = -arg;
|
|
}
|
|
}
|
|
}
|
|
|
|
void do_delete_char(EditState *s, int argval)
|
|
{
|
|
int endpos;
|
|
|
|
if (s->b->flags & BF_READONLY)
|
|
return;
|
|
|
|
/* Delete hilighted region, if any.
|
|
* do_append_next_kill silently ignored.
|
|
*/
|
|
if (do_delete_selection(s))
|
|
return;
|
|
|
|
if (argval == NO_ARG) {
|
|
if (s->qe_state->last_cmd_func != (CmdFunc)do_append_next_kill) {
|
|
eb_delete_uchar(s->b, s->offset);
|
|
return;
|
|
}
|
|
argval = 1;
|
|
}
|
|
|
|
/* save kill if numeric argument given */
|
|
endpos = eb_skip_chars(s->b, s->offset, argval);
|
|
do_kill(s, s->offset, endpos, argval, 0);
|
|
}
|
|
|
|
void do_backspace(EditState *s, int argval)
|
|
{
|
|
int offset1;
|
|
|
|
#ifndef CONFIG_TINY
|
|
if (s->b->flags & BF_PREVIEW) {
|
|
do_scroll_up_down(s, -2);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (s->b->flags & BF_READONLY) {
|
|
/* CG: could scroll down */
|
|
return;
|
|
}
|
|
|
|
/* Delete hilighted region, if any.
|
|
* do_append_next_kill silently ignored.
|
|
*/
|
|
if (do_delete_selection(s))
|
|
return;
|
|
|
|
if (argval == NO_ARG) {
|
|
if (s->qe_state->last_cmd_func == (CmdFunc)do_tab
|
|
&& !s->indent_tabs_mode) {
|
|
/* Delete tab or indentation? */
|
|
do_undo(s);
|
|
return;
|
|
}
|
|
if (s->qe_state->last_cmd_func != (CmdFunc)do_append_next_kill) {
|
|
offset1 = eb_prev(s->b, s->offset);
|
|
if (offset1 < s->offset) {
|
|
eb_delete_range(s->b, offset1, s->offset);
|
|
/* special case for composing */
|
|
if (s->compose_len > 0)
|
|
s->compose_len--;
|
|
}
|
|
return;
|
|
}
|
|
argval = 1;
|
|
}
|
|
/* save kill if numeric argument given */
|
|
do_delete_char(s, -argval);
|
|
}
|
|
|
|
/* return the cursor position relative to the screen. Note that xc is
|
|
given in pixel coordinates */
|
|
typedef struct {
|
|
int linec;
|
|
int yc;
|
|
int xc;
|
|
int offsetc;
|
|
DirType basec; /* direction of the line */
|
|
DirType dirc; /* direction of the char under the cursor */
|
|
int cursor_width;
|
|
int cursor_height;
|
|
} CursorContext;
|
|
|
|
int cursor_func(DisplayState *ds,
|
|
int offset1, int offset2, int line_num,
|
|
int x, int y, int w, int h, qe__unused__ int hex_mode)
|
|
{
|
|
CursorContext *m = ds->cursor_opaque;
|
|
|
|
if (m->offsetc >= offset1 && m->offsetc < offset2) {
|
|
if (w <= 0) { /* for RTL glyphs */
|
|
x += w;
|
|
w = -w;
|
|
if (w == 0) {
|
|
/* for end of line */
|
|
w = ds->space_width;
|
|
}
|
|
}
|
|
m->xc = x;
|
|
m->yc = y;
|
|
m->basec = ds->base;
|
|
m->dirc = ds->base; /* XXX: do it */
|
|
m->cursor_width = w;
|
|
m->cursor_height = h;
|
|
m->linec = line_num;
|
|
#if 0
|
|
printf("cursor_func: xc=%d yc=%d linec=%d offset: %d<=%d<%d\n",
|
|
m->xc, m->yc, m->linec, offset1, m->offsetc, offset2);
|
|
#endif
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void get_cursor_pos(EditState *s, CursorContext *m)
|
|
{
|
|
DisplayState ds1, *ds = &ds1;
|
|
|
|
memset(m, 0, sizeof(*m));
|
|
m->offsetc = s->offset;
|
|
m->xc = m->yc = NO_CURSOR;
|
|
display_init(ds, s, DISP_CURSOR, cursor_func, m);
|
|
display1(ds);
|
|
display_close(ds);
|
|
}
|
|
|
|
typedef struct {
|
|
int yd;
|
|
int xd;
|
|
int xdmin;
|
|
int offsetd;
|
|
} MoveContext;
|
|
|
|
/* called each time the cursor could be displayed */
|
|
static int down_cursor_func(DisplayState *ds,
|
|
int offset1, qe__unused__ int offset2, int line_num,
|
|
int x, qe__unused__ int y,
|
|
qe__unused__ int w, qe__unused__ int h,
|
|
qe__unused__ int hex_mode)
|
|
{
|
|
int d;
|
|
MoveContext *m = ds->cursor_opaque;
|
|
|
|
if (line_num == m->yd) {
|
|
if (offset1 >= 0) {
|
|
if (w < 0) { /* for RTL glyphs */
|
|
x += w;
|
|
w = -w;
|
|
}
|
|
/* find the closest char */
|
|
d = abs(x - m->xd);
|
|
if (d < m->xdmin) {
|
|
m->xdmin = d;
|
|
m->offsetd = offset1;
|
|
}
|
|
}
|
|
return 0;
|
|
} else if (line_num > m->yd) {
|
|
/* no need to explore more chars */
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void do_up_down(EditState *s, int n)
|
|
{
|
|
int dir = n < 0 ? -1 : 1;
|
|
|
|
for (; n != 0; n -= dir) {
|
|
#ifndef CONFIG_TINY
|
|
if (s->b->flags & BF_PREVIEW) {
|
|
if (s->mode->scroll_up_down
|
|
&& (dir > 0 || s->offset_top > 0)
|
|
&& eb_at_bol(s->b, s->offset)) {
|
|
s->mode->scroll_up_down(s, dir);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
if (s->mode->move_up_down)
|
|
s->mode->move_up_down(s, dir);
|
|
}
|
|
}
|
|
|
|
void do_left_right(EditState *s, int n)
|
|
{
|
|
int dir = n < 0 ? -1 : 1;
|
|
|
|
for (; n != 0; n -= dir) {
|
|
#ifndef CONFIG_TINY
|
|
if (s->b->flags & BF_PREVIEW) {
|
|
EditState *e = find_window(s, KEY_LEFT, NULL);
|
|
if (e && (e->flags & WF_FILELIST)
|
|
&& s->qe_state->active_window == s
|
|
&& dir < 0 && eb_at_bol(s->b, s->offset)) {
|
|
s->qe_state->active_window = e;
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
if (s->mode->move_left_right)
|
|
s->mode->move_left_right(s, dir);
|
|
}
|
|
}
|
|
|
|
void text_move_up_down(EditState *s, int dir)
|
|
{
|
|
MoveContext m1, *m = &m1;
|
|
DisplayState ds1, *ds = &ds1;
|
|
CursorContext cm;
|
|
|
|
if (s->qe_state->last_cmd_func != (CmdFunc)do_up_down)
|
|
s->up_down_last_x = -1;
|
|
|
|
get_cursor_pos(s, &cm);
|
|
if (cm.xc == NO_CURSOR)
|
|
return;
|
|
|
|
if (s->up_down_last_x == -1)
|
|
s->up_down_last_x = cm.xc;
|
|
|
|
if (dir < 0) {
|
|
/* difficult case: we need to go backward on displayed text */
|
|
while (cm.linec <= 0) {
|
|
int offset_top = s->offset_top;
|
|
|
|
if (offset_top <= 0)
|
|
return;
|
|
|
|
offset_top = eb_prev(s->b, offset_top);
|
|
s->offset_top = s->mode->backward_offset(s, offset_top);
|
|
|
|
/* adjust y_disp so that the cursor is at the same position */
|
|
s->y_disp += cm.yc;
|
|
get_cursor_pos(s, &cm);
|
|
s->y_disp -= cm.yc;
|
|
}
|
|
}
|
|
|
|
/* find cursor offset */
|
|
m->yd = cm.linec + dir;
|
|
m->xd = s->up_down_last_x;
|
|
m->xdmin = 0x7fffffff;
|
|
/* if no cursor position is found, we go to bof or eof according
|
|
to dir */
|
|
if (dir > 0)
|
|
m->offsetd = s->b->total_size;
|
|
else
|
|
m->offsetd = 0;
|
|
display_init(ds, s, DISP_CURSOR, down_cursor_func, m);
|
|
display1(ds);
|
|
display_close(ds);
|
|
s->offset = m->offsetd;
|
|
}
|
|
|
|
typedef struct {
|
|
int y_found;
|
|
int offset_found;
|
|
int dir;
|
|
int offsetc;
|
|
} ScrollContext;
|
|
|
|
/* called each time the cursor could be displayed */
|
|
static int scroll_cursor_func(DisplayState *ds,
|
|
int offset1, int offset2,
|
|
qe__unused__ int line_num,
|
|
qe__unused__ int x, int y,
|
|
qe__unused__ int w, int h,
|
|
qe__unused__ int hex_mode)
|
|
{
|
|
ScrollContext *m = ds->cursor_opaque;
|
|
int y1;
|
|
|
|
y1 = y + h;
|
|
/* XXX: add bidir handling : position cursor on left / right */
|
|
if (m->dir < 0) {
|
|
if (y >= 0 && y < m->y_found) {
|
|
m->y_found = y;
|
|
m->offset_found = offset1;
|
|
}
|
|
} else {
|
|
if (y1 <= ds->height && y1 > m->y_found) {
|
|
m->y_found = y1;
|
|
m->offset_found = offset1;
|
|
}
|
|
}
|
|
/* XXX: should also track horizontal position? */
|
|
if (m->offsetc >= offset1 && m->offsetc < offset2 &&
|
|
y >= 0 && y1 <= ds->height) {
|
|
m->offset_found = m->offsetc;
|
|
m->y_found = 0x7fffffff * m->dir; /* ensure that no other
|
|
position will be found */
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void do_scroll_left_right(EditState *s, int n)
|
|
{
|
|
DisplayState ds1, *ds = &ds1;
|
|
int adjust;
|
|
|
|
if (s->wrap == WRAP_TERM)
|
|
return;
|
|
|
|
/* compute space_width */
|
|
display_init(ds, s, DISP_NONE, NULL, NULL);
|
|
adjust = n * ds->space_width;
|
|
display_close(ds);
|
|
|
|
if (n > 0) {
|
|
if (s->wrap == WRAP_TRUNCATE) {
|
|
if (s->x_disp[0] == 0) {
|
|
s->wrap = WRAP_LINE;
|
|
} else {
|
|
s->x_disp[0] = min(s->x_disp[0] + adjust, 0);
|
|
}
|
|
} else
|
|
if (s->wrap == WRAP_LINE || s->wrap == WRAP_AUTO) {
|
|
s->wrap = WRAP_WORD;
|
|
}
|
|
} else {
|
|
if (s->wrap == WRAP_WORD) {
|
|
s->wrap = WRAP_LINE;
|
|
} else
|
|
if (s->wrap == WRAP_LINE || s->wrap == WRAP_AUTO) {
|
|
s->wrap = WRAP_TRUNCATE;
|
|
} else {
|
|
s->x_disp[0] = min(s->x_disp[0] + adjust, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void do_scroll_up_down(EditState *s, int dir)
|
|
{
|
|
if (s->mode->scroll_up_down)
|
|
s->mode->scroll_up_down(s, dir);
|
|
}
|
|
|
|
void perform_scroll_up_down(EditState *s, int h)
|
|
{
|
|
ScrollContext m1, *m = &m1;
|
|
DisplayState ds1, *ds = &ds1;
|
|
int dir;
|
|
|
|
if (h < 0)
|
|
dir = -1;
|
|
else
|
|
dir = 1;
|
|
|
|
/* move display up/down */
|
|
s->y_disp -= h;
|
|
|
|
/* y_disp should not be > 0. So we update offset_top until we have
|
|
it negative */
|
|
if (s->y_disp > 0) {
|
|
display_init(ds, s, DISP_CURSOR_SCREEN, NULL, NULL);
|
|
while (s->y_disp > 0) {
|
|
if (s->offset_top <= 0) {
|
|
/* cannot go back: we stay at the top of the screen and
|
|
exit loop */
|
|
s->y_disp = 0;
|
|
} else {
|
|
int offset = eb_prev(s->b, s->offset_top);
|
|
s->offset_top = s->mode->backward_offset(s, offset);
|
|
ds->y = 0;
|
|
s->mode->display_line(s, ds, s->offset_top);
|
|
s->y_disp -= ds->y;
|
|
}
|
|
}
|
|
display_close(ds);
|
|
}
|
|
|
|
/* now update cursor position so that it is on screen */
|
|
m->offsetc = s->offset;
|
|
m->dir = -dir;
|
|
m->y_found = 0x7fffffff * dir;
|
|
m->offset_found = s->offset; /* default offset */
|
|
display_init(ds, s, DISP_CURSOR_SCREEN, scroll_cursor_func, m);
|
|
display1(ds);
|
|
display_close(ds);
|
|
|
|
s->offset = m->offset_found;
|
|
}
|
|
|
|
void text_scroll_up_down(EditState *s, int dir)
|
|
{
|
|
int h, line_height;
|
|
|
|
/* try to round to a line height */
|
|
line_height = get_line_height(s->screen, s, QE_STYLE_DEFAULT);
|
|
h = 1;
|
|
if (abs(dir) == 2) {
|
|
/* one page at a time: C-v / M-v */
|
|
dir /= 2;
|
|
h = (s->height / line_height) - 1;
|
|
if (h < 1)
|
|
h = 1;
|
|
}
|
|
h = h * line_height;
|
|
|
|
perform_scroll_up_down(s, dir * h);
|
|
}
|
|
|
|
/* center the cursor in the window */
|
|
/* XXX: make it generic to all modes */
|
|
void do_center_cursor(EditState *s, int force)
|
|
{
|
|
CursorContext cm;
|
|
|
|
/* only apply to text modes */
|
|
if (!s->mode->display_line)
|
|
return;
|
|
|
|
if (s->offset < s->offset_top
|
|
|| (s->offset_bottom >= 0 && s->offset >= s->offset_bottom)) {
|
|
/* if point is outside the current window, first move the
|
|
* window to start at the line with point. This significantly
|
|
* speeds up get_cursor_pos() on large files, except for the
|
|
* pathological case of huge lines.
|
|
*/
|
|
int offset = eb_prev(s->b, s->offset);
|
|
s->offset_top = s->mode->backward_offset(s, offset);
|
|
} else {
|
|
if (!force)
|
|
return;
|
|
}
|
|
|
|
get_cursor_pos(s, &cm);
|
|
if (cm.xc == NO_CURSOR)
|
|
return;
|
|
|
|
/* try to center display */
|
|
perform_scroll_up_down(s, -((s->height / 2) - cm.yc));
|
|
}
|
|
|
|
/* called each time the cursor could be displayed */
|
|
typedef struct {
|
|
int yd;
|
|
int xd;
|
|
int xdmin;
|
|
int offsetd;
|
|
int dir;
|
|
int after_found;
|
|
} LeftRightMoveContext;
|
|
|
|
static int left_right_cursor_func(DisplayState *ds,
|
|
int offset1, qe__unused__ int offset2,
|
|
int line_num,
|
|
int x, qe__unused__ int y,
|
|
qe__unused__ int w, qe__unused__ int h,
|
|
qe__unused__ int hex_mode)
|
|
{
|
|
int d;
|
|
LeftRightMoveContext *m = ds->cursor_opaque;
|
|
|
|
if (w < 0) { /* for RTL glyphs */
|
|
x += w;
|
|
w = -w;
|
|
}
|
|
if (line_num == m->yd &&
|
|
((m->dir < 0 && x < m->xd) ||
|
|
(m->dir > 0 && x > m->xd))) {
|
|
/* find the closest char in the correct direction */
|
|
d = abs(x - m->xd);
|
|
if (d < m->xdmin) {
|
|
m->xdmin = d;
|
|
m->offsetd = offset1;
|
|
}
|
|
return 0;
|
|
} else if (line_num > m->yd) {
|
|
m->after_found = 1;
|
|
/* no need to explore more chars */
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* go to left or right in visual order */
|
|
void text_move_left_right_visual(EditState *s, int dir)
|
|
{
|
|
LeftRightMoveContext m1, *m = &m1;
|
|
DisplayState ds1, *ds = &ds1;
|
|
int xc, yc, nextline;
|
|
CursorContext cm;
|
|
|
|
get_cursor_pos(s, &cm);
|
|
xc = cm.xc;
|
|
yc = cm.linec;
|
|
|
|
nextline = 0;
|
|
for (;;) {
|
|
/* find cursor offset */
|
|
m->yd = yc;
|
|
if (!nextline) {
|
|
m->xd = xc;
|
|
} else {
|
|
m->xd = -dir * 0x3fffffff; /* not too big to avoid overflow */
|
|
}
|
|
m->xdmin = 0x7fffffff;
|
|
m->offsetd = -1;
|
|
m->dir = dir;
|
|
m->after_found = 0;
|
|
display_init(ds, s, DISP_CURSOR, left_right_cursor_func, m);
|
|
display1(ds);
|
|
display_close(ds);
|
|
if (m->offsetd >= 0) {
|
|
/* position found : update and exit */
|
|
s->offset = m->offsetd;
|
|
break;
|
|
} else {
|
|
if (dir > 0) {
|
|
/* no suitable position found: go to next line */
|
|
/* if no char after, no need to continue */
|
|
if (!m->after_found)
|
|
break;
|
|
} else {
|
|
/* no suitable position found: go to previous line */
|
|
if (yc <= 0) {
|
|
int offset = s->offset_top;
|
|
|
|
if (offset <= 0)
|
|
break;
|
|
offset = eb_prev(s->b, offset);
|
|
s->offset_top = s->mode->backward_offset(s, offset);
|
|
/* adjust y_disp so that the cursor is at the same position */
|
|
s->y_disp += cm.yc;
|
|
get_cursor_pos(s, &cm);
|
|
s->y_disp -= cm.yc;
|
|
yc = cm.linec;
|
|
}
|
|
}
|
|
yc += dir;
|
|
nextline = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* mouse get cursor func */
|
|
#ifndef CONFIG_TINY
|
|
|
|
/* called each time the cursor could be displayed */
|
|
typedef struct {
|
|
int yd;
|
|
int xd;
|
|
int dy_min;
|
|
int dx_min;
|
|
int offset_found;
|
|
int hex_mode;
|
|
} MouseGotoContext;
|
|
|
|
/* distance from x to segment [x1,x2-1] */
|
|
static int seg_dist(int x, int x1, int x2)
|
|
{
|
|
if (x <= x1)
|
|
return x1 - x;
|
|
else if (x >= x2)
|
|
return x - x2 + 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* XXX: would need two passes in the general case (first search line,
|
|
then colunm */
|
|
static int mouse_goto_func(DisplayState *ds,
|
|
int offset1, qe__unused__ int offset2,
|
|
qe__unused__ int line_num,
|
|
int x, int y, int w, int h, int hex_mode)
|
|
{
|
|
MouseGotoContext *m = ds->cursor_opaque;
|
|
int dy, dx;
|
|
|
|
dy = seg_dist(m->yd, y, y + h);
|
|
if (dy < m->dy_min) {
|
|
m->dy_min = dy;
|
|
m->dx_min = 0x3fffffff;
|
|
}
|
|
if (dy == m->dy_min) {
|
|
dx = seg_dist(m->xd, x, x + w);
|
|
if (dx < m->dx_min) {
|
|
m->dx_min = dx;
|
|
m->offset_found = offset1;
|
|
m->hex_mode = hex_mode;
|
|
/* fast exit test */
|
|
if (dy == 0 && dx == 0)
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* go to left or right in visual order. In hex mode, a side effect is
|
|
to select the right column. */
|
|
void text_mouse_goto(EditState *s, int x, int y)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
MouseGotoContext m1, *m = &m1;
|
|
DisplayState ds1, *ds = &ds1;
|
|
|
|
m->dx_min = 0x3fffffff;
|
|
m->dy_min = 0x3fffffff;
|
|
m->xd = x;
|
|
m->yd = y;
|
|
m->offset_found = s->offset; /* fail safe */
|
|
m->hex_mode = s->hex_mode;
|
|
|
|
display_init(ds, s, DISP_CURSOR_SCREEN, mouse_goto_func, m);
|
|
ds->hex_mode = -1; /* we select both hex chars and normal chars */
|
|
display1(ds);
|
|
display_close(ds);
|
|
|
|
s->offset = m->offset_found;
|
|
s->hex_mode = m->hex_mode;
|
|
|
|
/* activate window (need more ideas for popups) */
|
|
if (!(s->flags & WF_POPUP))
|
|
qs->active_window = s;
|
|
if (s->mouse_force_highlight)
|
|
s->force_highlight = 1;
|
|
}
|
|
#else
|
|
void text_mouse_goto(EditState *s, int x, int y)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
int do_delete_selection(EditState *s)
|
|
{
|
|
int res = 0;
|
|
|
|
if (s->region_style && s->b->mark != s->offset) {
|
|
/* Delete hilighted region */
|
|
// XXX: make it optional?
|
|
res = eb_delete_range(s->b, s->b->mark, s->offset);
|
|
}
|
|
/* deactivate region hilite */
|
|
s->region_style = 0;
|
|
|
|
return res;
|
|
}
|
|
|
|
void do_char(EditState *s, int key, int argval)
|
|
{
|
|
#ifndef CONFIG_TINY
|
|
if (s->b->flags & BF_PREVIEW) {
|
|
if (key == KEY_SPC) {
|
|
do_scroll_up_down(s, 2);
|
|
return;
|
|
}
|
|
do_preview_mode(s, 0);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (s->b->flags & BF_READONLY)
|
|
return;
|
|
|
|
/* Delete hilighted region */
|
|
do_delete_selection(s);
|
|
|
|
for (;;) {
|
|
if (s->mode->write_char)
|
|
s->mode->write_char(s, key);
|
|
if (argval-- <= 1)
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_UNICODE_JOIN
|
|
void do_combine_accent(EditState *s, int accent)
|
|
{
|
|
int offset0, len, c;
|
|
unsigned int g[2];
|
|
char buf[MAX_CHAR_BYTES];
|
|
|
|
if (s->b->flags & BF_READONLY)
|
|
return;
|
|
|
|
c = eb_prevc(s->b, s->offset, &offset0);
|
|
if (c == accent) {
|
|
eb_delete_range(s->b, offset0, s->offset);
|
|
} else
|
|
if (((expand_ligature(g, c) && g[1] == (unsigned int)accent)
|
|
|| (c != '\n' && combine_accent(g, c, accent)))
|
|
&& (len = eb_encode_uchar(s->b, buf, g[0])) > 0) {
|
|
/* XXX: should bypass eb_encode_uchar to detect encoding failure */
|
|
eb_replace(s->b, offset0, s->offset - offset0, buf, len);
|
|
s->offset = offset0 + len;
|
|
} else {
|
|
do_char(s, accent, 1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void text_write_char(EditState *s, int key)
|
|
{
|
|
int cur_ch, len, cur_len, offset1, ret, insert;
|
|
char buf[MAX_CHAR_BYTES];
|
|
|
|
if (check_read_only(s))
|
|
return;
|
|
|
|
/* XXX: Should delete hilighted region */
|
|
|
|
/* deactivate region hilite */
|
|
s->region_style = 0;
|
|
|
|
cur_ch = eb_nextc(s->b, s->offset, &offset1);
|
|
cur_len = offset1 - s->offset;
|
|
len = eb_encode_uchar(s->b, buf, key);
|
|
insert = (s->insert || cur_ch == '\n');
|
|
|
|
if (insert) {
|
|
const InputMethod *m;
|
|
int match_buf[20], match_len, offset, i;
|
|
|
|
/* use compose system only if insert mode */
|
|
if (s->compose_len == 0)
|
|
s->compose_start_offset = s->offset;
|
|
|
|
/* break sequence of insertions */
|
|
if (key == '\n' || (key != ' ' && s->b->last_log_char == ' ')) {
|
|
s->b->last_log = LOGOP_FREE;
|
|
}
|
|
s->b->last_log_char = key;
|
|
|
|
/* insert char */
|
|
eb_insert(s->b, s->offset, buf, len);
|
|
s->offset += len;
|
|
|
|
s->compose_buf[s->compose_len++] = key;
|
|
m = s->input_method;
|
|
for (;;) {
|
|
if (!m) {
|
|
s->compose_len = 0;
|
|
break;
|
|
}
|
|
ret = m->input_match(match_buf, countof(match_buf),
|
|
&match_len, m->data, s->compose_buf,
|
|
s->compose_len);
|
|
if (ret == INPUTMETHOD_NOMATCH) {
|
|
/* no match : reset compose state */
|
|
s->compose_len = 0;
|
|
break;
|
|
} else
|
|
if (ret == INPUTMETHOD_MORECHARS) {
|
|
/* more chars expected: do nothing and insert current key */
|
|
break;
|
|
} else {
|
|
/* match: delete matched chars */
|
|
offset = eb_skip_chars(s->b, s->compose_start_offset, match_len);
|
|
eb_delete_range(s->b, s->compose_start_offset, offset);
|
|
s->compose_len -= match_len;
|
|
umemmove(s->compose_buf, s->compose_buf + match_len,
|
|
s->compose_len);
|
|
/* then insert match */
|
|
for (i = 0; i < ret; i++) {
|
|
key = match_buf[i];
|
|
len = eb_encode_uchar(s->b, buf, key);
|
|
eb_insert(s->b, s->compose_start_offset, buf, len);
|
|
s->compose_start_offset += len;
|
|
/* should only bump s->offset if at insert point */
|
|
s->offset += len;
|
|
}
|
|
/* if some compose chars are left, we iterate */
|
|
if (s->compose_len == 0)
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
eb_replace(s->b, s->offset, cur_len, buf, len);
|
|
/* adjust offset because in inserting at point */
|
|
s->offset += len;
|
|
}
|
|
}
|
|
|
|
struct QuoteKeyArgument {
|
|
EditState *s;
|
|
int argval;
|
|
};
|
|
|
|
/* XXX: may be better to move it into qe_key_process() */
|
|
static void quote_key(void *opaque, int key)
|
|
{
|
|
struct QuoteKeyArgument *qa = opaque;
|
|
EditState *s = qa->s;
|
|
|
|
put_status(s, ""); /* erase "Quote: " message */
|
|
|
|
if (!s)
|
|
return;
|
|
|
|
/* CG: why not insert special keys as well? */
|
|
if (!KEY_IS_SPECIAL(key)) {
|
|
do_char(s, key, qa->argval);
|
|
edit_display(s->qe_state);
|
|
dpy_flush(&global_screen);
|
|
}
|
|
qe_ungrab_keys();
|
|
}
|
|
|
|
void do_quoted_insert(EditState *s, int argval)
|
|
{
|
|
struct QuoteKeyArgument *qa = qe_mallocz(struct QuoteKeyArgument);
|
|
|
|
qa->s = s;
|
|
qa->argval = argval;
|
|
|
|
qe_grab_keys(quote_key, qa);
|
|
put_status(s, "Quote: ");
|
|
}
|
|
|
|
void do_overwrite_mode(EditState *s, int argval)
|
|
{
|
|
if (argval == NO_ARG)
|
|
s->insert = !s->insert;
|
|
else
|
|
s->insert = !(argval > 0);
|
|
}
|
|
|
|
void do_tab(EditState *s, int argval)
|
|
{
|
|
/* CG: should do smart complete, smart indent, insert tab */
|
|
if (s->indent_tabs_mode) {
|
|
do_char(s, 9, argval);
|
|
} else {
|
|
int offset = s->offset;
|
|
int offset0 = eb_goto_bol(s->b, offset);
|
|
int col = 0;
|
|
int tw = s->b->tab_width > 0 ? s->b->tab_width : DEFAULT_TAB_WIDTH;
|
|
int indent = s->indent_size > 0 ? s->indent_size : tw;
|
|
|
|
while (offset0 < offset) {
|
|
int c = eb_nextc(s->b, offset0, &offset0);
|
|
if (c == '\t') {
|
|
col += tw - col % tw;
|
|
} else {
|
|
col += unicode_tty_glyph_width(c);
|
|
}
|
|
}
|
|
if (argval == NO_ARG)
|
|
argval = 1;
|
|
|
|
s->offset += eb_insert_spaces(s->b, s->offset,
|
|
indent * argval - (col % indent));
|
|
}
|
|
}
|
|
|
|
#ifndef CONFIG_TINY
|
|
void do_preview_mode(EditState *s, int set)
|
|
{
|
|
const char *state = NULL;
|
|
|
|
if (set < 0 && (s->b->flags & BF_PREVIEW)) {
|
|
s->b->flags &= ~BF_PREVIEW;
|
|
state = "exited";
|
|
} else
|
|
if (set > 0 && !(s->b->flags & BF_PREVIEW)) {
|
|
s->b->flags |= BF_PREVIEW;
|
|
state = "started";
|
|
} else
|
|
if (set == 0) {
|
|
state = (s->b->flags & BF_PREVIEW) ? "active" : "inactive";
|
|
}
|
|
if (state)
|
|
put_status(s, "Preview mode %s", state);
|
|
}
|
|
#endif
|
|
|
|
void do_newline(EditState *s)
|
|
{
|
|
#ifndef CONFIG_TINY
|
|
if (s->b->flags & BF_PREVIEW) {
|
|
do_preview_mode(s, -1);
|
|
return;
|
|
}
|
|
#endif
|
|
if (s->b->flags & BF_READONLY)
|
|
return;
|
|
|
|
/* CG: in overwrite mode, should just go to beginning of next line */
|
|
s->offset += eb_insert_uchar(s->b, s->offset, '\n');
|
|
}
|
|
|
|
void do_open_line(EditState *s)
|
|
{
|
|
if (s->b->flags & (BF_PREVIEW | BF_READONLY))
|
|
return;
|
|
|
|
/* XXX: should preserve s->offset */
|
|
eb_insert_uchar(s->b, s->offset, '\n');
|
|
}
|
|
|
|
#if 0
|
|
void do_space(EditState *s, int key, int argval)
|
|
{
|
|
if (s->b->flags & BF_READONLY) {
|
|
do_scroll_up_down(s, 1, argval);
|
|
return;
|
|
}
|
|
do_char(s, key, argval);
|
|
}
|
|
#endif
|
|
|
|
void do_keyboard_quit(EditState *s)
|
|
{
|
|
#ifndef CONFIG_TINY
|
|
if (s->b->flags & BF_PREVIEW) {
|
|
do_preview_mode(s, -1);
|
|
return;
|
|
}
|
|
#endif
|
|
/* deactivate region hilite */
|
|
s->region_style = 0;
|
|
/* deactivate search hilite */
|
|
s->isearch_state = NULL;
|
|
|
|
/* well, currently nothing needs to be aborted in global context */
|
|
/* CG: Should remove popups, sidepanes, helppanes... */
|
|
put_status(s, "|");
|
|
put_status(s, "Quit");
|
|
}
|
|
|
|
/* block functions */
|
|
void do_set_mark(EditState *s)
|
|
{
|
|
do_mark_region(s, s->offset, s->offset);
|
|
put_status(s, "Mark set");
|
|
}
|
|
|
|
void do_mark_whole_buffer(EditState *s)
|
|
{
|
|
do_mark_region(s, s->b->total_size, 0);
|
|
}
|
|
|
|
EditBuffer *new_yank_buffer(QEmacsState *qs, EditBuffer *base)
|
|
{
|
|
char bufname[32];
|
|
EditBuffer *b;
|
|
int cur = qs->yank_current;
|
|
|
|
if (qs->yank_buffers[cur]) {
|
|
cur = (cur + 1) % NB_YANK_BUFFERS;
|
|
qs->yank_current = cur;
|
|
/* Maybe should instead just clear the buffer and reset styles */
|
|
qe_kill_buffer(qs->yank_buffers[cur]);
|
|
qs->yank_buffers[cur] = NULL;
|
|
}
|
|
snprintf(bufname, sizeof(bufname), "*kill-%d*", cur + 1);
|
|
if (base) {
|
|
b = eb_new(bufname, BF_SYSTEM | (base->flags & BF_STYLES));
|
|
eb_set_charset(b, base->charset, base->eol_type);
|
|
} else {
|
|
b = eb_new(bufname, 0);
|
|
}
|
|
qs->yank_buffers[cur] = b;
|
|
return b;
|
|
}
|
|
|
|
void do_append_next_kill(qe__unused__ EditState *s)
|
|
{
|
|
/* do nothing! */
|
|
}
|
|
|
|
void do_kill(EditState *s, int p1, int p2, int dir, int keep)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
int len, tmp;
|
|
EditBuffer *b;
|
|
|
|
/* deactivate region hilite */
|
|
s->region_style = 0;
|
|
|
|
if (p1 > p2) {
|
|
tmp = p1;
|
|
p1 = p2;
|
|
p2 = tmp;
|
|
}
|
|
len = p2 - p1;
|
|
b = qs->yank_buffers[qs->yank_current];
|
|
if (!b || !dir || qs->last_cmd_func != (CmdFunc)do_append_next_kill) {
|
|
/* append kill if last command was kill already */
|
|
b = new_yank_buffer(qs, s->b);
|
|
}
|
|
/* insert at beginning or end depending on kill direction */
|
|
eb_insert_buffer_convert(b, dir < 0 ? 0 : b->total_size, s->b, p1, len);
|
|
if (keep) {
|
|
/* no message */
|
|
} else
|
|
if (!(s->b->flags & BF_READONLY)) {
|
|
if (s->mode->delete_bytes) {
|
|
s->mode->delete_bytes(s, p1, len);
|
|
} else {
|
|
eb_delete(s->b, p1, len);
|
|
}
|
|
s->offset = p1;
|
|
} else {
|
|
put_status(s, "Region copied");
|
|
}
|
|
if (dir) {
|
|
qs->this_cmd_func = (CmdFunc)do_append_next_kill;
|
|
}
|
|
selection_activate(qs->screen);
|
|
}
|
|
|
|
void do_kill_region(EditState *s, int keep)
|
|
{
|
|
do_kill(s, s->b->mark, s->offset, 0, keep);
|
|
}
|
|
|
|
void do_kill_line(EditState *s, int argval)
|
|
{
|
|
int p1, p2, offset1, dir = 1;
|
|
|
|
p1 = s->offset;
|
|
if (argval == NO_ARG) {
|
|
if (s->region_style && s->b->mark != s->offset) {
|
|
/* kill highlighted region */
|
|
p1 = s->b->mark;
|
|
p2 = s->offset;
|
|
} else
|
|
if (eb_nextc(s->b, p1, &offset1) == '\n') {
|
|
/* kill end of line marker */
|
|
p2 = s->offset = offset1;
|
|
} else {
|
|
/* kill to end of line */
|
|
do_eol(s);
|
|
p2 = s->offset;
|
|
}
|
|
} else
|
|
if (argval <= 0) {
|
|
/* kill backwards */
|
|
dir = -1;
|
|
for (;;) {
|
|
do_bol(s);
|
|
p2 = s->offset;
|
|
if (p2 <= 0 || argval == 0)
|
|
break;
|
|
p2 = eb_prev(s->b, p2);
|
|
s->offset = p2;
|
|
argval += 1;
|
|
}
|
|
} else {
|
|
for (;;) {
|
|
do_eol(s);
|
|
p2 = s->offset;
|
|
if (p2 >= s->b->total_size || argval == 0)
|
|
break;
|
|
p2 = eb_next(s->b, p2);
|
|
s->offset = p2;
|
|
argval -= 1;
|
|
}
|
|
}
|
|
do_kill(s, p1, p2, dir, 0);
|
|
}
|
|
|
|
void do_kill_beginning_of_line(EditState *s, int argval)
|
|
{
|
|
do_kill_line(s, argval == NO_ARG ? 0 : -argval);
|
|
}
|
|
|
|
void do_kill_word(EditState *s, int n)
|
|
{
|
|
int start = s->offset;
|
|
|
|
if (n != 0) {
|
|
do_word_left_right(s, n);
|
|
do_kill(s, start, s->offset, n, 0);
|
|
}
|
|
}
|
|
|
|
void do_yank(EditState *s)
|
|
{
|
|
int size;
|
|
QEmacsState *qs = s->qe_state;
|
|
EditBuffer *b;
|
|
|
|
if (s->b->flags & BF_READONLY)
|
|
return;
|
|
|
|
/* First delete any highlighted range */
|
|
do_delete_selection(s);
|
|
|
|
/* if the GUI selection is used, it will be handled in the GUI code */
|
|
selection_request(qs->screen);
|
|
|
|
s->b->mark = s->offset;
|
|
b = qs->yank_buffers[qs->yank_current];
|
|
if (b) {
|
|
size = b->total_size;
|
|
if (size > 0) {
|
|
s->b->last_log = LOGOP_FREE;
|
|
s->offset += eb_insert_buffer_convert(s->b, s->offset, b, 0, size);
|
|
}
|
|
}
|
|
qs->this_cmd_func = (CmdFunc)do_yank;
|
|
}
|
|
|
|
void do_yank_pop(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
|
|
if (qs->last_cmd_func != (CmdFunc)do_yank) {
|
|
put_status(s, "Previous command was not a yank");
|
|
return;
|
|
}
|
|
|
|
eb_delete_range(s->b, s->b->mark, s->offset);
|
|
|
|
if (--qs->yank_current < 0) {
|
|
/* get last yank buffer, yank ring may not be full */
|
|
qs->yank_current = NB_YANK_BUFFERS;
|
|
while (--qs->yank_current && !qs->yank_buffers[qs->yank_current])
|
|
continue;
|
|
}
|
|
do_yank(s);
|
|
}
|
|
|
|
void do_exchange_point_and_mark(EditState *s)
|
|
{
|
|
int tmp;
|
|
|
|
tmp = s->b->mark;
|
|
s->b->mark = s->offset;
|
|
s->offset = tmp;
|
|
}
|
|
|
|
static int reload_buffer(EditState *s, EditBuffer *b)
|
|
{
|
|
FILE *f, *f1 = NULL;
|
|
int ret, saved;
|
|
|
|
/* if no file associated, cannot do anything */
|
|
if (b->filename[0] == '\0')
|
|
return 0;
|
|
|
|
if (!f1 && b->data_type == &raw_data_type) {
|
|
struct stat st;
|
|
|
|
if (stat(b->filename, &st) < 0 || !S_ISREG(st.st_mode))
|
|
return -1;
|
|
|
|
f = fopen(b->filename, "r");
|
|
if (!f) {
|
|
goto fail;
|
|
}
|
|
} else {
|
|
f = f1;
|
|
}
|
|
/* XXX: the log buffer is inappropriate if the file was modified on
|
|
* disk. If this is a reload operation, should create a log for
|
|
* clearing the buffer and another one for loading it. So the
|
|
* operation can be undone.
|
|
*/
|
|
saved = b->save_log;
|
|
b->save_log = 0;
|
|
if (b->data_type->buffer_load)
|
|
ret = b->data_type->buffer_load(b, f);
|
|
else
|
|
ret = -1;
|
|
|
|
b->modified = 0;
|
|
b->save_log = saved;
|
|
|
|
if (!f1 && f)
|
|
fclose(f);
|
|
|
|
if (ret < 0) {
|
|
fail:
|
|
if (!f1) {
|
|
put_status(s, "Could not load '%s'", b->filename);
|
|
} else {
|
|
put_status(s, "Error while reloading '%s'", b->filename);
|
|
}
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
QEModeData *qe_create_buffer_mode_data(EditBuffer *b, ModeDef *m)
|
|
{
|
|
QEModeData *md = NULL;
|
|
int size = m->buffer_instance_size - sizeof(QEModeData);
|
|
|
|
if (size >= 0) {
|
|
md = qe_mallocz_hack(QEModeData, size);
|
|
if (md) {
|
|
md->mode = m;
|
|
md->b = b;
|
|
md->next = b->mode_data_list;
|
|
b->mode_data_list = md;
|
|
}
|
|
if (!b->default_mode) {
|
|
b->default_mode = m;
|
|
}
|
|
}
|
|
return md;
|
|
}
|
|
|
|
void *qe_get_buffer_mode_data(EditBuffer *b, ModeDef *m, EditState *e)
|
|
{
|
|
if (b) {
|
|
QEModeData *md;
|
|
for (md = b->mode_data_list; md; md = md->next) {
|
|
if (md->mode == m)
|
|
return md;
|
|
}
|
|
}
|
|
if (e)
|
|
put_status(e, "Not a %s buffer", m->name);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
QEModeData *qe_create_window_mode_data(EditState *s, ModeDef *m)
|
|
{
|
|
QEModeData *md = NULL;
|
|
int size = m->window_instance_size - sizeof(QEModeData);
|
|
|
|
if (!s->mode_data && size >= 0) {
|
|
md = qe_mallocz_hack(QEModeData, size);
|
|
if (md) {
|
|
md->mode = m;
|
|
md->s = s;
|
|
s->mode_data = md;
|
|
}
|
|
}
|
|
return md;
|
|
}
|
|
|
|
void *qe_get_window_mode_data(EditState *e, ModeDef *m, int status)
|
|
{
|
|
if (e) {
|
|
QEModeData *md = e->mode_data;
|
|
if (md && md->mode == m)
|
|
return md;
|
|
}
|
|
if (status)
|
|
put_status(e, "Not a %s buffer", m->name);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void *check_mode_data(void **pp) {
|
|
QEmacsState *qs = &qe_state;
|
|
QEModeData *md = *pp;
|
|
EditBuffer *b;
|
|
EditState *e;
|
|
|
|
for (b = qs->first_buffer; b != NULL; b = b->next) {
|
|
QEModeData **mdp;
|
|
for (mdp = &b->mode_data_list; *mdp; mdp = &(*mdp)->next) {
|
|
if (*mdp == md)
|
|
return md;
|
|
}
|
|
}
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (e->mode_data == md)
|
|
return md;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int qe_free_mode_data(QEModeData *md)
|
|
{
|
|
int rc = -1;
|
|
|
|
if (!md)
|
|
return 0;
|
|
|
|
if (check_buffer(&md->b)) {
|
|
EditBuffer *b = md->b;
|
|
QEModeData **mdp;
|
|
for (mdp = &b->mode_data_list; *mdp; mdp = &(*mdp)->next) {
|
|
if (*mdp == md) {
|
|
/* unlink before calling destructor */
|
|
*mdp = md->next;
|
|
if (md->mode->mode_free)
|
|
md->mode->mode_free(b, md);
|
|
rc = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (check_window(&md->s)) {
|
|
if (md->s->mode_data == md) {
|
|
md->s->mode_data = NULL;
|
|
rc = 0;
|
|
}
|
|
}
|
|
if (rc == 0) {
|
|
/* mode data was found, OK to free */
|
|
qe_free(&md);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int edit_set_mode(EditState *s, ModeDef *m)
|
|
{
|
|
int mode_flags = 0;
|
|
EditBuffer *b = s->b;
|
|
const char *errstr = NULL;
|
|
int rc = 0;
|
|
|
|
/* if a mode is already defined, try to close it */
|
|
if (s->mode) {
|
|
/* save mode data if necessary */
|
|
s->interactive = 0;
|
|
if (s->mode->mode_close)
|
|
s->mode->mode_close(s);
|
|
generic_mode_close(s);
|
|
qe_free_mode_data(s->mode_data);
|
|
s->mode = NULL; /* XXX: should instead use fundamental_mode */
|
|
set_colorize_func(s, NULL, NULL);
|
|
|
|
/* XXX: this code makes no sense, if must be reworked! */
|
|
#if 0
|
|
int data_count;
|
|
EditState *e;
|
|
|
|
/* try to remove the raw or mode specific data if it is no
|
|
longer used. */
|
|
data_count = 0;
|
|
for (e = s->qe_state->first_window; e != NULL; e = e->next_window) {
|
|
if (e != s && e->b == b) {
|
|
if (e->mode && e->mode->data_type != &raw_data_type)
|
|
data_count++;
|
|
}
|
|
}
|
|
/* we try to remove mode specific data if it is redundant with
|
|
the buffer raw data */
|
|
if (data_count == 0 && !b->modified) {
|
|
/* close mode specific buffer representation because it is
|
|
always redundant if it was not modified */
|
|
/* XXX: move this to reset buffer data: eb_free or changing
|
|
* data_type */
|
|
if (b->data_type != &raw_data_type) {
|
|
b->data_type->buffer_close(b);
|
|
b->data_data = NULL;
|
|
b->data_type = &raw_data_type;
|
|
eb_delete(b, 0, b->total_size);
|
|
b->modified = 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* if a new mode is wanted, open it */
|
|
if (m) {
|
|
s->mode_data = NULL;
|
|
if (m->buffer_instance_size > 0) {
|
|
if (!qe_get_buffer_mode_data(b, m, NULL)) {
|
|
if (qe_create_buffer_mode_data(b, m)) {
|
|
mode_flags = MODEF_NEWINSTANCE;
|
|
} else {
|
|
errstr = "Cannot allocate buffer mode data";
|
|
}
|
|
}
|
|
}
|
|
if (m->window_instance_size > 0) {
|
|
if (!qe_create_window_mode_data(s, m)) {
|
|
/* safe fall back: use text mode */
|
|
errstr = "Cannot allocate window mode data";
|
|
}
|
|
}
|
|
if (m->data_type != &raw_data_type) {
|
|
/* if a non raw data type is requested, we see if we can use it */
|
|
if (b->data_type == &raw_data_type) {
|
|
/* non raw data type: we must call a mode specific
|
|
load method */
|
|
s->mode = m;
|
|
b->data_type = m->data_type;
|
|
b->data_type_name = m->data_type->name;
|
|
if (reload_buffer(s, b) < 0) {
|
|
b->data_type = &raw_data_type;
|
|
b->data_type_name = NULL;
|
|
errstr = "Cannot reload buffer";
|
|
}
|
|
} else
|
|
if (b->data_type != m->data_type) {
|
|
/* non raw data type requested, but the the buffer has
|
|
a different type: we cannot switch mode, so we fall
|
|
back to text */
|
|
errstr = "incompatible data type";
|
|
} else {
|
|
/* same data type: nothing more to do */
|
|
}
|
|
} else {
|
|
/* if raw data and nothing loaded, we try to load */
|
|
if (b->total_size == 0 && !b->modified)
|
|
reload_buffer(s, b);
|
|
}
|
|
if (errstr) {
|
|
put_status(s, "Cannot set mode %s: %s", m->name, errstr);
|
|
m = &text_mode;
|
|
rc = -1;
|
|
}
|
|
|
|
s->mode = m;
|
|
|
|
/* init mode */
|
|
generic_mode_init(s);
|
|
s->wrap = m->default_wrap;
|
|
m->mode_init(s, s->b, MODEF_VIEW | mode_flags);
|
|
if (m->colorize_func)
|
|
set_colorize_func(s, m->colorize_func, m);
|
|
/* modify offset_top so that its value is correct */
|
|
if (s->mode->backward_offset)
|
|
s->offset_top = s->mode->backward_offset(s, s->offset_top);
|
|
/* keep saved data in sync with last mode used for buffer */
|
|
generic_save_window_data(s);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
void do_set_mode(EditState *s, const char *name)
|
|
{
|
|
ModeDef *m;
|
|
|
|
/* set-mode from the dired window applies to the target window */
|
|
s = qe_find_target_window(s, 0);
|
|
|
|
/* XXX: should check if mode is appropriate */
|
|
m = qe_find_mode(name, 0);
|
|
if (m)
|
|
edit_set_mode(s, m);
|
|
else
|
|
put_status(s, "No mode %s", name);
|
|
}
|
|
|
|
QECharset *read_charset(EditState *s, const char *charset_str,
|
|
EOLType *eol_typep)
|
|
{
|
|
char buf[64];
|
|
const char *p;
|
|
QECharset *charset;
|
|
EOLType eol_type = *eol_typep;
|
|
|
|
p = NULL;
|
|
|
|
if (strend(charset_str, "-mac", &p))
|
|
eol_type = EOL_MAC;
|
|
else
|
|
if (strend(charset_str, "-dos", &p))
|
|
eol_type = EOL_DOS;
|
|
else
|
|
if (strend(charset_str, "-unix", &p))
|
|
eol_type = EOL_UNIX;
|
|
|
|
if (p) {
|
|
pstrncpy(buf, sizeof(buf), charset_str, p - charset_str);
|
|
charset_str = buf;
|
|
}
|
|
|
|
charset = find_charset(charset_str);
|
|
if (!charset) {
|
|
put_status(s, "Unknown charset '%s'", charset_str);
|
|
return NULL;
|
|
}
|
|
*eol_typep = eol_type;
|
|
return charset;
|
|
}
|
|
|
|
void do_show_coding_system(EditState *s)
|
|
{
|
|
put_status(s, "Buffer charset is now %s%s", s->b->charset->name,
|
|
s->b->eol_type == EOL_DOS ? "-dos" :
|
|
s->b->eol_type == EOL_MAC ? "-mac" : "-unix");
|
|
}
|
|
|
|
void do_set_auto_coding(EditState *s, int verbose)
|
|
{
|
|
u8 buf[4096];
|
|
int buf_size;
|
|
EditBuffer *b = s->b;
|
|
EOLType eol_type = b->eol_type;
|
|
QECharset *charset;
|
|
|
|
buf_size = eb_read(b, 0, buf, sizeof(buf));
|
|
eol_type = b->eol_type;
|
|
/* XXX: detect_charset returns a default charset */
|
|
/* XXX: should enforce 32 bit alignment of buf */
|
|
charset = detect_charset(buf, buf_size, &eol_type);
|
|
eb_set_charset(b, charset, eol_type);
|
|
if (verbose) {
|
|
do_show_coding_system(s);
|
|
}
|
|
}
|
|
|
|
void do_set_buffer_file_coding_system(EditState *s, const char *charset_str)
|
|
{
|
|
QECharset *charset;
|
|
EOLType eol_type;
|
|
|
|
eol_type = s->b->eol_type;
|
|
charset = read_charset(s, charset_str, &eol_type);
|
|
if (!charset)
|
|
return;
|
|
eb_set_charset(s->b, charset, eol_type);
|
|
do_show_coding_system(s);
|
|
}
|
|
|
|
/* convert the charset of a buffer to another charset */
|
|
void do_convert_buffer_file_coding_system(EditState *s,
|
|
const char *charset_str)
|
|
{
|
|
QECharset *charset;
|
|
EOLType eol_type;
|
|
EditBuffer *b1, *b;
|
|
int offset, len, i;
|
|
EditBufferCallbackList *cb;
|
|
int pos[32];
|
|
char buf[MAX_CHAR_BYTES];
|
|
|
|
eol_type = s->b->eol_type;
|
|
charset = read_charset(s, charset_str, &eol_type);
|
|
if (!charset)
|
|
return;
|
|
|
|
b = s->b;
|
|
|
|
b1 = eb_new("*tmp*", b->flags & BF_STYLES);
|
|
eb_set_charset(b1, charset, eol_type);
|
|
|
|
/* preserve positions */
|
|
cb = b->first_callback;
|
|
for (i = 0; i < countof(pos) && cb; cb = cb->next) {
|
|
if (cb->callback == eb_offset_callback) {
|
|
pos[i] = eb_get_char_offset(b, *(int*)cb->opaque);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/* slow, but simple iterative method */
|
|
for (offset = 0; offset < b->total_size;) {
|
|
QETermStyle style = eb_get_style(b, offset);
|
|
int c = eb_nextc(b, offset, &offset);
|
|
b1->cur_style = style;
|
|
len = eb_encode_uchar(b1, buf, c);
|
|
eb_insert(b1, b1->total_size, buf, len);
|
|
}
|
|
|
|
/* replace current buffer with conversion */
|
|
/* quick hack to transfer styles from tmp buffer to b */
|
|
eb_free(&b->b_styles);
|
|
eb_delete(b, 0, b->total_size);
|
|
eb_set_charset(b, charset, eol_type);
|
|
eb_insert_buffer(b, 0, b1, 0, b1->total_size);
|
|
b->b_styles = b1->b_styles;
|
|
b1->b_styles = NULL;
|
|
|
|
/* restore positions */
|
|
cb = b->first_callback;
|
|
for (i = 0; i < countof(pos) && cb; cb = cb->next) {
|
|
if (cb->callback == eb_offset_callback) {
|
|
*(int*)cb->opaque = eb_goto_char(b, pos[i]);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
eb_free(&b1);
|
|
|
|
put_status(s, "Buffer charset is now %s, %d bytes",
|
|
s->b->charset->name, b->total_size);
|
|
}
|
|
|
|
void do_toggle_bidir(EditState *s)
|
|
{
|
|
s->bidir = !s->bidir;
|
|
}
|
|
|
|
void do_toggle_line_numbers(EditState *s)
|
|
{
|
|
s->line_numbers = !s->line_numbers;
|
|
}
|
|
|
|
void do_toggle_truncate_lines(EditState *s)
|
|
{
|
|
if (s->wrap == WRAP_TERM)
|
|
return;
|
|
|
|
if (s->wrap == WRAP_TRUNCATE) {
|
|
s->wrap = WRAP_LINE;
|
|
s->x_disp[0] = s->x_disp[1] = 0;
|
|
} else {
|
|
s->wrap = WRAP_TRUNCATE;
|
|
}
|
|
}
|
|
|
|
void do_word_wrap(EditState *s)
|
|
{
|
|
if (s->wrap == WRAP_TERM)
|
|
return;
|
|
|
|
if (s->wrap == WRAP_WORD) {
|
|
s->wrap = WRAP_LINE;
|
|
} else {
|
|
s->wrap = WRAP_WORD;
|
|
s->x_disp[0] = s->x_disp[1] = 0;
|
|
}
|
|
}
|
|
|
|
/* do_goto: move point to a specified position.
|
|
* take string and default unit,
|
|
* string is parsed as an integer with an optional sign, unit and suffix
|
|
* units: (b)yte, (c)har, (w)ord, (l)line, (%)percentage
|
|
* optional suffix :col or .col for column number in goto_line
|
|
*/
|
|
|
|
void do_goto(EditState *s, const char *str, int unit)
|
|
{
|
|
const char *p;
|
|
int pos, line, col, rel;
|
|
|
|
/* Update s->offset from str specification:
|
|
* optional +- for relative moves
|
|
* distance in decimal, octal or hex
|
|
* optional space
|
|
* optional unit suffix:
|
|
* b(bytes) c(chars) w(words) l(lines) %(percent)
|
|
*
|
|
* CG: XXX: resulting offset may fall inside a character.
|
|
*/
|
|
rel = (*str == '+' || *str == '-');
|
|
pos = strtol(str, (char**)&p, 0);
|
|
|
|
/* skip space required to separate hex offset from b or c suffix */
|
|
if (*p == ' ')
|
|
p++;
|
|
|
|
/* handle an optional multiple suffix */
|
|
switch (*p) {
|
|
case 'g':
|
|
pos *= 1000;
|
|
/* fall thru */
|
|
case 'm':
|
|
pos *= 1000;
|
|
/* fall thru */
|
|
case 'k':
|
|
pos *= 1000;
|
|
p++;
|
|
break;
|
|
case 'G':
|
|
pos *= 1024;
|
|
/* fall thru */
|
|
case 'M':
|
|
pos *= 1024;
|
|
/* fall thru */
|
|
case 'K':
|
|
pos *= 1024;
|
|
p++;
|
|
break;
|
|
}
|
|
|
|
if (memchr("bcwl%", *p, 5))
|
|
unit = *p++;
|
|
|
|
switch (unit) {
|
|
case 'b':
|
|
if (*p)
|
|
goto error;
|
|
if (rel)
|
|
pos += s->offset;
|
|
/* XXX: should realign on character boundary?
|
|
* realignment probably better addressed in display module
|
|
*/
|
|
s->offset = clamp(pos, 0, s->b->total_size);
|
|
return;
|
|
case 'c':
|
|
if (*p)
|
|
goto error;
|
|
if (rel)
|
|
pos += eb_get_char_offset(s->b, s->offset);
|
|
s->offset = eb_goto_char(s->b, max(0, pos));
|
|
return;
|
|
case '%':
|
|
/* CG: should not require long long for this */
|
|
pos = pos * (long long)s->b->total_size / 100;
|
|
if (rel)
|
|
pos += s->offset;
|
|
eb_get_pos(s->b, &line, &col, clamp(pos, 0, s->b->total_size));
|
|
line += (col > 0);
|
|
goto getcol;
|
|
|
|
case 'l':
|
|
line = pos - 1;
|
|
if (rel || pos <= 0) {
|
|
eb_get_pos(s->b, &line, &col, s->offset);
|
|
line += pos;
|
|
}
|
|
getcol:
|
|
col = 0;
|
|
if (*p == ':' || *p == '.') {
|
|
col = strtol(p + 1, (char**)&p, 0);
|
|
}
|
|
if (*p)
|
|
goto error;
|
|
s->offset = eb_goto_pos(s->b, max(0, line), col);
|
|
return;
|
|
}
|
|
error:
|
|
put_status(s, "Invalid position: %s", str);
|
|
}
|
|
|
|
void do_goto_line(EditState *s, int line, int column)
|
|
{
|
|
if (line >= 1)
|
|
s->offset = eb_goto_pos(s->b, line - 1, column > 0 ? column - 1 : 0);
|
|
}
|
|
|
|
void do_count_lines(EditState *s)
|
|
{
|
|
int total_lines, line_num, mark_line, col_num;
|
|
|
|
eb_get_pos(s->b, &total_lines, &col_num, s->b->total_size);
|
|
eb_get_pos(s->b, &mark_line, &col_num, s->b->mark);
|
|
eb_get_pos(s->b, &line_num, &col_num, s->offset);
|
|
|
|
put_status(s, "%d lines, point on line %d, %d lines in block",
|
|
total_lines, line_num + 1, abs(line_num - mark_line));
|
|
}
|
|
|
|
void do_what_cursor_position(EditState *s)
|
|
{
|
|
char buf[256];
|
|
int accents[6];
|
|
buf_t outbuf, *out;
|
|
int line_num, col_num;
|
|
int offset1, off;
|
|
int c, cc;
|
|
int i, n;
|
|
|
|
out = buf_init(&outbuf, buf, sizeof(buf));
|
|
if (s->offset < s->b->total_size) {
|
|
c = eb_nextc(s->b, s->offset, &offset1);
|
|
n = 0;
|
|
if (c != '\n' && !s->unihex_mode) {
|
|
while (n < countof(accents)
|
|
&& qe_isaccent(cc = eb_nextc(s->b, offset1, &off))) {
|
|
accents[n++] = cc;
|
|
offset1 = off;
|
|
}
|
|
}
|
|
if (s->b->eol_type == EOL_MAC) {
|
|
/* CR and LF are swapped in old style Mac buffers */
|
|
if (c == '\r' || c == '\n')
|
|
c ^= '\r' ^ '\n';
|
|
}
|
|
buf_puts(out, "char:");
|
|
if (c < 32 || c == 127) {
|
|
buf_printf(out, " ^%c", (c + '@') & 127);
|
|
} else
|
|
if (c < 127 || c >= 160) {
|
|
buf_put_byte(out, ' ');
|
|
buf_put_byte(out, '\'');
|
|
if (c == '\\' || c == '\'') {
|
|
buf_put_byte(out, '\\');
|
|
}
|
|
if (qe_isaccent(c)) {
|
|
buf_putc_utf8(out, ' ');
|
|
}
|
|
buf_putc_utf8(out, c);
|
|
for (i = 0; i < n; i++) {
|
|
buf_put_byte(out, ' ');
|
|
buf_putc_utf8(out, accents[i]);
|
|
}
|
|
buf_put_byte(out, '\'');
|
|
}
|
|
if (n == 0) {
|
|
if (c < 0x100) {
|
|
buf_printf(out, " \\%03o", c);
|
|
}
|
|
buf_printf(out, " %d", c);
|
|
}
|
|
buf_printf(out, " 0x%02x", c);
|
|
for (i = 0; i < n; i++) {
|
|
buf_printf(out, "/0x%02x", accents[i]);
|
|
}
|
|
/* Display buffer bytes if char is encoded */
|
|
if (offset1 != s->offset + 1) {
|
|
int sep = '[';
|
|
buf_put_byte(out, ' ');
|
|
for (off = s->offset; off < offset1; off++) {
|
|
cc = eb_read_one_byte(s->b, off);
|
|
buf_printf(out, "%c%02X", sep, cc);
|
|
sep = ' ';
|
|
}
|
|
buf_put_byte(out, ']');
|
|
}
|
|
if (s->b->style_bytes) {
|
|
buf_printf(out, " {%0*llX}", s->b->style_bytes * 2,
|
|
(unsigned long long)eb_get_style(s->b, s->offset));
|
|
}
|
|
}
|
|
eb_get_pos(s->b, &line_num, &col_num, s->offset);
|
|
put_status(s, "%s point=%d mark=%d size=%d region=%d col=%d",
|
|
out->buf, s->offset, s->b->mark, s->b->total_size,
|
|
abs(s->offset - s->b->mark), col_num);
|
|
}
|
|
|
|
void do_set_tab_width(EditState *s, int tab_width)
|
|
{
|
|
if (tab_width > 1)
|
|
s->b->tab_width = tab_width;
|
|
}
|
|
|
|
void do_set_indent_width(EditState *s, int indent_width)
|
|
{
|
|
if (indent_width > 1)
|
|
s->indent_size = indent_width;
|
|
}
|
|
|
|
void do_set_indent_tabs_mode(EditState *s, int val)
|
|
{
|
|
s->indent_tabs_mode = (val != 0);
|
|
}
|
|
|
|
static void do_set_fill_column(EditState *s, int fill_column)
|
|
{
|
|
if (fill_column > 1)
|
|
s->b->fill_column = fill_column;
|
|
}
|
|
|
|
static char *qe_get_mode_name(EditState *s, char *buf, int size, int full)
|
|
{
|
|
buf_t outbuf, *out;
|
|
|
|
out = buf_init(&outbuf, buf, size);
|
|
|
|
if (s->b->data_type_name)
|
|
buf_printf(out, "%s+", s->b->data_type_name);
|
|
buf_puts(out, s->mode ? s->mode->name : "raw");
|
|
|
|
if (full) {
|
|
if (!s->insert)
|
|
buf_puts(out, " Ovwrt");
|
|
if (s->interactive)
|
|
buf_puts(out, " Interactive");
|
|
if (s->b->flags & BF_PREVIEW)
|
|
buf_puts(out, " Preview");
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/* compute string for the first part of the mode line (flags,
|
|
filename, modename) */
|
|
void basic_mode_line(EditState *s, buf_t *out, int c1)
|
|
{
|
|
char buf[128];
|
|
const char *mode_name;
|
|
int mod, state;
|
|
|
|
mod = s->b->modified ? '*' : '-';
|
|
if (s->b->flags & BF_LOADING)
|
|
state = 'L';
|
|
else if (s->b->flags & BF_SAVING)
|
|
state = 'S';
|
|
else if (s->busy)
|
|
state = 'B';
|
|
else
|
|
state = '-';
|
|
|
|
mode_name = qe_get_mode_name(s, buf, sizeof(buf), 1);
|
|
/* Strip text mode name if another mode is also active */
|
|
strstart(mode_name, "text ", &mode_name);
|
|
|
|
buf_printf(out, "%c%c:%c%c %-20s (%s)--",
|
|
c1, state, s->b->flags & BF_READONLY ? '%' : mod,
|
|
mod, s->b->name, mode_name);
|
|
}
|
|
|
|
void text_mode_line(EditState *s, buf_t *out)
|
|
{
|
|
int line_num, col_num, wrap_mode;
|
|
const QEProperty *tag;
|
|
|
|
wrap_mode = '-';
|
|
if (!s->hex_mode) {
|
|
if (s->wrap == WRAP_TRUNCATE)
|
|
wrap_mode = 'T';
|
|
else if (s->wrap == WRAP_WORD)
|
|
wrap_mode = 'W';
|
|
}
|
|
basic_mode_line(s, out, wrap_mode);
|
|
|
|
eb_get_pos(s->b, &line_num, &col_num, s->offset);
|
|
buf_printf(out, "L%d--C%d--%s",
|
|
line_num + 1, col_num, s->b->charset->name);
|
|
if (s->b->eol_type == EOL_DOS)
|
|
buf_puts(out, "-dos");
|
|
if (s->b->eol_type == EOL_MAC)
|
|
buf_puts(out, "-mac");
|
|
if (s->bidir)
|
|
buf_printf(out, "--%s", s->cur_rtl ? "RTL" : "LTR");
|
|
|
|
if (s->input_method)
|
|
buf_printf(out, "--%s", s->input_method->name);
|
|
buf_printf(out, "--%d%%", compute_percent(s->offset, s->b->total_size));
|
|
if (s->x_disp[0])
|
|
buf_printf(out, "--<%d", -s->x_disp[0]);
|
|
if (s->x_disp[1])
|
|
buf_printf(out, "-->%d", -s->x_disp[1]);
|
|
tag = eb_find_property(s->b, 0, s->offset + 1, QE_PROP_TAG);
|
|
if (tag)
|
|
buf_printf(out, "--%s", (char*)tag->data);
|
|
#if 0
|
|
buf_printf(out, "--[%d]", s->y_disp);
|
|
#endif
|
|
}
|
|
|
|
void display_mode_line(EditState *s)
|
|
{
|
|
char buf[MAX_SCREEN_WIDTH];
|
|
buf_t outbuf, *out;
|
|
int y = s->ytop + s->height;
|
|
|
|
if (s->flags & WF_MODELINE) {
|
|
out = buf_init(&outbuf, buf, sizeof(buf));
|
|
s->mode->get_mode_line(s, out);
|
|
if (!strequal(buf, s->modeline_shadow)) {
|
|
print_at_byte(s->screen, s->xleft, y, s->width,
|
|
s->qe_state->mode_line_height,
|
|
buf, QE_STYLE_MODE_LINE);
|
|
pstrcpy(s->modeline_shadow, sizeof(s->modeline_shadow), buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
void display_window_borders(EditState *e)
|
|
{
|
|
QEmacsState *qs = e->qe_state;
|
|
|
|
if (e->borders_invalid) {
|
|
if (e->flags & (WF_POPUP | WF_RSEPARATOR)) {
|
|
CSSRect rect;
|
|
QEColor color;
|
|
int x = e->x1;
|
|
int y = e->y1;
|
|
int width = e->x2 - e->x1;
|
|
int height = e->y2 - e->y1;
|
|
|
|
rect.x1 = 0;
|
|
rect.y1 = 0;
|
|
rect.x2 = qs->width;
|
|
rect.y2 = qs->height;
|
|
/* XXX: should use popup size? */
|
|
set_clip_rectangle(qs->screen, &rect);
|
|
color = qe_styles[QE_STYLE_WINDOW_BORDER].bg_color;
|
|
if (e->flags & WF_POPUP) {
|
|
/* XXX: should use client area instead of recomputing it */
|
|
int top_h = e->caption ? qs->mode_line_height : qs->border_width;
|
|
int bottom_h = qs->border_width;
|
|
int left_w = qs->border_width;
|
|
int right_w = qs->border_width;
|
|
|
|
fill_rectangle(qs->screen, x, y, width, top_h, color);
|
|
fill_rectangle(qs->screen, x, y + bottom_h,
|
|
left_w, height - top_h - bottom_h, color);
|
|
fill_rectangle(qs->screen, x + width - right_w, y + top_h,
|
|
right_w, height - top_h - bottom_h, color);
|
|
fill_rectangle(qs->screen, x, y + height - bottom_h,
|
|
width, bottom_h, color);
|
|
/* display caption */
|
|
if (e->caption) {
|
|
QEStyleDef styledef;
|
|
QECharMetrics metrics;
|
|
QEFont *font;
|
|
unsigned int buf[256];
|
|
int len;
|
|
|
|
/* XXX: Should convert from UTF-8? */
|
|
for (len = 0; len < 256 && e->caption[len]; len++) {
|
|
buf[len] = e->caption[len];
|
|
}
|
|
get_style(e, &styledef, QE_STYLE_WINDOW_BORDER);
|
|
font = select_font(qs->screen,
|
|
styledef.font_style, styledef.font_size);
|
|
text_metrics(qs->screen, font, &metrics, buf, len);
|
|
draw_text(qs->screen, font,
|
|
x + width / 2 - metrics.width / 2, y + metrics.font_ascent,
|
|
buf, len, styledef.fg_color);
|
|
release_font(qs->screen, font);
|
|
}
|
|
}
|
|
if (e->flags & WF_RSEPARATOR) {
|
|
fill_rectangle(qs->screen,
|
|
x + width - qs->separator_width, y,
|
|
qs->separator_width, height, color);
|
|
}
|
|
}
|
|
e->borders_invalid = 0;
|
|
}
|
|
}
|
|
|
|
#if 1
|
|
/* Should move all this to display.c */
|
|
|
|
/* compute style */
|
|
static void apply_style(QEStyleDef *stp, QETermStyle style)
|
|
{
|
|
QEStyleDef *s;
|
|
|
|
if (style & QE_TERM_COMPOSITE) {
|
|
int fg = QE_TERM_GET_FG(style);
|
|
int bg = QE_TERM_GET_BG(style);
|
|
if (style & QE_TERM_BOLD) {
|
|
stp->font_style |= QE_FONT_STYLE_BOLD;
|
|
if (fg < 8)
|
|
fg |= 8;
|
|
}
|
|
if (style & QE_TERM_UNDERLINE)
|
|
stp->font_style |= QE_FONT_STYLE_UNDERLINE;
|
|
if (style & QE_TERM_ITALIC)
|
|
stp->font_style |= QE_FONT_STYLE_ITALIC;
|
|
if (style & QE_TERM_BLINK)
|
|
stp->font_style |= QE_FONT_STYLE_BLINK;
|
|
stp->fg_color = qe_unmap_color(fg, QE_TERM_FG_COLORS);
|
|
stp->bg_color = qe_unmap_color(bg, QE_TERM_BG_COLORS);
|
|
} else {
|
|
s = &qe_styles[style & QE_STYLE_NUM];
|
|
if (s->fg_color != COLOR_TRANSPARENT)
|
|
stp->fg_color = s->fg_color;
|
|
if (s->bg_color != COLOR_TRANSPARENT)
|
|
stp->bg_color = s->bg_color;
|
|
if (s->font_style != 0)
|
|
stp->font_style = s->font_style;
|
|
if (s->font_size != 0)
|
|
stp->font_size = s->font_size;
|
|
}
|
|
/* for selection, we need a special handling because only color is
|
|
changed */
|
|
if (style & QE_STYLE_SEL) {
|
|
s = &qe_styles[QE_STYLE_SELECTION];
|
|
stp->fg_color = s->fg_color;
|
|
stp->bg_color = s->bg_color;
|
|
}
|
|
}
|
|
|
|
void get_style(EditState *e, QEStyleDef *stp, QETermStyle style)
|
|
{
|
|
/* get root default style */
|
|
*stp = qe_styles[0];
|
|
|
|
/* apply window default style */
|
|
if (e && e->default_style != 0)
|
|
apply_style(stp, e->default_style);
|
|
|
|
/* apply specific style */
|
|
if (style != 0)
|
|
apply_style(stp, style);
|
|
}
|
|
|
|
void style_complete(CompleteState *cp)
|
|
{
|
|
int i;
|
|
QEStyleDef *stp;
|
|
|
|
stp = qe_styles;
|
|
for (i = 0; i < QE_STYLE_NB; i++, stp++) {
|
|
complete_test(cp, stp->name);
|
|
}
|
|
}
|
|
|
|
int find_style_index(const char *name)
|
|
{
|
|
int i;
|
|
QEStyleDef *stp;
|
|
|
|
stp = qe_styles;
|
|
for (i = 0; i < QE_STYLE_NB; i++, stp++) {
|
|
if (strequal(stp->name, name))
|
|
return i;
|
|
}
|
|
if (qe_isdigit(*name)) {
|
|
i = strtol(name, NULL, 0);
|
|
if (i < QE_STYLE_NB)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
QEStyleDef *find_style(const char *name)
|
|
{
|
|
int i = find_style_index(name);
|
|
|
|
if (i >= 0 && i < QE_STYLE_NB)
|
|
return qe_styles + i;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static CompletionDef style_completion = {
|
|
"style", style_complete
|
|
};
|
|
|
|
static const char * const qe_style_properties[] = {
|
|
#define CSS_PROP_COLOR 0
|
|
"color", /* color */
|
|
#define CSS_PROP_BACKGROUND_COLOR 1
|
|
"background-color", /* color */
|
|
#define CSS_PROP_FONT_FAMILY 2
|
|
"font-family", /* font_family: serif|times|sans|arial|helvetica| */
|
|
/* fixed|monospace|courier */
|
|
#define CSS_PROP_FONT_STYLE 3
|
|
"font-style", /* font_style: italic / normal */
|
|
#define CSS_PROP_FONT_WEIGHT 4
|
|
"font-weight", /* font_weight: bold / normal */
|
|
#define CSS_PROP_FONT_SIZE 5
|
|
"font-size", /* font_size: inherit / size */
|
|
#define CSS_PROP_TEXT_DECORATION 6
|
|
"text-decoration", /* text_decoration: none / underline */
|
|
};
|
|
|
|
void style_property_complete(CompleteState *cp)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < countof(qe_style_properties); i++) {
|
|
complete_test(cp, qe_style_properties[i]);
|
|
}
|
|
}
|
|
|
|
static CompletionDef style_property_completion = {
|
|
"style-property", style_property_complete
|
|
};
|
|
|
|
int find_style_property(const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < countof(qe_style_properties); i++) {
|
|
if (strequal(qe_style_properties[i], name))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Note: we use the same syntax as CSS styles to ease merging */
|
|
void do_set_style(EditState *e, const char *stylestr,
|
|
const char *propstr, const char *value)
|
|
{
|
|
QEStyleDef *stp;
|
|
int v, prop_index;
|
|
|
|
stp = find_style(stylestr);
|
|
if (!stp) {
|
|
put_status(e, "Unknown style '%s'", stylestr);
|
|
return;
|
|
}
|
|
|
|
prop_index = find_style_property(propstr);
|
|
if (prop_index < 0) {
|
|
put_status(e, "Unknown property '%s'", propstr);
|
|
return;
|
|
}
|
|
|
|
switch (prop_index) {
|
|
case CSS_PROP_COLOR:
|
|
if (css_get_color(&stp->fg_color, value))
|
|
goto bad_color;
|
|
break;
|
|
case CSS_PROP_BACKGROUND_COLOR:
|
|
if (css_get_color(&stp->bg_color, value))
|
|
goto bad_color;
|
|
break;
|
|
bad_color:
|
|
put_status(e, "Unknown color '%s'", value);
|
|
return;
|
|
case CSS_PROP_FONT_FAMILY:
|
|
v = css_get_font_family(value);
|
|
stp->font_style = (stp->font_style & ~QE_FONT_FAMILY_MASK) | v;
|
|
break;
|
|
case CSS_PROP_FONT_STYLE:
|
|
/* XXX: cannot handle inherit correctly */
|
|
v = stp->font_style;
|
|
if (strequal(value, "italic")) {
|
|
v |= QE_FONT_STYLE_ITALIC;
|
|
} else
|
|
if (strequal(value, "normal")) {
|
|
v &= ~QE_FONT_STYLE_ITALIC;
|
|
}
|
|
stp->font_style = v;
|
|
break;
|
|
case CSS_PROP_FONT_WEIGHT:
|
|
/* XXX: cannot handle inherit correctly */
|
|
v = stp->font_style;
|
|
if (strequal(value, "bold")) {
|
|
v |= QE_FONT_STYLE_BOLD;
|
|
} else
|
|
if (strequal(value, "normal")) {
|
|
v &= ~QE_FONT_STYLE_BOLD;
|
|
}
|
|
stp->font_style = v;
|
|
break;
|
|
case CSS_PROP_FONT_SIZE:
|
|
if (strequal(value, "inherit")) {
|
|
stp->font_size = 0;
|
|
} else {
|
|
stp->font_size = strtol(value, NULL, 0);
|
|
}
|
|
break;
|
|
case CSS_PROP_TEXT_DECORATION:
|
|
/* XXX: cannot handle inherit correctly */
|
|
if (strequal(value, "none")) {
|
|
stp->font_style &= ~QE_FONT_STYLE_UNDERLINE;
|
|
} else
|
|
if (strequal(value, "underline")) {
|
|
stp->font_style |= QE_FONT_STYLE_UNDERLINE;
|
|
}
|
|
break;
|
|
}
|
|
//s->qe_state->complete_refresh = 1;
|
|
}
|
|
|
|
void do_define_color(EditState *e, const char *name, const char *value)
|
|
{
|
|
if (css_define_color(name, value))
|
|
put_status(e, "Invalid color '%s'", value);
|
|
}
|
|
#endif
|
|
|
|
void do_set_display_size(qe__unused__ EditState *s, int w, int h)
|
|
{
|
|
if (w != NO_ARG && h != NO_ARG) {
|
|
screen_width = w;
|
|
screen_height = h;
|
|
}
|
|
}
|
|
|
|
/* NOTE: toggle-full-screen zooms the current pane to the whole screen if
|
|
possible. It does not hide the modeline not the status line */
|
|
void do_toggle_full_screen(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
QEditScreen *screen = s->screen;
|
|
|
|
if (screen->dpy.dpy_full_screen) {
|
|
qs->is_full_screen = !qs->is_full_screen;
|
|
screen->dpy.dpy_full_screen(screen, qs->is_full_screen);
|
|
do_refresh(s);
|
|
} else {
|
|
put_status(s, "full screen unsupported on this device");
|
|
}
|
|
}
|
|
|
|
void do_toggle_mode_line(EditState *s)
|
|
{
|
|
s->flags ^= WF_MODELINE;
|
|
do_refresh(s);
|
|
}
|
|
|
|
void do_set_window_style(EditState *s, const char *stylestr)
|
|
{
|
|
int style_index = find_style_index(stylestr);
|
|
|
|
if (style_index < 0) {
|
|
put_status(s, "Unknown style '%s'", stylestr);
|
|
return;
|
|
}
|
|
s->default_style = style_index;
|
|
}
|
|
|
|
void do_set_system_font(EditState *s, const char *qe_font_name,
|
|
const char *system_fonts)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
int font_type;
|
|
|
|
font_type = css_get_enum(qe_font_name, "fixed,serif,sans");
|
|
if (font_type < 0) {
|
|
put_status(s, "Invalid qemacs font");
|
|
return;
|
|
}
|
|
pstrcpy(qs->system_fonts[font_type], sizeof(qs->system_fonts[0]),
|
|
system_fonts);
|
|
//qs->complete_refresh = 1;
|
|
}
|
|
|
|
static void display_bol_bidir(DisplayState *ds, DirType base,
|
|
int embedding_level_max)
|
|
{
|
|
ds->base = base;
|
|
ds->x = ds->x_disp = ds->edit_state->x_disp[base];
|
|
if (ds->base == DIR_RTL) {
|
|
/* XXX: probably broken. bidir handling needs fixing */
|
|
ds->x_start = ds->edit_state->width - ds->x;
|
|
} else {
|
|
ds->x_start = ds->x;
|
|
}
|
|
ds->left_gutter = 0;
|
|
ds->x_line = ds->x_start;
|
|
ds->style = 0;
|
|
ds->last_style = 0;
|
|
ds->fragment_index = 0;
|
|
ds->line_index = 0;
|
|
ds->nb_fragments = 0;
|
|
ds->word_index = 0;
|
|
ds->embedding_level_max = embedding_level_max;
|
|
ds->last_word_space = 0;
|
|
}
|
|
|
|
void display_bol(DisplayState *ds)
|
|
{
|
|
display_bol_bidir(ds, DIR_LTR, 0);
|
|
}
|
|
|
|
void display_close(DisplayState *ds)
|
|
{
|
|
}
|
|
|
|
void display_init(DisplayState *ds, EditState *e, enum DisplayType do_disp,
|
|
int (*cursor_func)(DisplayState *ds,
|
|
int offset1, int offset2, int line_num,
|
|
int x, int y, int w, int h, int hex_mode),
|
|
void *cursor_opaque)
|
|
{
|
|
QEFont *font;
|
|
QEStyleDef styledef;
|
|
|
|
ds->edit_state = e;
|
|
ds->do_disp = do_disp;
|
|
ds->cursor_func = cursor_func;
|
|
ds->cursor_opaque = cursor_opaque;
|
|
ds->wrap = e->wrap;
|
|
if (ds->wrap == WRAP_AUTO) {
|
|
/* XXX: check e->mode->default_wrap */
|
|
/* Behave as WRAP_LINE if window is not at least 75% of full width.
|
|
* This allows the same default behavior for full width window and
|
|
* the dired view pane but behaves as WRAP_TRUNCATE on split screens
|
|
*/
|
|
if (e->width >= e->screen->width * 3 / 4) {
|
|
ds->wrap = WRAP_LINE;
|
|
}
|
|
}
|
|
/* select default values */
|
|
get_style(e, &styledef, QE_STYLE_DEFAULT);
|
|
font = select_font(e->screen, styledef.font_style, styledef.font_size);
|
|
ds->default_line_height = font->ascent + font->descent;
|
|
ds->space_width = glyph_width(e->screen, font, ' ');
|
|
ds->tab_width = ds->space_width * e->b->tab_width;
|
|
ds->height = e->height;
|
|
ds->hex_mode = e->hex_mode;
|
|
ds->cur_hex_mode = 0;
|
|
ds->y = e->y_disp;
|
|
ds->line_num = 0;
|
|
ds->line_numbers = e->line_numbers * ds->space_width * 8;
|
|
if (ds->line_numbers > e->width / 2)
|
|
ds->line_numbers = 0;
|
|
if (ds->wrap == WRAP_TERM) {
|
|
ds->eol_width = 0;
|
|
ds->width = ds->line_numbers +
|
|
e->wrap_cols * glyph_width(e->screen, font, '0');
|
|
} else {
|
|
ds->eol_width = max3(glyph_width(e->screen, font, '/'),
|
|
glyph_width(e->screen, font, '\\'),
|
|
glyph_width(e->screen, font, '$'));
|
|
ds->width = e->width - ds->eol_width;
|
|
}
|
|
ds->eol_reached = 0;
|
|
ds->eod = 0;
|
|
display_bol(ds);
|
|
release_font(e->screen, font);
|
|
}
|
|
|
|
static void reverse_fragments(TextFragment *str, int len)
|
|
{
|
|
int i, len2 = len / 2;
|
|
|
|
for (i = 0; i < len2; i++) {
|
|
TextFragment tmp = str[i];
|
|
str[i] = str[len - 1 - i];
|
|
str[len - 1 - i] = tmp;
|
|
}
|
|
}
|
|
|
|
#define LINE_SHADOW_INCR 10
|
|
|
|
/* CRC to optimize redraw. */
|
|
/* XXX: is it safe enough ? */
|
|
static uint64_t compute_crc(const void *p, int size, uint64_t sum)
|
|
{
|
|
const u8 *data = (const u8 *)p;
|
|
|
|
/* Rotating sum necessary to prevent trivial collisions on
|
|
* line_chars because it is an array of code points stored as u32.
|
|
*/
|
|
/* XXX: We still have a bug when transposing two 31 byte words as in
|
|
* B123456789012345678901234567890 A123456789012345678901234567890
|
|
* Here is another trivial collision:
|
|
s->esc_params[s->nb_esc_params] = 0;
|
|
s->has_params[s->nb_esc_params] = 1;
|
|
*/
|
|
while (((uintptr_t)data & 3) && size > 0) {
|
|
//sum += ((sum >> 31) & 1) + sum + *data;
|
|
//sum += sum + *data + (sum >> 32);
|
|
sum = (sum << 3) + *data + (sum >> 32);
|
|
data++;
|
|
size--;
|
|
}
|
|
while (size >= 4) {
|
|
//sum += ((sum >> 31) & 1) + sum + *(const uint32_t *)(const void *)data;
|
|
//sum += sum + *(const uint32_t *)(const void *)data + (sum >> 32);
|
|
sum = (sum << 3) + *(const uint32_t *)(const void *)data + (sum >> 32);
|
|
data += 4;
|
|
size -= 4;
|
|
}
|
|
while (size > 0) {
|
|
//sum += ((sum >> 31) & 1) + sum + *data;
|
|
//sum += sum + *data + (sum >> 32);
|
|
sum = (sum << 2) + *data + (sum >> 32);
|
|
data++;
|
|
size--;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
/* flush the line fragments to the screen.
|
|
`offset1..offset2` is the range of offsets for cursor management
|
|
`last` is 0 for a line wrap, 1 for end of line, -1 for continuation
|
|
*/
|
|
static void flush_line(DisplayState *ds,
|
|
TextFragment *fragments, int nb_fragments,
|
|
int offset1, int offset2, int last)
|
|
{
|
|
EditState *e = ds->edit_state;
|
|
QEditScreen *screen = e->screen;
|
|
int level, pos, p, i, x, x1, y, baseline, line_height, max_descent;
|
|
TextFragment *frag;
|
|
QEFont *font;
|
|
|
|
/* compute baseline and lineheight (incorrect for very long lines) */
|
|
baseline = 0;
|
|
max_descent = 0;
|
|
for (i = 0; i < nb_fragments; i++) {
|
|
frag = &fragments[i];
|
|
if (frag->ascent > baseline)
|
|
baseline = frag->ascent;
|
|
if (frag->descent > max_descent)
|
|
max_descent = frag->descent;
|
|
}
|
|
if (nb_fragments == 0) {
|
|
/* if empty line, still needs a non zero line height */
|
|
line_height = ds->default_line_height;
|
|
} else {
|
|
line_height = baseline + max_descent;
|
|
}
|
|
|
|
/* swap according to embedding level (incorrect for very long lines) */
|
|
for (level = ds->embedding_level_max; level > 0; level--) {
|
|
pos = 0;
|
|
while (pos < nb_fragments) {
|
|
if (fragments[pos].embedding_level >= level) {
|
|
/* find all chars >= level */
|
|
for (p = pos + 1; p < nb_fragments && fragments[p].embedding_level >= level; p++)
|
|
continue;
|
|
reverse_fragments(fragments + pos, p - pos);
|
|
pos = p + 1;
|
|
} else {
|
|
pos++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* draw everything if line is visible in window */
|
|
if (ds->do_disp == DISP_PRINT
|
|
&& ds->y + line_height >= 0
|
|
&& ds->y < e->ytop + e->height) {
|
|
QEStyleDef styledef, default_style;
|
|
int no_display = 0;
|
|
|
|
/* test if display needed */
|
|
if (ds->line_num >= 0 && ds->line_num < 2048) {
|
|
/* paranoid: prevent cache growing too large */
|
|
/* Use checksum based line cache to improve speed in graphics mode.
|
|
* XXX: overlong lines will fail the cache test
|
|
*/
|
|
if (ds->line_num >= e->shadow_nb_lines) {
|
|
/* reallocate shadow */
|
|
int n = ds->line_num + LINE_SHADOW_INCR;
|
|
if (qe_realloc(&e->line_shadow, n * sizeof(QELineShadow))) {
|
|
/* put an impossible value so that we redraw */
|
|
memset(&e->line_shadow[e->shadow_nb_lines], 0xff,
|
|
(n - e->shadow_nb_lines) * sizeof(QELineShadow));
|
|
e->shadow_nb_lines = n;
|
|
}
|
|
}
|
|
if (ds->line_num < e->shadow_nb_lines && !disable_crc) {
|
|
QELineShadow *ls;
|
|
uint64_t crc;
|
|
|
|
crc = compute_crc(fragments, sizeof(*fragments) * nb_fragments, 0);
|
|
crc = compute_crc(ds->line_chars, sizeof(*ds->line_chars) * ds->line_index, crc);
|
|
ls = &e->line_shadow[ds->line_num];
|
|
if (ls->y != ds->y || ls->x != ds->x_line
|
|
|| ls->height != line_height || ls->crc != crc) {
|
|
/* update values for the line cache */
|
|
ls->y = ds->y;
|
|
ls->x = ds->x_line;
|
|
ls->height = line_height;
|
|
ls->crc = crc;
|
|
} else {
|
|
no_display = 1;
|
|
}
|
|
}
|
|
}
|
|
if (!no_display) {
|
|
/* display */
|
|
get_style(e, &default_style, QE_STYLE_DEFAULT);
|
|
x = ds->x_start;
|
|
y = ds->y;
|
|
|
|
/* first display background rectangles */
|
|
/* XXX: should coalesce rectangles with identical style */
|
|
if (ds->left_gutter > 0) {
|
|
/* erase space before the line display, aka left gutter */
|
|
get_style(e, &styledef, QE_STYLE_GUTTER);
|
|
fill_rectangle(screen, e->xleft + x, e->ytop + y,
|
|
ds->left_gutter, line_height, styledef.bg_color);
|
|
}
|
|
x = ds->x_line;
|
|
x1 = ds->width + ds->eol_width;
|
|
for (i = 0; i < nb_fragments && x < x1; i++) {
|
|
frag = &fragments[i];
|
|
get_style(e, &styledef, frag->style);
|
|
fill_rectangle(screen, e->xleft + x, e->ytop + y,
|
|
frag->width, line_height, styledef.bg_color);
|
|
x += frag->width;
|
|
}
|
|
if (x < x1 && last != -1) {
|
|
/* XXX: color may be inappropriate for terminal mode */
|
|
fill_rectangle(screen, e->xleft + x, e->ytop + y,
|
|
x1 - x, line_height, default_style.bg_color);
|
|
}
|
|
if (x1 < e->width) {
|
|
/* right gutter like space beyond terminal right margin */
|
|
get_style(e, &styledef, QE_STYLE_GUTTER);
|
|
fill_rectangle(screen, e->xleft + x1, e->ytop + y,
|
|
e->width - x1, line_height, styledef.bg_color);
|
|
}
|
|
|
|
/* then display text */
|
|
x = ds->x_line;
|
|
y += baseline;
|
|
|
|
for (i = 0; i < nb_fragments && x < x1; i++) {
|
|
frag = &fragments[i];
|
|
x += frag->width;
|
|
if (x > 0) {
|
|
get_style(e, &styledef, frag->style);
|
|
font = select_font(screen,
|
|
styledef.font_style, styledef.font_size);
|
|
draw_text(screen, font,
|
|
e->xleft + x - frag->width, e->ytop + y,
|
|
ds->line_chars + frag->line_index,
|
|
frag->len, styledef.fg_color);
|
|
release_font(screen, font);
|
|
}
|
|
}
|
|
|
|
if (last == 0 && ds->eol_width != 0) {
|
|
/* draw eol mark */
|
|
unsigned int markbuf[1];
|
|
|
|
markbuf[0] = '/'; /* RTL eol mark */
|
|
x = 0; /* displayed at the left border */
|
|
if (ds->base == DIR_LTR) {
|
|
markbuf[0] = '\\'; /* LTR eol mark */
|
|
x = ds->width; /* displayed at the right border */
|
|
}
|
|
font = select_font(screen,
|
|
default_style.font_style,
|
|
default_style.font_size);
|
|
draw_text(screen, font, e->xleft + x, e->ytop + y,
|
|
markbuf, 1, default_style.fg_color);
|
|
release_font(screen, font);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* call cursor callback */
|
|
if (ds->cursor_func) {
|
|
x = ds->x_line;
|
|
y = ds->y;
|
|
|
|
/* RTL eol cursor check (probably incorrect) */
|
|
if (offset1 >= 0 && offset2 >= 0 &&
|
|
ds->base == DIR_RTL &&
|
|
ds->cursor_func(ds, offset1, offset2, ds->line_num,
|
|
x, y, -ds->eol_width, line_height, e->hex_mode)) {
|
|
ds->eod = 1;
|
|
}
|
|
|
|
for (i = 0; i < nb_fragments; i++) {
|
|
int j, k;
|
|
|
|
frag = &fragments[i];
|
|
|
|
for (j = frag->line_index, k = 0; k < frag->len; k++, j++) {
|
|
int _offset1 = ds->line_offsets[j][0];
|
|
int _offset2 = ds->line_offsets[j][1];
|
|
int hex_mode = ds->line_hex_mode[j];
|
|
int w = ds->line_char_widths[j];
|
|
x += w;
|
|
if ((hex_mode == ds->hex_mode || ds->hex_mode == -1) &&
|
|
_offset1 >= 0 && _offset2 >= 0) {
|
|
#if 0
|
|
/* probably broken, bidir needs rework */
|
|
if (ds->base == DIR_RTL) {
|
|
if (ds->cursor_func(ds, _offset1, _offset2, ds->line_num,
|
|
x, y, -w, line_height, hex_mode))
|
|
ds->eod = 1;
|
|
} else
|
|
#endif
|
|
{
|
|
if (ds->cursor_func(ds, _offset1, _offset2, ds->line_num,
|
|
x - w, y, w, line_height, hex_mode))
|
|
ds->eod = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* LTR eol cursor check */
|
|
if (offset1 >= 0 && offset2 >= 0 &&
|
|
ds->base == DIR_LTR &&
|
|
ds->cursor_func(ds, offset1, offset2, ds->line_num,
|
|
x, y, ds->eol_width, line_height, e->hex_mode)) {
|
|
ds->eod = 1;
|
|
}
|
|
ds->x_line = x;
|
|
}
|
|
#if 0
|
|
printf("y=%d line_num=%d line_height=%d baseline=%d\n",
|
|
ds->y, ds->line_num, line_height, baseline);
|
|
#endif
|
|
if (last != -1) {
|
|
/* bump to next line */
|
|
ds->x_line = ds->x_start;
|
|
ds->y += line_height;
|
|
ds->line_num++;
|
|
}
|
|
}
|
|
|
|
/* keep 'n' line chars at the start of the line */
|
|
static void keep_line_chars(DisplayState *ds, int n)
|
|
{
|
|
int index;
|
|
|
|
index = ds->line_index - n;
|
|
memmove(ds->line_chars, ds->line_chars + index, n * sizeof(unsigned int));
|
|
memmove(ds->line_offsets, ds->line_offsets + index, n * 2 * sizeof(unsigned int));
|
|
memmove(ds->line_char_widths, ds->line_char_widths + index, n * sizeof(short));
|
|
ds->line_index = n;
|
|
}
|
|
|
|
#ifndef CONFIG_UNICODE_JOIN
|
|
|
|
/* fallback unicode functions */
|
|
|
|
int unicode_to_glyphs(unsigned int *dst, unsigned int *char_to_glyph_pos,
|
|
int dst_size, unsigned int *src, int src_size, int reverse)
|
|
{
|
|
int len, i;
|
|
|
|
len = src_size;
|
|
if (len > dst_size)
|
|
len = dst_size;
|
|
memcpy(dst, src, len * sizeof(unsigned int));
|
|
if (char_to_glyph_pos) {
|
|
for (i = 0; i < len; i++)
|
|
char_to_glyph_pos[i] = i;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* layout of a word fragment */
|
|
static void flush_fragment(DisplayState *ds)
|
|
{
|
|
int w, len, i, j;
|
|
QETermStyle style;
|
|
QEditScreen *screen = ds->edit_state->screen;
|
|
TextFragment *frag;
|
|
QEStyleDef styledef;
|
|
QEFont *font;
|
|
unsigned int char_to_glyph_pos[MAX_WORD_SIZE];
|
|
int nb_glyphs, dst_max_size, ascent, descent;
|
|
|
|
if (ds->fragment_index == 0)
|
|
return;
|
|
|
|
if (ds->nb_fragments >= MAX_SCREEN_WIDTH ||
|
|
ds->line_index + ds->fragment_index > MAX_SCREEN_WIDTH) {
|
|
/* too many fragments on the same line, flush and stay on the line */
|
|
flush_line(ds, ds->fragments, ds->nb_fragments, -1, -1, -1);
|
|
ds->nb_fragments = 0;
|
|
ds->line_index = 0;
|
|
ds->word_index = 0;
|
|
}
|
|
|
|
/* update word start index if needed */
|
|
if (ds->nb_fragments >= 1 && ds->last_word_space != ds->last_space) {
|
|
ds->last_word_space = ds->last_space;
|
|
ds->word_index = ds->nb_fragments;
|
|
}
|
|
|
|
/* convert fragment to glyphs (currently font independent, but may
|
|
change) */
|
|
//dst_max_size = MAX_SCREEN_WIDTH - ds->line_index;
|
|
//if (dst_max_size <= 0)
|
|
// goto the_end;
|
|
dst_max_size = MAX_WORD_SIZE; // assuming ds->fragment_index MAX_WORD_SIZE
|
|
nb_glyphs = unicode_to_glyphs(ds->line_chars + ds->line_index,
|
|
char_to_glyph_pos, dst_max_size,
|
|
ds->fragment_chars, ds->fragment_index,
|
|
ds->last_embedding_level & 1);
|
|
|
|
/* compute new offsets */
|
|
j = ds->line_index;
|
|
for (i = 0; i < nb_glyphs; i++) {
|
|
ds->line_offsets[j][0] = -1;
|
|
ds->line_offsets[j][1] = -1;
|
|
j++;
|
|
}
|
|
for (i = 0; i < ds->fragment_index; i++) {
|
|
int offset1, offset2;
|
|
j = ds->line_index + char_to_glyph_pos[i];
|
|
offset1 = ds->fragment_offsets[i][0];
|
|
offset2 = ds->fragment_offsets[i][1];
|
|
ds->line_hex_mode[j] = ds->fragment_hex_mode[i];
|
|
/* we suppose the the chars are contiguous */
|
|
if (ds->line_offsets[j][0] == -1 ||
|
|
ds->line_offsets[j][0] > offset1)
|
|
ds->line_offsets[j][0] = offset1;
|
|
if (ds->line_offsets[j][1] == -1 ||
|
|
ds->line_offsets[j][1] < offset2)
|
|
ds->line_offsets[j][1] = offset2;
|
|
}
|
|
|
|
style = ds->last_style;
|
|
get_style(ds->edit_state, &styledef, style);
|
|
/* select font according to current style */
|
|
font = select_font(screen, styledef.font_style, styledef.font_size);
|
|
j = ds->line_index;
|
|
ascent = font->ascent;
|
|
descent = font->descent;
|
|
if (ds->line_chars[j] == '\t') {
|
|
int x1;
|
|
/* special case for TAB */
|
|
x1 = (ds->x - ds->x_disp) % ds->tab_width;
|
|
w = ds->tab_width - x1;
|
|
/* display a single space */
|
|
ds->line_chars[j] = ' ';
|
|
ds->line_char_widths[j] = w;
|
|
} else {
|
|
/* XXX: use text metrics for full fragment */
|
|
w = 0;
|
|
/* XXX: is the width negative for a RTL fragment? */
|
|
for (i = 0; i < nb_glyphs; i++) {
|
|
QECharMetrics metrics;
|
|
text_metrics(screen, font, &metrics, &ds->line_chars[j], 1);
|
|
if (metrics.font_ascent > ascent)
|
|
ascent = metrics.font_ascent;
|
|
if (metrics.font_descent > descent)
|
|
descent = metrics.font_descent;
|
|
ds->line_char_widths[j] = metrics.width;
|
|
w += ds->line_char_widths[j];
|
|
j++;
|
|
}
|
|
}
|
|
release_font(screen, font);
|
|
|
|
/* add the fragment */
|
|
frag = &ds->fragments[ds->nb_fragments++];
|
|
frag->width = w;
|
|
frag->line_index = ds->line_index;
|
|
frag->len = nb_glyphs;
|
|
frag->embedding_level = ds->last_embedding_level;
|
|
frag->style = style;
|
|
frag->ascent = ascent;
|
|
frag->descent = descent;
|
|
#if QE_TERM_STYLE_BITS == 16
|
|
frag->dummy = 0; /* initialize padding for checksum consistency */
|
|
#endif
|
|
|
|
ds->line_index += nb_glyphs;
|
|
ds->x += frag->width;
|
|
|
|
switch (ds->wrap) {
|
|
case WRAP_TRUNCATE:
|
|
case WRAP_AUTO:
|
|
break;
|
|
case WRAP_LINE:
|
|
case WRAP_TERM:
|
|
while (ds->x > ds->width) {
|
|
int len1, w1, ww, n;
|
|
//printf("x=%d maxw=%d len=%d\n", ds->x, ds->width, frag->len);
|
|
frag = &ds->fragments[ds->nb_fragments - 1];
|
|
/* find fragment truncation to fit the line */
|
|
len = len1 = frag->len;
|
|
w1 = ds->x;
|
|
while (ds->x > ds->width) {
|
|
len--;
|
|
ww = ds->line_char_widths[frag->line_index + len];
|
|
ds->x -= ww;
|
|
if (len == 0 && ds->x == 0) {
|
|
/* avoid looping by putting at least one char per line */
|
|
len = 1;
|
|
ds->x += ww;
|
|
break;
|
|
}
|
|
}
|
|
len1 -= len;
|
|
w1 -= ds->x;
|
|
frag->len = len;
|
|
frag->width -= w1;
|
|
//printf("after: x=%d w1=%d\n", ds->x, w1);
|
|
n = ds->nb_fragments;
|
|
if (len == 0)
|
|
n--;
|
|
|
|
/* flush fragments with a line continuation mark */
|
|
flush_line(ds, ds->fragments, n, -1, -1, 0);
|
|
|
|
/* skip line number column if present */
|
|
ds->left_gutter = ds->line_numbers;
|
|
ds->x = ds->x_line += ds->left_gutter;
|
|
|
|
/* move the remaining fragment to next line */
|
|
ds->nb_fragments = 0;
|
|
if (len1 > 0) {
|
|
memmove(ds->fragments, frag, sizeof(TextFragment));
|
|
frag = ds->fragments;
|
|
frag->width = w1;
|
|
frag->line_index = 0;
|
|
frag->len = len1;
|
|
ds->nb_fragments = 1;
|
|
ds->x += w1;
|
|
}
|
|
keep_line_chars(ds, len1);
|
|
}
|
|
break;
|
|
case WRAP_WORD:
|
|
if (ds->x > ds->width) {
|
|
int index;
|
|
|
|
/* flush fragments with a line continuation mark */
|
|
flush_line(ds, ds->fragments, ds->word_index, -1, -1, 0);
|
|
|
|
/* skip line number column if present */
|
|
ds->left_gutter = ds->line_numbers;
|
|
ds->x = ds->x_line += ds->left_gutter;
|
|
|
|
/* put words on next line */
|
|
index = ds->fragments[ds->word_index].line_index;
|
|
memmove(ds->fragments, ds->fragments + ds->word_index,
|
|
(ds->nb_fragments - ds->word_index) * sizeof(TextFragment));
|
|
ds->nb_fragments -= ds->word_index;
|
|
|
|
for (i = 0; i < ds->nb_fragments; i++) {
|
|
ds->fragments[i].line_index -= index;
|
|
ds->x += ds->fragments[i].width;
|
|
}
|
|
keep_line_chars(ds, ds->line_index - index);
|
|
ds->word_index = 0;
|
|
}
|
|
break;
|
|
}
|
|
ds->fragment_index = 0;
|
|
}
|
|
|
|
int display_char_bidir(DisplayState *ds, int offset1, int offset2,
|
|
int embedding_level, int ch)
|
|
{
|
|
int space, istab, isaccent;
|
|
QETermStyle style;
|
|
EditState *e;
|
|
|
|
style = ds->style;
|
|
|
|
/* special code to colorize block */
|
|
e = ds->edit_state;
|
|
if (e->show_selection || e->region_style) {
|
|
int mark = e->b->mark;
|
|
int offset = e->offset;
|
|
|
|
if ((offset1 >= offset && offset1 < mark) ||
|
|
(offset1 >= mark && offset1 < offset)) {
|
|
if (e->show_selection)
|
|
style |= QE_STYLE_SEL;
|
|
else
|
|
style = e->region_style;
|
|
}
|
|
}
|
|
/* special patch for selection in hex mode */
|
|
if (offset1 == offset2) {
|
|
offset1 = -1;
|
|
offset2 = -1;
|
|
}
|
|
|
|
space = (ch == ' ');
|
|
istab = (ch == '\t');
|
|
isaccent = qe_isaccent(ch);
|
|
/* a fragment is a part of word where style/embedding_level do not
|
|
change. For TAB, only one fragment containing it is sent */
|
|
if (ds->fragment_index >= 1) {
|
|
if (ds->fragment_index >= MAX_WORD_SIZE ||
|
|
istab ||
|
|
space != ds->last_space ||
|
|
style != ds->last_style ||
|
|
embedding_level != ds->last_embedding_level) {
|
|
/* flush the current fragment if needed */
|
|
if (isaccent && ds->fragment_chars[ds->fragment_index - 1] == ' ') {
|
|
/* separate last space to make it part of the next word */
|
|
int off1, off2, cur_hex;
|
|
--ds->fragment_index;
|
|
off1 = ds->fragment_offsets[ds->fragment_index][0];
|
|
off2 = ds->fragment_offsets[ds->fragment_index][1];
|
|
cur_hex = ds->fragment_hex_mode[ds->fragment_index];
|
|
flush_fragment(ds);
|
|
ds->fragment_chars[ds->fragment_index] = ' ';
|
|
ds->fragment_offsets[ds->fragment_index][0] = off1;
|
|
ds->fragment_offsets[ds->fragment_index][1] = off2;
|
|
ds->fragment_hex_mode[ds->fragment_index] = cur_hex;
|
|
ds->fragment_index++;
|
|
} else {
|
|
flush_fragment(ds);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isaccent && ds->fragment_index == 0) {
|
|
/* prepend a space if fragment starts with an accent */
|
|
ds->fragment_chars[ds->fragment_index] = ' ';
|
|
ds->fragment_offsets[ds->fragment_index][0] = offset1;
|
|
ds->fragment_offsets[ds->fragment_index][1] = offset2;
|
|
ds->fragment_hex_mode[ds->fragment_index] = ds->cur_hex_mode;
|
|
ds->fragment_index++;
|
|
offset1 = offset2 = -1;
|
|
}
|
|
/* store the char and its embedding level */
|
|
ds->fragment_chars[ds->fragment_index] = ch;
|
|
ds->fragment_offsets[ds->fragment_index][0] = offset1;
|
|
ds->fragment_offsets[ds->fragment_index][1] = offset2;
|
|
ds->fragment_hex_mode[ds->fragment_index] = ds->cur_hex_mode;
|
|
ds->fragment_index++;
|
|
|
|
ds->last_space = space;
|
|
ds->last_style = style;
|
|
ds->last_embedding_level = embedding_level;
|
|
|
|
if (istab) {
|
|
flush_fragment(ds);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void display_printhex(DisplayState *ds, int offset1, int offset2,
|
|
unsigned int h, int n)
|
|
{
|
|
int i, v;
|
|
EditState *e = ds->edit_state;
|
|
|
|
ds->cur_hex_mode = 1;
|
|
for (i = 0; i < n; i++) {
|
|
v = (h >> ((n - i - 1) * 4)) & 0xf;
|
|
if (v >= 10)
|
|
v += 'a' - 10;
|
|
else
|
|
v += '0';
|
|
/* XXX: simplistic */
|
|
if (e->hex_nibble == i) {
|
|
display_char(ds, offset1, offset2, v);
|
|
} else {
|
|
display_char(ds, offset1, offset1, v);
|
|
}
|
|
}
|
|
ds->cur_hex_mode = 0;
|
|
}
|
|
|
|
void display_printf(DisplayState *ds, int offset1, int offset2,
|
|
const char *fmt, ...)
|
|
{
|
|
char buf[256], *p;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
p = buf;
|
|
if (*p) {
|
|
/* XXX: utf-8 unsupported, not needed at this point */
|
|
display_char(ds, offset1, offset2, *p++);
|
|
while (*p) {
|
|
/* XXX: Should make these display character mouse selectable */
|
|
display_char(ds, -1, -1, *p++);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* end of line */
|
|
void display_eol(DisplayState *ds, int offset1, int offset2)
|
|
{
|
|
flush_fragment(ds);
|
|
|
|
/* note: the line may be empty */
|
|
flush_line(ds, ds->fragments, ds->nb_fragments, offset1, offset2, 1);
|
|
}
|
|
|
|
/* temporary function for backward compatibility */
|
|
static void display1(DisplayState *ds)
|
|
{
|
|
EditState *e = ds->edit_state;
|
|
int offset;
|
|
|
|
ds->eod = 0;
|
|
offset = e->offset_top;
|
|
for (;;) {
|
|
/* XXX: need early bailout from display_line if WRAP_TRUNCATE
|
|
and far beyond the right border after cursor found.
|
|
*/
|
|
offset = e->mode->display_line(e, ds, offset);
|
|
e->offset_bottom = offset;
|
|
|
|
/* EOF reached ? */
|
|
if (offset < 0)
|
|
break;
|
|
|
|
switch (ds->do_disp) {
|
|
case DISP_NONE:
|
|
return;
|
|
case DISP_CURSOR:
|
|
if (ds->eod)
|
|
return;
|
|
break;
|
|
case DISP_CURSOR_SCREEN:
|
|
if (ds->eod || ds->y >= ds->height)
|
|
return;
|
|
break;
|
|
case DISP_PRINT:
|
|
default:
|
|
if (ds->y >= ds->height)
|
|
return; /* end of screen */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************************************************/
|
|
int text_backward_offset(EditState *s, int offset)
|
|
{
|
|
int line, col;
|
|
|
|
/* CG: beware: offset may fall inside a character */
|
|
eb_get_pos(s->b, &line, &col, offset);
|
|
return eb_goto_pos(s->b, line, 0);
|
|
}
|
|
|
|
#ifdef CONFIG_UNICODE_JOIN
|
|
/* max_size should be >= 2 */
|
|
static int bidir_compute_attributes(TypeLink *list_tab, int max_size,
|
|
EditBuffer *b, int offset)
|
|
{
|
|
TypeLink *p;
|
|
FriBidiCharType type, ltype;
|
|
int left, offset1;
|
|
unsigned int c;
|
|
|
|
p = list_tab;
|
|
/* Add the starting link */
|
|
p->type = FRIBIDI_TYPE_SOT;
|
|
p->len = 0;
|
|
p->pos = 0;
|
|
p++;
|
|
left = max_size - 2;
|
|
|
|
ltype = FRIBIDI_TYPE_SOT;
|
|
|
|
for (;;) {
|
|
offset1 = offset;
|
|
c = eb_nextc(b, offset, &offset);
|
|
if (c == '\n')
|
|
break;
|
|
type = fribidi_get_type(c);
|
|
/* if not enough room, increment last link */
|
|
if (type != ltype && left > 0) {
|
|
p->type = type;
|
|
p->pos = offset1;
|
|
p->len = 1;
|
|
p++;
|
|
left--;
|
|
ltype = type;
|
|
} else {
|
|
p[-1].len++;
|
|
}
|
|
}
|
|
|
|
/* Add the ending link */
|
|
p->type = FRIBIDI_TYPE_EOT;
|
|
p->len = 0;
|
|
p->pos = offset1;
|
|
p++;
|
|
|
|
return p - list_tab;
|
|
}
|
|
#endif
|
|
|
|
/************************************************************/
|
|
/* colorization handling */
|
|
/* NOTE: only one colorization mode can be selected at a time for a
|
|
buffer */
|
|
|
|
static int get_staticly_colorized_line(EditState *s, unsigned int *buf, int buf_size,
|
|
QETermStyle *sbuf,
|
|
int offset, int *offset_ptr, int line_num)
|
|
{
|
|
EditBuffer *b = s->b;
|
|
unsigned int *buf_ptr, *buf_end;
|
|
|
|
buf_ptr = buf;
|
|
buf_end = buf + buf_size - 1;
|
|
for (;;) {
|
|
QETermStyle style = eb_get_style(b, offset);
|
|
int c = eb_nextc(b, offset, &offset);
|
|
if (c == '\n') {
|
|
/* XXX: set style for end of line? */
|
|
break;
|
|
}
|
|
if (buf_ptr < buf_end) {
|
|
sbuf[buf_ptr - buf] = style;
|
|
*buf_ptr++ = c;
|
|
}
|
|
}
|
|
*buf_ptr = '\0';
|
|
sbuf[buf_ptr - buf] = 0; /* end of line style? */
|
|
*offset_ptr = offset;
|
|
return buf_ptr - buf;
|
|
}
|
|
|
|
#ifndef CONFIG_TINY
|
|
|
|
/* Gets the colorized line beginning at 'offset'. Its length
|
|
excluding '\n' is returned */
|
|
|
|
#define COLORIZED_LINE_PREALLOC_SIZE 64
|
|
|
|
static int syntax_get_colorized_line(EditState *s,
|
|
unsigned int *buf, int buf_size,
|
|
QETermStyle *sbuf,
|
|
int offset, int *offsetp, int line_num)
|
|
{
|
|
QEColorizeContext cctx;
|
|
EditBuffer *b = s->b;
|
|
int i, len, line, n, col, bom;
|
|
|
|
/* invalidate cache if needed */
|
|
if (s->colorize_max_valid_offset != INT_MAX) {
|
|
eb_get_pos(b, &line, &col, s->colorize_max_valid_offset);
|
|
line++;
|
|
if (line < s->colorize_nb_valid_lines)
|
|
s->colorize_nb_valid_lines = line;
|
|
eb_delete_properties(b, s->colorize_max_valid_offset, INT_MAX);
|
|
s->colorize_max_valid_offset = INT_MAX;
|
|
}
|
|
|
|
/* realloc state array if needed */
|
|
if ((line_num + 2) > s->colorize_nb_lines) {
|
|
/* Reallocate colorization state buffer with pseudo-Fibonacci
|
|
* geometric progression (ratio of 1.625)
|
|
*/
|
|
n = max(s->colorize_nb_lines, COLORIZED_LINE_PREALLOC_SIZE);
|
|
while (n < (line_num + 2))
|
|
n += (n >> 1) + (n >> 3);
|
|
if (!qe_realloc(&s->colorize_states,
|
|
n * sizeof(*s->colorize_states))) {
|
|
return 0;
|
|
}
|
|
s->colorize_nb_lines = n;
|
|
}
|
|
|
|
memset(&cctx, 0, sizeof(cctx));
|
|
cctx.s = s;
|
|
cctx.b = b;
|
|
|
|
/* propagate state if needed */
|
|
if (line_num >= s->colorize_nb_valid_lines) {
|
|
if (s->colorize_nb_valid_lines == 0) {
|
|
s->colorize_states[0] = 0; /* initial state : zero */
|
|
s->colorize_nb_valid_lines = 1;
|
|
}
|
|
offset = eb_goto_pos(b, s->colorize_nb_valid_lines - 1, 0);
|
|
cctx.colorize_state = s->colorize_states[s->colorize_nb_valid_lines - 1];
|
|
cctx.state_only = 1;
|
|
|
|
for (line = s->colorize_nb_valid_lines; line <= line_num; line++) {
|
|
cctx.offset = offset;
|
|
len = eb_get_line(b, buf, buf_size - 1, offset, &offset);
|
|
if (buf[len] != '\n') {
|
|
/* line was truncated */
|
|
/* XXX: should use reallocatable buffer */
|
|
offset = eb_goto_pos(b, line, 0);
|
|
}
|
|
buf[len] = '\0';
|
|
|
|
/* skip byte order mark if present */
|
|
bom = (buf[0] == 0xFEFF);
|
|
if (bom) {
|
|
cctx.offset = eb_next(b, cctx.offset);
|
|
}
|
|
s->colorize_func(&cctx, buf + bom, len - bom, s->colorize_mode);
|
|
s->colorize_states[line] = cctx.colorize_state;
|
|
}
|
|
}
|
|
|
|
/* compute line color */
|
|
cctx.colorize_state = s->colorize_states[line_num];
|
|
cctx.state_only = 0;
|
|
cctx.offset = offset;
|
|
len = eb_get_line(b, buf, buf_size - 1, offset, offsetp);
|
|
if (buf[len] != '\n') {
|
|
/* line was truncated */
|
|
/* XXX: should use reallocatable buffer */
|
|
*offsetp = eb_next_line(b, offset);
|
|
}
|
|
buf[len] = '\0';
|
|
if (s->offset >= offset && s->offset < *offsetp) {
|
|
/* compute cursor position */
|
|
int offset1 = offset;
|
|
for (cctx.cur_pos = 0; offset1 < s->offset; cctx.cur_pos++)
|
|
offset1 = eb_next(b, offset1);
|
|
}
|
|
|
|
bom = (buf[0] == 0xFEFF);
|
|
if (bom) {
|
|
SET_COLOR1(buf, 0, QE_STYLE_PREPROCESS);
|
|
cctx.offset = eb_next(b, cctx.offset);
|
|
}
|
|
cctx.combine_stop = len - bom;
|
|
cctx.cur_pos -= bom;
|
|
s->colorize_func(&cctx, buf + bom, len - bom, s->colorize_mode);
|
|
cctx.cur_pos += bom;
|
|
/* buf[len] has char '\0' but may hold style, force buf ending */
|
|
buf[len + 1] = 0;
|
|
|
|
/* XXX: if state is same as previous, minimize invalid region? */
|
|
s->colorize_states[line_num + 1] = cctx.colorize_state;
|
|
|
|
/* Extend valid area */
|
|
if (s->colorize_nb_valid_lines < line_num + 2)
|
|
s->colorize_nb_valid_lines = line_num + 2;
|
|
|
|
/* Extract styles from colored codepoint array */
|
|
for (i = 0; i <= len + 1; i++) {
|
|
sbuf[i] = buf[i] >> STYLE_SHIFT;
|
|
buf[i] &= CHAR_MASK;
|
|
}
|
|
|
|
/* Combine with buffer styles on restricted range */
|
|
if (s->b->b_styles) {
|
|
int i, start = bom + cctx.combine_start, stop = bom + cctx.combine_stop;
|
|
int offset = cctx.offset;
|
|
for (i = bom; i < stop; i++) {
|
|
QETermStyle style = eb_get_style(b, offset);
|
|
if (style && i >= start) {
|
|
sbuf[i] = style;
|
|
}
|
|
offset = eb_next(b, offset);
|
|
}
|
|
}
|
|
if (!(s->colorize_mode->flags & MODEF_NO_TRAILING_BLANKS)) {
|
|
/* Mark trailing blanks as errors if cursor is not at end of line */
|
|
for (i = len; i > 0 && qe_isblank(buf[i - 1]) && i != cctx.cur_pos; i--) {
|
|
sbuf[i - 1] = QE_STYLE_BLANK_HILITE;
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/* invalidate the colorize data */
|
|
static void colorize_callback(qe__unused__ EditBuffer *b,
|
|
void *opaque, qe__unused__ int arg,
|
|
qe__unused__ enum LogOperation op,
|
|
int offset,
|
|
qe__unused__ int size)
|
|
{
|
|
EditState *e = opaque;
|
|
|
|
if (offset < e->colorize_max_valid_offset)
|
|
e->colorize_max_valid_offset = offset;
|
|
}
|
|
|
|
#endif /* CONFIG_TINY */
|
|
|
|
void set_colorize_func(EditState *s, ColorizeFunc colorize_func, ModeDef *colorize_mode)
|
|
{
|
|
s->colorize_func = NULL;
|
|
|
|
#ifndef CONFIG_TINY
|
|
/* invalidate the previous states & free previous colorizer */
|
|
eb_free_callback(s->b, colorize_callback, s);
|
|
qe_free(&s->colorize_states);
|
|
s->colorize_nb_lines = 0;
|
|
s->colorize_nb_valid_lines = 0;
|
|
s->colorize_max_valid_offset = INT_MAX;
|
|
s->colorize_func = colorize_func;
|
|
s->colorize_mode = colorize_mode;
|
|
if (colorize_func)
|
|
eb_add_callback(s->b, colorize_callback, s, 0);
|
|
#endif
|
|
}
|
|
|
|
int get_colorized_line(EditState *s, unsigned int *buf, int buf_size,
|
|
QETermStyle *sbuf,
|
|
int offset, int *offsetp, int line_num)
|
|
{
|
|
#ifndef CONFIG_TINY
|
|
if (s->colorize_func) {
|
|
return syntax_get_colorized_line(s, buf, buf_size, sbuf,
|
|
offset, offsetp, line_num);
|
|
} else
|
|
#endif
|
|
if (s->b->b_styles) {
|
|
return get_staticly_colorized_line(s, buf, buf_size, sbuf,
|
|
offset, offsetp, line_num);
|
|
} else {
|
|
int len = eb_get_line(s->b, buf, buf_size, offset, offsetp);
|
|
if (buf[len] != '\n') {
|
|
/* line was truncated */
|
|
/* XXX: should use reallocatable buffer */
|
|
*offsetp = eb_goto_pos(s->b, line_num + 1, 0);
|
|
}
|
|
buf[len] = '\0';
|
|
if (sbuf) {
|
|
memset(sbuf, 0, (len + 1) * sizeof(*sbuf));
|
|
}
|
|
return len;
|
|
}
|
|
}
|
|
|
|
#define RLE_EMBEDDINGS_SIZE 128
|
|
|
|
/* Display one line in the window */
|
|
int text_display_line(EditState *s, DisplayState *ds, int offset)
|
|
{
|
|
int c;
|
|
int offset0, offset1, line_num, col_num;
|
|
TypeLink embeds[RLE_EMBEDDINGS_SIZE], *bd;
|
|
int embedding_level, embedding_max_level;
|
|
FriBidiCharType base;
|
|
unsigned int buf[COLORED_MAX_LINE_SIZE];
|
|
QETermStyle sbuf[COLORED_MAX_LINE_SIZE];
|
|
int i, char_index, colored_nb_chars;
|
|
|
|
line_num = 0;
|
|
/* XXX: should test a flag, to avoid this call in hex/binary */
|
|
if (s->line_numbers || s->colorize_func) {
|
|
eb_get_pos(s->b, &line_num, &col_num, offset);
|
|
}
|
|
|
|
offset1 = offset;
|
|
|
|
#ifdef CONFIG_UNICODE_JOIN
|
|
/* compute the embedding levels and rle encode them */
|
|
if (s->bidir
|
|
&& bidir_compute_attributes(embeds, RLE_EMBEDDINGS_SIZE,
|
|
s->b, offset) > 2)
|
|
{
|
|
base = FRIBIDI_TYPE_WL;
|
|
fribidi_analyse_string(embeds, &base, &embedding_max_level);
|
|
/* assure that base has only two possible values */
|
|
if (base != FRIBIDI_TYPE_RTL)
|
|
base = FRIBIDI_TYPE_LTR;
|
|
} else
|
|
#endif
|
|
{
|
|
/* all line is at embedding level 0 */
|
|
embedding_max_level = 0;
|
|
embeds[1].level = 0;
|
|
embeds[2].pos = 0x7fffffff;
|
|
base = FRIBIDI_TYPE_LTR;
|
|
}
|
|
|
|
display_bol_bidir(ds, base, embedding_max_level);
|
|
|
|
/* line numbers */
|
|
if (ds->line_numbers) {
|
|
ds->style = QE_STYLE_GUTTER;
|
|
display_printf(ds, -1, -1, "%6d ", line_num + 1);
|
|
ds->style = 0;
|
|
}
|
|
|
|
/* prompt display, only on first line */
|
|
if (s->prompt && offset1 == 0) {
|
|
const char *p = s->prompt;
|
|
|
|
while (*p) {
|
|
display_char(ds, -1, -1, utf8_decode(&p));
|
|
}
|
|
}
|
|
|
|
/* colorize */
|
|
colored_nb_chars = 0;
|
|
offset0 = offset;
|
|
if (s->colorize_func || s->b->b_styles
|
|
|| s->curline_style || s->region_style
|
|
|| s->isearch_state) {
|
|
/* XXX: deal with truncation */
|
|
colored_nb_chars = get_colorized_line(s, buf, countof(buf), sbuf,
|
|
offset, &offset0, line_num);
|
|
if (s->mode == &list_mode) {
|
|
QEmacsState *qs = s->qe_state;
|
|
int i;
|
|
|
|
if ((qs->active_window == s || s->force_highlight) &&
|
|
s->offset >= offset && s->offset < offset0)
|
|
{
|
|
/* highlight the current line */
|
|
for (i = 0; i <= colored_nb_chars; i++) {
|
|
sbuf[i] = QE_STYLE_HIGHLIGHT;
|
|
}
|
|
} else
|
|
if (buf[0] == '*') {
|
|
/* selection */
|
|
for (i = 0; i <= colored_nb_chars; i++) {
|
|
sbuf[i] |= QE_STYLE_SEL;
|
|
}
|
|
}
|
|
}
|
|
if (s->isearch_state) {
|
|
isearch_colorize_matches(s, buf, colored_nb_chars, sbuf, offset);
|
|
}
|
|
}
|
|
|
|
#if 1
|
|
/* colorize regions */
|
|
if (s->curline_style || s->region_style) {
|
|
/* CG: Should combine styles instead of replacing */
|
|
if (s->region_style && !s->curline_style) {
|
|
int line, start_offset, end_offset;
|
|
int i, start_char, end_char;
|
|
|
|
if (s->b->mark < s->offset) {
|
|
start_offset = max(offset, s->b->mark);
|
|
end_offset = min(offset0, s->offset);
|
|
} else {
|
|
start_offset = max(offset, s->offset);
|
|
end_offset = min(offset0, s->b->mark);
|
|
}
|
|
if (start_offset < end_offset) {
|
|
/* Compute character positions */
|
|
eb_get_pos(s->b, &line, &start_char, start_offset);
|
|
if (end_offset >= offset0)
|
|
end_char = colored_nb_chars;
|
|
else
|
|
eb_get_pos(s->b, &line, &end_char, end_offset);
|
|
|
|
for (i = start_char; i < end_char; i++) {
|
|
sbuf[i] = s->region_style;
|
|
}
|
|
}
|
|
} else
|
|
if (s->curline_style && s->offset >= offset && s->offset <= offset0) {
|
|
/* XXX: only if qs->active_window == s ? */
|
|
for (i = 0; i < colored_nb_chars; i++)
|
|
sbuf[i] = s->curline_style;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bd = embeds + 1;
|
|
char_index = 0;
|
|
for (;;) {
|
|
offset0 = offset;
|
|
if (offset >= s->b->total_size) {
|
|
/* the offset passed here is for cursor positioning
|
|
when s->offset == s->b->total_size.
|
|
*/
|
|
display_eol(ds, offset0, offset0 + 1);
|
|
offset = -1; /* signal end of text */
|
|
break;
|
|
} else {
|
|
ds->style = 0;
|
|
if (char_index < colored_nb_chars) {
|
|
ds->style = sbuf[char_index];
|
|
}
|
|
c = eb_nextc(s->b, offset, &offset);
|
|
if (c == '\n' && !(s->flags & WF_MINIBUF)) {
|
|
display_eol(ds, offset0, offset);
|
|
break;
|
|
}
|
|
/* compute embedding from RLE embedding list */
|
|
if (offset0 >= bd[1].pos)
|
|
bd++;
|
|
embedding_level = bd[0].level;
|
|
/* XXX: use embedding level for all cases ? */
|
|
/* CG: should query screen or window about display methods */
|
|
if ((c < ' ' && (c != '\t' || (s->flags & WF_MINIBUF))) || c == 127) {
|
|
if (c == '\r' && s->b->eol_type == EOL_MAC)
|
|
c = '\n';
|
|
display_printf(ds, offset0, offset, "^%c", ('@' + c) & 127);
|
|
} else
|
|
if (c >= 128
|
|
&& (s->qe_state->show_unicode == 1 ||
|
|
c == 0xfeff || /* Display BOM as \uFEFF to make it explicit */
|
|
c > MAX_UNICODE_DISPLAY ||
|
|
(c < 160 && s->b->charset == &charset_raw))) {
|
|
/* display unsupported unicode code points as hex */
|
|
if (c > 0xffff) {
|
|
display_printf(ds, offset0, offset, "\\U%08x", c);
|
|
} else
|
|
if (c > 0xff) {
|
|
display_printf(ds, offset0, offset, "\\u%04x", c);
|
|
} else {
|
|
display_printf(ds, offset0, offset, "\\x%02x", c);
|
|
}
|
|
} else {
|
|
display_char_bidir(ds, offset0, offset, embedding_level, c);
|
|
}
|
|
char_index++;
|
|
//if (ds->y >= s->height && ds->eod) //@@@ causes bug
|
|
// break;
|
|
}
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
/* Generic display algorithm with automatic fit */
|
|
static void generic_text_display(EditState *s)
|
|
{
|
|
CursorContext m1, *m = &m1;
|
|
DisplayState ds1, *ds = &ds1;
|
|
int x1, xc, yc, offset, bottom = -1;
|
|
|
|
if (s->offset == 0) {
|
|
s->offset_top = s->y_disp = s->x_disp[0] = s->x_disp[1] = 0;
|
|
}
|
|
|
|
/* if the cursor is before the top of the display zone, we must
|
|
resync backward */
|
|
if (s->offset < s->offset_top) {
|
|
s->offset_top = s->mode->backward_offset(s, s->offset);
|
|
///XXXX probably too strong, should keep cursor close to top
|
|
//s->y_disp = 0; //@@@?
|
|
}
|
|
|
|
if (s->display_invalid) {
|
|
/* invalidate the line shadow buffer */
|
|
qe_free(&s->line_shadow);
|
|
s->shadow_nb_lines = 0;
|
|
s->display_invalid = 0;
|
|
}
|
|
|
|
/* find cursor position with the current x_disp & y_disp and
|
|
update y_disp so that we display only the needed lines */
|
|
/* XXX: should update x_disp, y_disp to bring the cursor closest
|
|
to the center, top or bottom of the screen depending on
|
|
window movement */
|
|
memset(m, 0, sizeof(*m));
|
|
m->offsetc = s->offset;
|
|
m->xc = m->yc = NO_CURSOR;
|
|
display_init(ds, s, DISP_CURSOR_SCREEN, cursor_func, m);
|
|
offset = s->offset_top;
|
|
for (;;) {
|
|
if (ds->y <= 0) {
|
|
s->offset_top = offset;
|
|
s->y_disp = ds->y;
|
|
}
|
|
offset = s->mode->display_line(s, ds, offset);
|
|
s->offset_bottom = offset;
|
|
if (offset < 0 || ds->y >= s->height || m->xc != NO_CURSOR)
|
|
break;
|
|
}
|
|
display_close(ds);
|
|
|
|
//printf("cursor: xc=%d yc=%d linec=%d\n", m->xc, m->yc, m->linec);
|
|
if (m->xc == NO_CURSOR) {
|
|
/* if no cursor found then we compute offset_top so that we
|
|
have a chance to find the cursor in a small amount of time */
|
|
display_init(ds, s, DISP_CURSOR_SCREEN, cursor_func, m);
|
|
ds->y = 0;
|
|
offset = s->mode->backward_offset(s, s->offset);
|
|
bottom = s->mode->display_line(s, ds, offset);
|
|
if (m->xc == NO_CURSOR) {
|
|
/* XXX: should not happen */
|
|
put_error(NULL, "ERROR: cursor not found");
|
|
ds->y = 0;
|
|
} else {
|
|
ds->y = m->yc + m->cursor_height;
|
|
}
|
|
|
|
while (ds->y < s->height && offset > 0) {
|
|
offset = eb_prev(s->b, offset);
|
|
offset = s->mode->backward_offset(s, offset);
|
|
bottom = s->mode->display_line(s, ds, offset);
|
|
}
|
|
s->offset_top = offset;
|
|
s->offset_bottom = bottom;
|
|
/* adjust y_disp so that the cursor is at the bottom of the screen */
|
|
s->y_disp = min(s->height - ds->y, 0);
|
|
display_close(ds);
|
|
} else {
|
|
yc = m->yc;
|
|
if (yc < 0) {
|
|
s->y_disp += -yc;
|
|
} else
|
|
if ((yc + m->cursor_height) > s->height) {
|
|
s->y_disp += s->height - (yc + m->cursor_height);
|
|
}
|
|
}
|
|
|
|
/* update x cursor position if needed. Note that we distinguish
|
|
between rtl and ltr margins. We try to have x_disp == 0 as much
|
|
as possible */
|
|
if (ds->wrap == WRAP_TRUNCATE) {
|
|
#if 1 //@@@?
|
|
//put_status(s, "|xc=%d x_disp+%d", m->xc, s->x_disp[m->basec]);
|
|
if (m->xc != NO_CURSOR) {
|
|
xc = m->xc;
|
|
x1 = xc - s->x_disp[m->basec];
|
|
// Do not snap x_disp to 0 to allow scroll_left()
|
|
//if (x1 >= 0 && x1 < ds->width) {
|
|
// s->x_disp[m->basec] = 0;
|
|
//} else
|
|
if (xc < 0) {
|
|
/* XXX: refering to ds after display_close(ds) */
|
|
if (x1 >= 0 && x1 < ds->width) {
|
|
/* snap back to left margin */
|
|
s->x_disp[m->basec] = 0;
|
|
} else {
|
|
/* XXX: should center screen horizontally? */
|
|
/* XXX: maybe scroll horizontally by a quarter screen? */
|
|
s->x_disp[m->basec] += -xc;
|
|
}
|
|
} else
|
|
if (xc + m->cursor_width >= ds->width) {
|
|
/* XXX: maybe scroll horizontally by a quarter screen? */
|
|
s->x_disp[m->basec] += ds->width - (xc + m->cursor_width);
|
|
}
|
|
}
|
|
#else
|
|
xc = m->xc;
|
|
x1 = xc - s->x_disp[m->basec];
|
|
if (x1 >= 0 && x1 < ds->width - ds->eol_width) {
|
|
s->x_disp[m->basec] = 0;
|
|
} else
|
|
if (xc < 0) {
|
|
s->x_disp[m->basec] -= xc;
|
|
} else
|
|
if (xc >= ds->width) {
|
|
s->x_disp[m->basec] += ds->width - xc - ds->eol_width;
|
|
}
|
|
#endif
|
|
} else {
|
|
s->x_disp[0] = 0;
|
|
s->x_disp[1] = 0;
|
|
}
|
|
|
|
/* now we can display the text and get the real cursor position ! */
|
|
|
|
m->offsetc = s->offset;
|
|
m->xc = m->yc = NO_CURSOR;
|
|
display_init(ds, s, DISP_PRINT, cursor_func, m);
|
|
display1(ds);
|
|
/* display the remaining region */
|
|
if (ds->y < s->height) {
|
|
QEStyleDef default_style;
|
|
get_style(s, &default_style, QE_STYLE_DEFAULT);
|
|
fill_rectangle(s->screen, s->xleft, s->ytop + ds->y,
|
|
s->width, s->height - ds->y,
|
|
default_style.bg_color);
|
|
if (ds->line_num >= 0 && ds->line_num < s->shadow_nb_lines) {
|
|
/* erase the line shadow for the rest of the window */
|
|
memset(&s->line_shadow[ds->line_num], 0xff,
|
|
(s->shadow_nb_lines - ds->line_num) * sizeof(QELineShadow));
|
|
}
|
|
}
|
|
display_close(ds);
|
|
|
|
xc = m->xc;
|
|
yc = m->yc;
|
|
|
|
if (xc != NO_CURSOR && yc != NO_CURSOR
|
|
&& s->qe_state->active_window == s) {
|
|
int x, y, w, h;
|
|
x = s->xleft + xc;
|
|
y = s->ytop + yc;
|
|
w = m->cursor_width;
|
|
h = m->cursor_height;
|
|
if (s->screen->dpy.dpy_cursor_at) {
|
|
/* hardware cursor */
|
|
s->screen->dpy.dpy_cursor_at(s->screen, x, y, w, h);
|
|
} else {
|
|
/* software cursor */
|
|
if (w < 0) {
|
|
x += w;
|
|
w = -w;
|
|
}
|
|
xor_rectangle(s->screen, x, y, w, h, QERGB(0xFF, 0xFF, 0xFF));
|
|
if (m->linec >= 0 && m->linec < s->shadow_nb_lines) {
|
|
/* invalidate line so that the cursor will be erased next time */
|
|
memset(&s->line_shadow[m->linec], 0xff, sizeof(QELineShadow));
|
|
}
|
|
}
|
|
}
|
|
s->cur_rtl = (m->dirc == DIR_RTL);
|
|
#if 0
|
|
printf("cursor1: xc=%d yc=%d w=%d h=%d linec=%d\n",
|
|
m->xc, m->yc, m->cursor_width, m->cursor_height, m->linec);
|
|
#endif
|
|
}
|
|
|
|
typedef struct ExecCmdState {
|
|
EditState *s;
|
|
CmdDef *d;
|
|
int nb_args;
|
|
int argval;
|
|
int key;
|
|
const char *ptype;
|
|
CmdArg args[MAX_CMD_ARGS];
|
|
unsigned char args_type[MAX_CMD_ARGS];
|
|
char default_input[512]; /* default input if none given */
|
|
} ExecCmdState;
|
|
|
|
/* Signature based dispatcher.
|
|
So far 144 qemacs commands have these signatures:
|
|
- void (*)(EditState *); (68)
|
|
- void (*)(EditState *, int); (35)
|
|
- void (*)(EditState *, const char *); (19)
|
|
- void (*)(EditState *, int, int); (2)
|
|
- void (*)(EditState *, const char *, int); (2)
|
|
- void (*)(EditState *, const char *, const char *); (6)
|
|
- void (*)(EditState *, const char *, const char *, const char *); (2)
|
|
- void (*)(EditState *, const char *, const char *, int); (2)
|
|
*/
|
|
void call_func(CmdSig sig, CmdProto func, qe__unused__ int nb_args,
|
|
CmdArg *args, qe__unused__ unsigned char *args_type)
|
|
{
|
|
switch (sig) {
|
|
case CMD_void:
|
|
(*func.func)();
|
|
break;
|
|
case CMD_ES: /* ES, no other arguments */
|
|
(*func.ES)(args[0].s);
|
|
break;
|
|
case CMD_ESi: /* ES + integer */
|
|
(*func.ESi)(args[0].s, args[1].n);
|
|
break;
|
|
case CMD_ESs: /* ES + string */
|
|
(*func.ESs)(args[0].s, args[1].p);
|
|
break;
|
|
case CMD_ESss: /* ES + string + string */
|
|
(*func.ESss)(args[0].s, args[1].p, args[2].p);
|
|
break;
|
|
case CMD_ESsi: /* ES + string + integer */
|
|
(*func.ESsi)(args[0].s, args[1].p, args[2].n);
|
|
break;
|
|
case CMD_ESii: /* ES + integer + integer */
|
|
(*func.ESii)(args[0].s, args[1].n, args[2].n);
|
|
break;
|
|
case CMD_ESssi: /* ES + string + string + integer */
|
|
(*func.ESssi)(args[0].s, args[1].p, args[2].p, args[3].n);
|
|
break;
|
|
case CMD_ESsss: /* ES + string + string + string */
|
|
(*func.ESsss)(args[0].s, args[1].p, args[2].p, args[3].p);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void get_param(const char **pp, char *param, int param_size, int osep, int sep)
|
|
{
|
|
const char *p;
|
|
char *q;
|
|
|
|
param_size--;
|
|
p = *pp;
|
|
if (*p == osep) {
|
|
p++;
|
|
if (param) {
|
|
q = param;
|
|
while (*p != sep && *p != '\0') {
|
|
if ((q - param) < param_size)
|
|
*q++ = *p;
|
|
p++;
|
|
}
|
|
*q = '\0';
|
|
} else {
|
|
while (*p != sep && *p != '\0')
|
|
p++;
|
|
}
|
|
if (*p == sep)
|
|
p++;
|
|
} else {
|
|
if (param)
|
|
param[0] = '\0';
|
|
}
|
|
*pp = p;
|
|
}
|
|
|
|
/* return -1 if error, 0 if no more args, 1 if one arg parsed */
|
|
int parse_arg(const char **pp, CmdArgSpec *ap)
|
|
{
|
|
int tc, type;
|
|
const char *p;
|
|
|
|
p = *pp;
|
|
if (*p == '\0')
|
|
return 0;
|
|
tc = *p++;
|
|
get_param(&p, ap->prompt, sizeof(ap->prompt), '{', '}');
|
|
get_param(&p, ap->completion, sizeof(ap->completion), '[', ']');
|
|
get_param(&p, ap->history, sizeof(ap->history), '|', '|');
|
|
type = 0;
|
|
switch (tc) {
|
|
case 'k':
|
|
type = CMD_ARG_USE_KEY | CMD_ARG_INT;
|
|
break;
|
|
case 'p':
|
|
type = CMD_ARG_USE_ARGVAL | CMD_ARG_INT;
|
|
break;
|
|
case 'P':
|
|
type = CMD_ARG_MUL_ARGVAL | CMD_ARG_INT;
|
|
break;
|
|
case 'm':
|
|
type = CMD_ARG_USE_MARK | CMD_ARG_INT;
|
|
break;
|
|
case 'd':
|
|
type = CMD_ARG_USE_POINT | CMD_ARG_INT;
|
|
break;
|
|
case 'z':
|
|
type = CMD_ARG_USE_ZERO | CMD_ARG_INT;
|
|
break;
|
|
case 'e':
|
|
type = CMD_ARG_USE_BSIZE | CMD_ARG_INT;
|
|
break;
|
|
case 'n':
|
|
type = CMD_ARG_INT;
|
|
break;
|
|
case 'v':
|
|
type = CMD_ARG_INTVAL;
|
|
break;
|
|
case 's':
|
|
type = CMD_ARG_STRING;
|
|
break;
|
|
case 'S': /* used in define_kbd_macro, and mode selection */
|
|
/* actual string is in prompt buffer */
|
|
type = CMD_ARG_STRINGVAL;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
*pp = p;
|
|
ap->arg_type = type;
|
|
return 1;
|
|
}
|
|
|
|
int qe_get_prototype(CmdDef *d, char *buf, int size)
|
|
{
|
|
buf_t outbuf, *out;
|
|
const char *r;
|
|
const char *sep = "";
|
|
CmdArgSpec cas;
|
|
|
|
out = buf_init(&outbuf, buf, size);
|
|
|
|
buf_put_byte(out, '(');
|
|
|
|
/* construct argument type list */
|
|
r = d->spec;
|
|
if (*r == '*') {
|
|
r++; /* buffer modification indicator */
|
|
}
|
|
|
|
while (parse_arg(&r, &cas) > 0) {
|
|
switch (cas.arg_type & CMD_ARG_TYPE_MASK) {
|
|
case CMD_ARG_INT:
|
|
buf_printf(out, "%sint ", sep);
|
|
break;
|
|
case CMD_ARG_STRING:
|
|
buf_printf(out, "%sstring ", sep);
|
|
break;
|
|
case CMD_ARG_WINDOW:
|
|
case CMD_ARG_INTVAL:
|
|
case CMD_ARG_STRINGVAL:
|
|
default:
|
|
continue;
|
|
}
|
|
sep = ", ";
|
|
switch (cas.arg_type & ~CMD_ARG_TYPE_MASK) {
|
|
case CMD_ARG_USE_ARGVAL:
|
|
case CMD_ARG_MUL_ARGVAL:
|
|
buf_puts(out, "= argval");
|
|
break;
|
|
case CMD_ARG_USE_KEY:
|
|
buf_puts(out, "= key");
|
|
break;
|
|
case CMD_ARG_USE_MARK:
|
|
buf_puts(out, "= mark");
|
|
break;
|
|
case CMD_ARG_USE_POINT:
|
|
buf_puts(out, "= point");
|
|
break;
|
|
case CMD_ARG_USE_ZERO:
|
|
buf_puts(out, "= 0");
|
|
break;
|
|
case CMD_ARG_USE_BSIZE:
|
|
buf_puts(out, "= bufsize");
|
|
break;
|
|
default:
|
|
buf_puts(out, *cas.history ? cas.history : cas.completion);
|
|
break;
|
|
}
|
|
}
|
|
buf_put_byte(out, ')');
|
|
return out->len;
|
|
}
|
|
|
|
static void arg_edit_cb(void *opaque, char *str);
|
|
static void parse_arguments(ExecCmdState *es);
|
|
static void free_cmd(ExecCmdState **esp);
|
|
|
|
void exec_command(EditState *s, CmdDef *d, int argval, int key)
|
|
{
|
|
ExecCmdState *es;
|
|
const char *argdesc;
|
|
|
|
if (qe_state.trace_buffer && qe_state.trace_buffer != s->b)
|
|
eb_trace_bytes(d->name, -1, EB_TRACE_COMMAND);
|
|
|
|
argdesc = d->spec;
|
|
if (*argdesc == '*') {
|
|
argdesc++;
|
|
if (s->b->flags & BF_READONLY) {
|
|
put_status(s, "Buffer is read only");
|
|
return;
|
|
}
|
|
}
|
|
|
|
es = qe_mallocz(ExecCmdState);
|
|
if (!es)
|
|
return;
|
|
|
|
es->s = s;
|
|
es->d = d;
|
|
es->argval = argval;
|
|
es->key = key;
|
|
es->nb_args = 0;
|
|
|
|
/* first argument is always the window */
|
|
es->args[0].s = s;
|
|
es->args_type[0] = CMD_ARG_WINDOW;
|
|
es->nb_args++;
|
|
es->ptype = argdesc;
|
|
|
|
parse_arguments(es);
|
|
}
|
|
|
|
/* parse as much arguments as possible. ask value to user if possible */
|
|
static void parse_arguments(ExecCmdState *es)
|
|
{
|
|
EditState *s = es->s;
|
|
QEmacsState *qs = s->qe_state;
|
|
QErrorContext ec;
|
|
CmdDef *d = es->d;
|
|
CmdArgSpec cas;
|
|
int ret, rep_count, get_arg, type, use_flag;
|
|
int elapsed_time;
|
|
|
|
while ((ret = parse_arg(&es->ptype, &cas)) != 0) {
|
|
if (ret < 0 || es->nb_args >= MAX_CMD_ARGS)
|
|
goto fail;
|
|
use_flag = cas.arg_type & ~CMD_ARG_TYPE_MASK;
|
|
type = cas.arg_type & CMD_ARG_TYPE_MASK;
|
|
es->args_type[es->nb_args] = type;
|
|
get_arg = 0;
|
|
switch (type) {
|
|
case CMD_ARG_INTVAL:
|
|
es->args[es->nb_args].n = d->val;
|
|
break;
|
|
case CMD_ARG_STRINGVAL:
|
|
/* CG: kludge for xxx-mode functions and named kbd macros,
|
|
must be last argument */
|
|
es->args[es->nb_args].p = cas.prompt;
|
|
break;
|
|
case CMD_ARG_INT:
|
|
if (use_flag == CMD_ARG_USE_KEY) {
|
|
es->args[es->nb_args].n = es->key;
|
|
} else
|
|
if (use_flag == CMD_ARG_USE_MARK) {
|
|
es->args[es->nb_args].n = s->b->mark;
|
|
} else
|
|
if (use_flag == CMD_ARG_USE_POINT) {
|
|
es->args[es->nb_args].n = s->offset;
|
|
} else
|
|
if (use_flag == CMD_ARG_USE_ZERO) {
|
|
es->args[es->nb_args].n = 0;
|
|
} else
|
|
if (use_flag == CMD_ARG_USE_BSIZE) {
|
|
es->args[es->nb_args].n = s->b->total_size;
|
|
} else
|
|
if (use_flag == CMD_ARG_USE_ARGVAL && es->argval != NO_ARG) {
|
|
es->args[es->nb_args].n = es->argval;
|
|
es->argval = NO_ARG;
|
|
} else
|
|
if (use_flag == CMD_ARG_MUL_ARGVAL) {
|
|
es->args[es->nb_args].n = d->val * ((es->argval == NO_ARG) ? 1 : es->argval);
|
|
es->argval = NO_ARG;
|
|
} else {
|
|
/* CG: Should add syntax for default value if no prompt */
|
|
es->args[es->nb_args].n = NO_ARG;
|
|
get_arg = 1;
|
|
}
|
|
break;
|
|
case CMD_ARG_STRING:
|
|
#if 0
|
|
if (use_flag == CMD_ARG_USE_ARGVAL && es->argval != NO_ARG) {
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "%d", es->argval);
|
|
es->args[es->nb_args].p = qe_strdup(buf); // XXX: memory leak?
|
|
es->argval = NO_ARG;
|
|
} else
|
|
#endif
|
|
{
|
|
es->args[es->nb_args].p = NULL;
|
|
get_arg = 1;
|
|
break;
|
|
}
|
|
}
|
|
es->nb_args++;
|
|
/* if no argument specified, try to ask it to the user */
|
|
if (get_arg && cas.prompt[0] != '\0') {
|
|
char def_input[1024];
|
|
|
|
/* XXX: currently, default input is handled non generically */
|
|
/* XXX: should use completion function for default input? */
|
|
def_input[0] = '\0';
|
|
es->default_input[0] = '\0';
|
|
if (strequal(cas.completion, "file") || strequal(cas.completion, "dir")) {
|
|
get_default_path(s->b, s->offset, def_input, sizeof(def_input));
|
|
} else
|
|
if (strequal(cas.completion, "buffer")) {
|
|
EditBuffer *b;
|
|
if (d->action.ESs == do_switch_to_buffer)
|
|
b = predict_switch_to_buffer(s);
|
|
else
|
|
b = s->b;
|
|
pstrcpy(es->default_input, sizeof(es->default_input), b->name);
|
|
}
|
|
if (es->default_input[0] != '\0') {
|
|
pstrcat(cas.prompt, sizeof(cas.prompt), "(default ");
|
|
pstrcat(cas.prompt, sizeof(cas.prompt), es->default_input);
|
|
pstrcat(cas.prompt, sizeof(cas.prompt), ") ");
|
|
}
|
|
minibuffer_edit(s, def_input, cas.prompt, get_history(cas.history),
|
|
cas.completion, arg_edit_cb, es);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* all arguments are parsed: we can now execute the command */
|
|
/* argval is handled as repetition count if not taken as argument */
|
|
if (es->argval != NO_ARG && es->argval > 1) {
|
|
rep_count = es->argval;
|
|
} else {
|
|
rep_count = 1;
|
|
}
|
|
|
|
qs->this_cmd_func = d->action.func;
|
|
qs->cmd_start_time = get_clock_ms();
|
|
|
|
while (rep_count--) {
|
|
/* special case for hex mode */
|
|
if (d->action.ESii != do_char) {
|
|
s->hex_nibble = 0;
|
|
/* special case for character composing */
|
|
if (d->action.ESi != do_backspace)
|
|
s->compose_len = 0;
|
|
}
|
|
#ifndef CONFIG_TINY
|
|
save_selection();
|
|
#endif
|
|
/* Save and restore ec context */
|
|
ec = qs->ec;
|
|
qs->ec.function = d->name;
|
|
call_func(d->sig, d->action, es->nb_args, es->args, es->args_type);
|
|
qs->ec = ec;
|
|
/* CG: This doesn't work if the function needs input */
|
|
/* CG: Should test for abort condition */
|
|
/* CG: Should follow qs->active_window ? */
|
|
}
|
|
|
|
elapsed_time = get_clock_ms() - qs->cmd_start_time;
|
|
qs->cmd_start_time += elapsed_time;
|
|
if (elapsed_time >= 100)
|
|
put_status(s, "|%s: %dms", d->name, elapsed_time);
|
|
|
|
qs->last_cmd_func = qs->this_cmd_func;
|
|
fail:
|
|
free_cmd(&es);
|
|
}
|
|
|
|
static void free_cmd(ExecCmdState **esp)
|
|
{
|
|
if (*esp) {
|
|
ExecCmdState *es = *esp;
|
|
int i;
|
|
|
|
/* free allocated parameters */
|
|
for (i = 0; i < es->nb_args; i++) {
|
|
switch (es->args_type[i]) {
|
|
case CMD_ARG_STRING:
|
|
qe_free((char **)&es->args[i].p);
|
|
break;
|
|
}
|
|
}
|
|
qe_free(esp);
|
|
}
|
|
}
|
|
|
|
/* when the argument has been typed by the user, this callback is
|
|
called */
|
|
static void arg_edit_cb(void *opaque, char *str)
|
|
{
|
|
ExecCmdState *es = opaque;
|
|
int index, val;
|
|
char *p;
|
|
|
|
if (!str) {
|
|
/* command aborted */
|
|
fail:
|
|
qe_free(&str);
|
|
free_cmd(&es);
|
|
return;
|
|
}
|
|
index = es->nb_args - 1;
|
|
switch (es->args_type[index]) {
|
|
case CMD_ARG_INT:
|
|
val = strtol(str, &p, 0);
|
|
if (*p != '\0') {
|
|
put_status(NULL, "Invalid number");
|
|
goto fail;
|
|
}
|
|
es->args[index].n = val;
|
|
break;
|
|
case CMD_ARG_STRING:
|
|
if (str[0] == '\0' && es->default_input[0] != '\0') {
|
|
qe_free(&str);
|
|
str = qe_strdup(es->default_input);
|
|
}
|
|
es->args[index].p = str; /* will be freed at the of the command */
|
|
break;
|
|
}
|
|
/* now we can parse the following arguments */
|
|
parse_arguments(es);
|
|
}
|
|
|
|
int check_read_only(EditState *s)
|
|
{
|
|
if (s->b->flags & BF_READONLY) {
|
|
put_status(s, "Buffer is read-only");
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void do_execute_command(EditState *s, const char *cmd, int argval)
|
|
{
|
|
CmdDef *d;
|
|
|
|
/* XXX: should test for '(' and '=' and evaluate script instead */
|
|
d = qe_find_cmd(cmd);
|
|
if (d) {
|
|
exec_command(s, d, argval, 0);
|
|
} else {
|
|
put_status(s, "No command %s", cmd);
|
|
}
|
|
}
|
|
|
|
void window_display(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
CSSRect rect;
|
|
|
|
/* set the clipping rectangle to the whole window */
|
|
/* XXX: should clip out popup windows */
|
|
rect.x1 = s->xleft;
|
|
rect.y1 = s->ytop;
|
|
rect.x2 = rect.x1 + s->width;
|
|
rect.y2 = rect.y1 + s->height;
|
|
set_clip_rectangle(s->screen, &rect);
|
|
|
|
if (qs->complete_refresh) {
|
|
edit_invalidate(s, 0);
|
|
s->borders_invalid = 1;
|
|
}
|
|
|
|
s->mode->display(s);
|
|
|
|
display_mode_line(s);
|
|
display_window_borders(s);
|
|
}
|
|
|
|
/* display all windows */
|
|
/* XXX: should use correct clipping to avoid popups display hacks */
|
|
void edit_display(QEmacsState *qs)
|
|
{
|
|
EditState *s;
|
|
int has_popups, has_minibuf;
|
|
int start_time, elapsed_time;
|
|
|
|
start_time = get_clock_ms();
|
|
|
|
/* first call hooks for mode specific fixups */
|
|
for (s = qs->first_window; s != NULL; s = s->next_window) {
|
|
if (s->mode->display_hook)
|
|
s->mode->display_hook(s);
|
|
}
|
|
|
|
/* count popups */
|
|
/* CG: maybe a separate list for popups? */
|
|
has_popups = 0;
|
|
has_minibuf = 0;
|
|
for (s = qs->first_window; s != NULL; s = s->next_window) {
|
|
if (s->flags & WF_POPUP) {
|
|
has_popups++;
|
|
}
|
|
if (s->flags & WF_MINIBUF) {
|
|
has_minibuf++;
|
|
}
|
|
}
|
|
|
|
/* refresh normal windows and minibuf with popup kludge */
|
|
for (s = qs->first_window; s != NULL; s = s->next_window) {
|
|
if (!(s->flags & WF_POPUP) &&
|
|
((s->flags & WF_MINIBUF) || !has_popups || qs->complete_refresh)) {
|
|
window_display(s);
|
|
}
|
|
}
|
|
/* refresh popups if any */
|
|
if (has_popups) {
|
|
for (s = qs->first_window; s != NULL; s = s->next_window) {
|
|
if (s->flags & WF_POPUP) {
|
|
//if (qs->complete_refresh)
|
|
// /* refresh frame */;
|
|
window_display(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Redraw status and diag messages */
|
|
if (*qs->status_shadow || *qs->diag_shadow) {
|
|
int width = qs->screen->width;
|
|
int height = qs->status_height;
|
|
int x = 0, y = qs->screen->height - height;
|
|
|
|
if (*qs->status_shadow && !has_minibuf) {
|
|
print_at_byte(qs->screen, x, y, width, height,
|
|
qs->status_shadow, QE_STYLE_STATUS);
|
|
}
|
|
if (*qs->diag_shadow) {
|
|
int w = strlen(qs->diag_shadow) + 1;
|
|
w *= get_glyph_width(qs->screen, NULL, QE_STYLE_STATUS, '0');
|
|
print_at_byte(qs->screen, x + width - w, y, w, height,
|
|
qs->diag_shadow, QE_STYLE_STATUS);
|
|
}
|
|
}
|
|
|
|
elapsed_time = get_clock_ms() - start_time;
|
|
if (elapsed_time >= 100)
|
|
put_status(s, "|edit_display: %dms", elapsed_time);
|
|
|
|
qs->complete_refresh = 0;
|
|
}
|
|
|
|
/* macros */
|
|
|
|
void do_start_kbd_macro(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
|
|
if (qs->defining_macro) {
|
|
qs->defining_macro = 0;
|
|
put_status(s, "Already defining kbd macro");
|
|
return;
|
|
}
|
|
qs->defining_macro = 1;
|
|
qe_free(&qs->macro_keys);
|
|
qs->nb_macro_keys = 0;
|
|
qs->macro_keys_size = 0;
|
|
put_status(s, "Defining kbd macro...");
|
|
}
|
|
|
|
void do_end_kbd_macro(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
|
|
/* if called inside a macro, it is last recorded keys, so ignore
|
|
it */
|
|
if (qs->macro_key_index != -1)
|
|
return;
|
|
|
|
if (!qs->defining_macro) {
|
|
put_status(s, "Not defining kbd macro");
|
|
return;
|
|
}
|
|
qs->defining_macro = 0;
|
|
put_status(s, "Keyboard macro defined");
|
|
}
|
|
|
|
void do_call_last_kbd_macro(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
int key;
|
|
|
|
if (qs->defining_macro) {
|
|
qs->defining_macro = 0;
|
|
put_status(s, "Cannot execute macro while defining one");
|
|
return;
|
|
}
|
|
|
|
if (qs->nb_macro_keys > 0) {
|
|
/* CG: should share code with do_execute_macro */
|
|
for (qs->macro_key_index = 0;
|
|
qs->macro_key_index < qs->nb_macro_keys;
|
|
qs->macro_key_index++) {
|
|
key = qs->macro_keys[qs->macro_key_index];
|
|
qe_key_process(key);
|
|
}
|
|
qs->macro_key_index = -1;
|
|
}
|
|
}
|
|
|
|
void do_execute_macro_keys(qe__unused__ EditState *s, const char *keys)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
const char *p;
|
|
int key;
|
|
|
|
qs->executing_macro++;
|
|
|
|
/* Interactive commands get their input from the macro, unless some
|
|
* suspend mechanism is added to create interactive macros.
|
|
*/
|
|
|
|
p = keys;
|
|
for (;;) {
|
|
skip_spaces(&p);
|
|
if (*p == '\0')
|
|
break;
|
|
key = strtokey(&p);
|
|
qe_key_process(key);
|
|
}
|
|
qs->executing_macro--;
|
|
}
|
|
|
|
void do_define_kbd_macro(EditState *s, const char *name, const char *keys,
|
|
const char *key_bind)
|
|
{
|
|
CmdDef *def;
|
|
int size;
|
|
char *buf;
|
|
|
|
size = 2 + strlen(keys) + 3;
|
|
buf = qe_malloc_array(char, size);
|
|
|
|
// XXX: should special case "last-kbd-macro"
|
|
|
|
/* CG: should parse macro keys to an array and pass index
|
|
* to do_execute_macro.
|
|
*/
|
|
snprintf(buf, size, "S{%s}%c", keys, 0);
|
|
|
|
def = qe_find_cmd(name);
|
|
if (def && def->action.ESs == do_execute_macro_keys) {
|
|
/* redefininig a macro */
|
|
/* XXX: freeing the current macro definition may cause a crash if it
|
|
* is currently executing.
|
|
*/
|
|
qe_free((char **)&def->spec);
|
|
def->spec = buf;
|
|
} else {
|
|
def = qe_mallocz_array(CmdDef, 2);
|
|
def->key = def->alt_key = KEY_NONE;
|
|
def->name = qe_strdup(name);
|
|
def->spec = buf;
|
|
def->sig = CMD_ESs;
|
|
def->val = 0;
|
|
def->action.ESs = do_execute_macro_keys;
|
|
def[1].val = 1; /* flag as allocated for free-all */
|
|
qe_register_cmd_table(def, NULL);
|
|
}
|
|
if (key_bind && *key_bind) {
|
|
do_set_key(s, key_bind, name, 0);
|
|
}
|
|
}
|
|
|
|
static void qe_save_macro(EditState *s, CmdDef *def, EditBuffer *b)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
char buf[32];
|
|
buf_t outbuf, *out;
|
|
int i;
|
|
const char *name = "last-kbd-macro";
|
|
|
|
if (def)
|
|
name = def->name;
|
|
|
|
eb_printf(b, "define_kbd_macro(\"%s\", \"", name);
|
|
|
|
if (def) {
|
|
const char *keys = def->spec + 2; /* skip S{ */
|
|
while (keys[1]) { /* stop at } */
|
|
eb_putc(b, utf8_decode(&keys));
|
|
}
|
|
} else {
|
|
for (i = 0; i < qs->nb_macro_keys; i++) {
|
|
out = buf_init(&outbuf, buf, sizeof(buf));
|
|
buf_put_key(out, qs->macro_keys[i]);
|
|
eb_puts(b, out->buf);
|
|
}
|
|
}
|
|
eb_puts(b, "\", \"\");\n");
|
|
}
|
|
|
|
void qe_save_macros(EditState *s, EditBuffer *b)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
CmdDef *d;
|
|
|
|
eb_puts(b, "// macros:\n");
|
|
qe_save_macro(s, NULL, b);
|
|
|
|
/* Enumerate defined macros */
|
|
for (d = qs->first_cmd; d != NULL; d = d->action.next) {
|
|
for (; d->name != NULL; d++) {
|
|
if (d->action.ESs == do_execute_macro_keys)
|
|
qe_save_macro(s, d, b);
|
|
}
|
|
}
|
|
eb_putc(b, '\n');
|
|
}
|
|
|
|
#define MACRO_KEY_INCR 64
|
|
|
|
static void macro_add_key(int key)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
int new_size;
|
|
|
|
if (qs->nb_macro_keys >= qs->macro_keys_size) {
|
|
new_size = qs->macro_keys_size + MACRO_KEY_INCR;
|
|
if (!qe_realloc(&qs->macro_keys, new_size * sizeof(unsigned short)))
|
|
return;
|
|
qs->macro_keys_size = new_size;
|
|
}
|
|
qs->macro_keys[qs->nb_macro_keys++] = key;
|
|
}
|
|
|
|
void do_numeric_argument(qe__unused__ EditState *s)
|
|
{
|
|
/* nothing is done there (see qe_key_process()) */
|
|
}
|
|
|
|
typedef struct QEKeyContext {
|
|
int argval;
|
|
int noargval;
|
|
int sign;
|
|
int is_numeric_arg;
|
|
int is_escape;
|
|
int nb_keys;
|
|
int describe_key; /* if true, the following command is only displayed */
|
|
void (*grab_key_cb)(void *opaque, int key);
|
|
void *grab_key_opaque;
|
|
unsigned int keys[MAX_KEYS];
|
|
char buf[128];
|
|
} QEKeyContext;
|
|
|
|
static QEKeyContext key_ctx;
|
|
|
|
/*
|
|
* All typed keys are sent to the callback. Previous grab is aborted
|
|
*/
|
|
void qe_grab_keys(void (*cb)(void *opaque, int key), void *opaque)
|
|
{
|
|
QEKeyContext *c = &key_ctx;
|
|
|
|
/* CG: Should free previous grab? */
|
|
/* CG: Should grabing be window dependent ? */
|
|
c->grab_key_cb = cb;
|
|
c->grab_key_opaque = opaque;
|
|
}
|
|
|
|
/*
|
|
* Abort key grabing
|
|
*/
|
|
void qe_ungrab_keys(void)
|
|
{
|
|
QEKeyContext *c = &key_ctx;
|
|
|
|
/* CG: Should free previous grab? */
|
|
c->grab_key_cb = NULL;
|
|
c->grab_key_opaque = NULL;
|
|
}
|
|
|
|
/* init qe key handling context */
|
|
static void qe_key_init(QEKeyContext *c)
|
|
{
|
|
c->is_numeric_arg = 0;
|
|
c->is_escape = 0;
|
|
c->noargval = 1;
|
|
c->argval = NO_ARG;
|
|
c->sign = 1;
|
|
c->nb_keys = 0;
|
|
c->buf[0] = '\0';
|
|
}
|
|
|
|
KeyDef *qe_find_binding(unsigned int *keys, int nb_keys, KeyDef *kd)
|
|
{
|
|
for (; kd != NULL; kd = kd->next) {
|
|
if (kd->nb_keys >= nb_keys
|
|
&& !memcmp(kd->keys, keys, nb_keys * sizeof(keys[0]))) {
|
|
break;
|
|
}
|
|
}
|
|
return kd;
|
|
}
|
|
|
|
KeyDef *qe_find_current_binding(unsigned int *keys, int nb_keys, ModeDef *m)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
|
|
for (; m; m = m->fallback) {
|
|
KeyDef *kd = qe_find_binding(keys, nb_keys, m->first_key);
|
|
if (kd != NULL)
|
|
return kd;
|
|
}
|
|
return qe_find_binding(keys, nb_keys, qs->first_key);
|
|
}
|
|
|
|
static void qe_key_process(int key)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
QEKeyContext *c = &key_ctx;
|
|
EditState *s;
|
|
KeyDef *kd;
|
|
CmdDef *d;
|
|
char buf1[128];
|
|
buf_t outbuf, *out;
|
|
int len;
|
|
|
|
if (qs->defining_macro && !qs->executing_macro) {
|
|
macro_add_key(key);
|
|
}
|
|
|
|
again:
|
|
if (c->grab_key_cb) {
|
|
/* grabber should return codes for quit / fall thru / ungrab */
|
|
c->grab_key_cb(c->grab_key_opaque, key);
|
|
/* allow key_grabber to quit and unget last key */
|
|
if (c->grab_key_cb || qs->ungot_key == -1)
|
|
return;
|
|
key = qs->ungot_key;
|
|
qs->ungot_key = -1;
|
|
}
|
|
|
|
/* safety check */
|
|
if (c->nb_keys >= MAX_KEYS) {
|
|
qe_key_init(c);
|
|
c->describe_key = 0;
|
|
return;
|
|
}
|
|
|
|
c->keys[c->nb_keys++] = key;
|
|
s = qs->active_window;
|
|
if (s == NULL) {
|
|
s = qs->active_window = qs->first_window;
|
|
if (s == NULL)
|
|
return;
|
|
}
|
|
put_status(s, " "); /* Erase pending keystrokes and message */
|
|
dpy_flush(&global_screen);
|
|
|
|
/* Special case for escape: we transform it as meta so
|
|
that unix users are happy ! */
|
|
if (key == KEY_ESC && c->nb_keys == 1) {
|
|
c->is_escape = 1;
|
|
goto next;
|
|
} else
|
|
if (c->is_escape) {
|
|
compose_keys(c->keys, &c->nb_keys);
|
|
c->is_escape = 0;
|
|
key = c->keys[c->nb_keys - 1];
|
|
}
|
|
|
|
/* see if one command is found */
|
|
kd = qe_find_current_binding(c->keys, c->nb_keys, s->mode);
|
|
if (!kd) {
|
|
/* no key found */
|
|
unsigned int key_default = KEY_DEFAULT;
|
|
|
|
if (c->nb_keys == 1) {
|
|
if (!KEY_IS_SPECIAL(key) && !KEY_IS_CONTROL(key)) {
|
|
if (c->is_numeric_arg) {
|
|
if (qe_isdigit(key)) {
|
|
if (c->argval == NO_ARG)
|
|
c->argval = 0;
|
|
c->argval = c->argval * 10 + (key - '0');
|
|
c->nb_keys = 0;
|
|
goto next;
|
|
} else
|
|
if (key == '-' && c->argval == NO_ARG) {
|
|
c->sign = -c->sign;
|
|
c->nb_keys = 0;
|
|
goto next;
|
|
}
|
|
}
|
|
kd = qe_find_current_binding(&key_default, 1, s->mode);
|
|
if (kd) {
|
|
/* horrible kludge to pass key as intrinsic argument */
|
|
/* CG: should have an argument type for key */
|
|
/* CG: should be no longer necessary */
|
|
kd->cmd->val = key;
|
|
goto exec_cmd;
|
|
}
|
|
}
|
|
}
|
|
if (!c->describe_key) {
|
|
/* CG: should beep */;
|
|
}
|
|
out = buf_init(&outbuf, buf1, sizeof(buf1));
|
|
buf_put_keys(out, c->keys, c->nb_keys);
|
|
put_status(s, "No command on %s", buf1);
|
|
c->describe_key = 0;
|
|
qe_key_init(c);
|
|
dpy_flush(&global_screen);
|
|
return;
|
|
} else
|
|
if (c->nb_keys == kd->nb_keys) {
|
|
exec_cmd:
|
|
d = kd->cmd;
|
|
if (d->action.ES == do_numeric_argument && !c->describe_key) {
|
|
/* special handling for numeric argument */
|
|
/* CG: XXX: should display value of numeric argument */
|
|
if (key == KEY_META('-')) {
|
|
c->sign = -c->sign;
|
|
} else
|
|
if (key >= KEY_META('0') && key <= KEY_META('9')) {
|
|
if (c->is_numeric_arg == 0)
|
|
c->argval = 0;
|
|
c->argval = c->argval * 10 + key - KEY_META('0');
|
|
} else { /* KEY_CTRL('u') */
|
|
c->noargval = c->noargval * 4;
|
|
}
|
|
c->is_numeric_arg = 1;
|
|
c->nb_keys = 0;
|
|
} else {
|
|
if (c->is_numeric_arg) {
|
|
if (c->argval == NO_ARG)
|
|
c->argval = c->noargval;
|
|
c->argval *= c->sign;
|
|
}
|
|
if (c->describe_key) {
|
|
out = buf_init(&outbuf, buf1, sizeof(buf1));
|
|
buf_put_keys(out, c->keys, c->nb_keys);
|
|
put_status(s, "%s runs the command %s", buf1, d->name);
|
|
c->describe_key = 0;
|
|
} else {
|
|
int argval = c->argval;
|
|
|
|
/* To allow recursive calls to qe_key_process, especially
|
|
* from macros, we reset the QEKeyContext before
|
|
* dispatching the command
|
|
*/
|
|
qe_key_init(c);
|
|
exec_command(s, d, argval, key);
|
|
}
|
|
qe_key_init(c);
|
|
edit_display(qs);
|
|
dpy_flush(&global_screen);
|
|
/* CG: should move ungot key handling to generic event dispatch */
|
|
if (qs->ungot_key != -1) {
|
|
key = qs->ungot_key;
|
|
qs->ungot_key = -1;
|
|
goto again;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
next:
|
|
/* display key pressed */
|
|
len = strlen(c->buf);
|
|
if (len > 0)
|
|
c->buf[len-1] = ' ';
|
|
/* Should print argument if any in a more readable way */
|
|
out = buf_attach(&outbuf, c->buf, sizeof(c->buf), len);
|
|
buf_put_key(out, key);
|
|
buf_put_byte(out, '-');
|
|
put_status(s, "~%s", c->buf);
|
|
dpy_flush(&global_screen);
|
|
}
|
|
|
|
/* Print a utf-8 encoded buffer as unicode */
|
|
void print_at_byte(QEditScreen *screen,
|
|
int x, int y, int width, int height,
|
|
const char *str, QETermStyle style)
|
|
{
|
|
unsigned int ubuf[MAX_SCREEN_WIDTH];
|
|
int len;
|
|
QEStyleDef styledef;
|
|
QEFont *font;
|
|
CSSRect rect;
|
|
|
|
len = utf8_to_unicode(ubuf, countof(ubuf), str);
|
|
get_style(NULL, &styledef, style);
|
|
|
|
/* clip rectangle */
|
|
rect.x1 = x;
|
|
rect.y1 = y;
|
|
rect.x2 = rect.x1 + width;
|
|
rect.y2 = rect.y1 + height;
|
|
set_clip_rectangle(screen, &rect);
|
|
|
|
/* start rectangle */
|
|
fill_rectangle(screen, x, y, width, height, styledef.bg_color);
|
|
font = select_font(screen, styledef.font_style, styledef.font_size);
|
|
draw_text(screen, font, x, y + font->ascent, ubuf, len, styledef.fg_color);
|
|
release_font(screen, font);
|
|
}
|
|
|
|
/* XXX: should take va_list */
|
|
static void eb_format_message(QEmacsState *qs, const char *bufname,
|
|
const char *message)
|
|
{
|
|
char header[128];
|
|
EditBuffer *eb;
|
|
buf_t outbuf, *out;
|
|
|
|
out = buf_init(&outbuf, header, sizeof(header));
|
|
|
|
if (qs->ec.filename)
|
|
buf_printf(out, "%s:%d: ", qs->ec.filename, qs->ec.lineno);
|
|
|
|
if (qs->ec.function)
|
|
buf_printf(out, "%s: ", qs->ec.function);
|
|
|
|
eb = eb_find_new(bufname, BF_UTF8);
|
|
if (eb) {
|
|
eb_printf(eb, "%s%s\n", header, message);
|
|
} else {
|
|
fprintf(stderr, "%s%s\n", header, message);
|
|
}
|
|
}
|
|
|
|
void put_error(qe__unused__ EditState *s, const char *fmt, ...)
|
|
{
|
|
/* CG: s is not used and may be NULL! */
|
|
QEmacsState *qs = &qe_state;
|
|
char buf[MAX_SCREEN_WIDTH];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
eb_format_message(qs, "*errors*", buf);
|
|
}
|
|
|
|
void put_status(EditState *s, const char *fmt, ...)
|
|
{
|
|
/* XXX: s may be NULL! */
|
|
QEmacsState *qs = s ? s->qe_state : &qe_state;
|
|
char buf[MAX_SCREEN_WIDTH];
|
|
const char *p;
|
|
va_list ap;
|
|
int silent = 0;
|
|
int diag = 0;
|
|
int force = 0;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (qs->active_window && (qs->active_window->flags & WF_MINIBUF)) {
|
|
/* display status messages in diag area if minibuffer is active */
|
|
diag = 1;
|
|
}
|
|
|
|
for (p = buf;; p++) {
|
|
if (*p == '|') {
|
|
diag = 1;
|
|
} else
|
|
if (*p == '~') {
|
|
silent = 1;
|
|
} else
|
|
if (*p == '!') {
|
|
force = 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!qs->screen->dpy.dpy_probe) {
|
|
eb_format_message(qs, "*errors*", p);
|
|
} else {
|
|
int width = qs->screen->width;
|
|
int height = qs->status_height;
|
|
int x = 0, y = qs->screen->height - height;
|
|
|
|
if (diag) {
|
|
if (force || !strequal(p, qs->diag_shadow)) {
|
|
/* right align display and overwrite last diag message */
|
|
int w = strlen(qs->diag_shadow);
|
|
w = snprintf(qs->diag_shadow, sizeof(qs->diag_shadow),
|
|
"%*s", w, p) + 1;
|
|
w *= get_glyph_width(qs->screen, NULL, QE_STYLE_STATUS, '0');
|
|
print_at_byte(qs->screen, x + width - w, y, w, height,
|
|
qs->diag_shadow, QE_STYLE_STATUS);
|
|
pstrcpy(qs->diag_shadow, sizeof(qs->diag_shadow), p);
|
|
}
|
|
} else {
|
|
if (force || !strequal(p, qs->status_shadow)) {
|
|
print_at_byte(qs->screen, x, y, width, height,
|
|
p, QE_STYLE_STATUS);
|
|
pstrcpy(qs->status_shadow, sizeof(qs->status_shadow), p);
|
|
}
|
|
}
|
|
skip_spaces(&p);
|
|
if (!silent && *p)
|
|
eb_format_message(qs, "*messages*", buf);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
EditState *find_file_window(const char *filename)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditState *s;
|
|
|
|
for (s = qs->first_window; s; s = s->next_window) {
|
|
if (strequal(s->b->filename, filename))
|
|
return s;
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
void switch_to_buffer(EditState *s, EditBuffer *b)
|
|
{
|
|
EditBuffer *b0 = s->b;
|
|
EditState *e;
|
|
ModeDef *mode;
|
|
|
|
/* remove region hilite */
|
|
s->region_style = 0;
|
|
|
|
if (b == b0)
|
|
return;
|
|
|
|
if (b0) {
|
|
/* Save generic mode data to the buffer */
|
|
generic_save_window_data(s);
|
|
|
|
/* Close the mode */
|
|
edit_set_mode(s, NULL);
|
|
}
|
|
|
|
/* now we can switch ! */
|
|
s->b = b;
|
|
|
|
/* Delete transient buffer if no other window displays it */
|
|
if (b0) {
|
|
if ((b0->flags & BF_TRANSIENT) && !eb_find_window(b0, NULL)) {
|
|
eb_free(&b0);
|
|
} else {
|
|
/* save buffer for predict_switch_to_buffer */
|
|
s->last_buffer = b0;
|
|
}
|
|
}
|
|
|
|
if (b) {
|
|
if (b->saved_data) {
|
|
/* Restore window mode and data from buffer saved data */
|
|
memcpy(s, b->saved_data, SAVED_DATA_SIZE);
|
|
s->offset = min(s->offset, b->total_size);
|
|
s->offset_top = min(s->offset_top, b->total_size);
|
|
mode = b->saved_mode;
|
|
} else {
|
|
/* Try to get window mode and data from another window */
|
|
e = eb_find_window(b, s);
|
|
if (e) {
|
|
memcpy(s, e, SAVED_DATA_SIZE);
|
|
mode = e->mode;
|
|
} else {
|
|
memset(s, 0, SAVED_DATA_SIZE);
|
|
mode = b->default_mode;
|
|
/* <default> default values */
|
|
s->insert = 1;
|
|
s->indent_size = s->qe_state->default_tab_width;
|
|
s->default_style = QE_STYLE_DEFAULT;
|
|
s->wrap = mode ? mode->default_wrap : WRAP_AUTO;
|
|
}
|
|
}
|
|
/* validate the mode */
|
|
if (!mode)
|
|
mode = b->default_mode;
|
|
if (!mode) {
|
|
/* default mode */
|
|
mode = &text_mode;
|
|
}
|
|
/* initialize the mode */
|
|
edit_set_mode(s, mode);
|
|
}
|
|
}
|
|
|
|
/* detach the window from the window tree. */
|
|
static void edit_detach(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState **ep;
|
|
|
|
/* unlink the window from the frame */
|
|
for (ep = &qs->first_window; *ep;) {
|
|
if ((*ep)->target_window == s) {
|
|
(*ep)->target_window = NULL;
|
|
}
|
|
if (*ep == s) {
|
|
*ep = s->next_window;
|
|
s->next_window = NULL;
|
|
} else {
|
|
ep = &(*ep)->next_window;
|
|
}
|
|
}
|
|
/* if window was active, activate target window or default window */
|
|
if (qs->active_window == s) {
|
|
if (s->target_window)
|
|
qs->active_window = s->target_window;
|
|
else
|
|
qs->active_window = qs->first_window;
|
|
}
|
|
}
|
|
|
|
/* move a window before another one */
|
|
static void edit_attach(EditState *s, EditState *e)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState **ep;
|
|
|
|
if (s != e) {
|
|
/* Detach the window from the frame */
|
|
for (ep = &qs->first_window; *ep; ep = &(*ep)->next_window) {
|
|
if (*ep == s) {
|
|
*ep = s->next_window;
|
|
s->next_window = NULL;
|
|
break;
|
|
}
|
|
}
|
|
/* Re-attach the window before `e` */
|
|
for (ep = &qs->first_window; *ep; ep = &(*ep)->next_window) {
|
|
if (*ep == e)
|
|
break;
|
|
}
|
|
s->next_window = *ep;
|
|
*ep = s;
|
|
if (qs->active_window == NULL)
|
|
qs->active_window = s;
|
|
}
|
|
}
|
|
|
|
/* compute the client area from the window position */
|
|
static void compute_client_area(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
int x1, y1, x2, y2;
|
|
|
|
x1 = s->x1;
|
|
y1 = s->y1;
|
|
x2 = s->x2;
|
|
y2 = s->y2;
|
|
if (s->flags & WF_MODELINE)
|
|
y2 -= qs->mode_line_height;
|
|
if (s->flags & WF_POPUP) {
|
|
x1 += qs->border_width;
|
|
x2 -= qs->border_width;
|
|
y1 += s->caption ? qs->mode_line_height : qs->border_width;
|
|
y2 -= qs->border_width;
|
|
}
|
|
if (s->flags & WF_RSEPARATOR)
|
|
x2 -= qs->separator_width;
|
|
|
|
s->xleft = x1;
|
|
s->ytop = y1;
|
|
s->width = x2 - x1;
|
|
s->height = y2 - y1;
|
|
|
|
s->line_height = s->char_width = 1;
|
|
if (s->screen && s->screen->dpy.dpy_probe) {
|
|
/* use window default style font except for dummy display */
|
|
s->line_height = max(1, get_line_height(s->screen, s, QE_STYLE_DEFAULT));
|
|
s->char_width = max(1, get_glyph_width(s->screen, s, QE_STYLE_DEFAULT, '0'));
|
|
}
|
|
|
|
s->rows = max(1, s->height / s->line_height);
|
|
s->cols = max(1, s->width / s->char_width);
|
|
}
|
|
|
|
/* Create a new edit window, add it in the window list and sets it
|
|
* active if none are active. The coordinates include the window
|
|
* borders.
|
|
*/
|
|
EditState *edit_new(EditBuffer *b,
|
|
int x1, int y1, int width, int height, int flags)
|
|
{
|
|
/* b may be NULL ??? */
|
|
QEmacsState *qs = &qe_state;
|
|
EditState *s, *e;
|
|
|
|
s = qe_mallocz(EditState);
|
|
if (!s)
|
|
return NULL;
|
|
|
|
s->qe_state = qs;
|
|
s->screen = qs->screen;
|
|
s->x1 = x1;
|
|
s->y1 = y1;
|
|
s->x2 = x1 + width;
|
|
s->y2 = y1 + height;
|
|
s->flags = flags;
|
|
compute_client_area(s);
|
|
|
|
/* link window in window list */
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (e->y1 > s->y1 || (e->y1 == s->y1 && e->x1 > s->x1))
|
|
break;
|
|
}
|
|
edit_attach(s, e);
|
|
|
|
/* restore saved window settings, set mode */
|
|
switch_to_buffer(s, b);
|
|
return s;
|
|
}
|
|
|
|
/* Close the edit window.
|
|
* Save the window state to the buffer for later retrieval.
|
|
* If it is active, find another window to activate.
|
|
*/
|
|
void edit_close(EditState **sp)
|
|
{
|
|
if (*sp) {
|
|
EditState *s = *sp;
|
|
|
|
/* save current state for later window reattachment */
|
|
switch_to_buffer(s, NULL);
|
|
edit_detach(s);
|
|
/* closing the window mode should have freed it already */
|
|
qe_free_mode_data(s->mode_data);
|
|
qe_free(&s->prompt);
|
|
qe_free(&s->caption);
|
|
qe_free(&s->line_shadow);
|
|
s->shadow_nb_lines = 0;
|
|
qe_free(sp);
|
|
}
|
|
}
|
|
|
|
static const char *file_completion_ignore_extensions = {
|
|
"|bak"
|
|
"|pdf|jpg|gif|png|swf" /* binary formats */
|
|
"|bmp|xls|xlsx|ppt|pptx"
|
|
"|apk"
|
|
"|bin|obj|dll|exe" /* DOS binaries */
|
|
"|o|so|a" /* Unix binaries */
|
|
"|dylib|dSYM|DS_Store" /* OS/X */
|
|
"|gz|tgz|taz|bz2|bzip2|xz|zip|rar|z|tar" /* archives */
|
|
"|cma|cmi|cmo|cmt|cmti|cmx"
|
|
"|class|jar" /* java */
|
|
"|b"
|
|
"|"
|
|
};
|
|
|
|
void file_complete(CompleteState *cp)
|
|
{
|
|
char path[MAX_FILENAME_SIZE];
|
|
char file[MAX_FILENAME_SIZE];
|
|
char filename[MAX_FILENAME_SIZE];
|
|
char *current;
|
|
FindFileState *ffst;
|
|
const char *base;
|
|
int len;
|
|
|
|
current = cp->current;
|
|
if (*current == '~') {
|
|
canonicalize_absolute_path(cp->s, filename, sizeof(filename), cp->current);
|
|
current = filename;
|
|
}
|
|
|
|
splitpath(path, sizeof(path), file, sizeof(file), current);
|
|
pstrcat(file, sizeof(file), "*");
|
|
|
|
if (cp->completion->flags & CF_RESOURCE) {
|
|
ffst = find_file_open(qe_state.res_path, file, FF_PATH | FF_NOXXDIR);
|
|
} else {
|
|
int flags = FF_NOXXDIR;
|
|
if (cp->completion->flags & CF_DIRNAME)
|
|
flags |= FF_ONLYDIR;
|
|
if (cp->fuzzy)
|
|
flags |= 1; // recursion level
|
|
ffst = find_file_open(*path ? path : ".", file, flags);
|
|
}
|
|
while (find_file_next(ffst, filename, sizeof(filename)) == 0) {
|
|
struct stat sb;
|
|
|
|
base = get_basename(filename);
|
|
/* ignore known backup files (hardcoded test for *~) */
|
|
len = strlen(base);
|
|
if (!len || base[len - 1] == '~')
|
|
continue;
|
|
/* ignore known binary file extensions */
|
|
if (match_extension(base, file_completion_ignore_extensions))
|
|
continue;
|
|
|
|
/* stat the file to find out if it's a directory.
|
|
* In this case add a slash to speed up typing long paths
|
|
*/
|
|
if (!stat(filename, &sb) && S_ISDIR(sb.st_mode))
|
|
pstrcat(filename, sizeof(filename), "/");
|
|
if (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
|
|
/* XXX: if the file has no extension and is executable,
|
|
* should check and ignore binary executable files.
|
|
*/
|
|
}
|
|
add_string(&cp->cs, filename, 0);
|
|
}
|
|
|
|
find_file_close(&ffst);
|
|
}
|
|
|
|
static CompletionDef file_completion = {
|
|
"file", file_complete,
|
|
#ifndef CONFIG_TINY
|
|
file_print_entry,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
NULL, CF_FILENAME
|
|
};
|
|
|
|
#ifndef CONFIG_TINY
|
|
static CompletionDef dir_completion = {
|
|
"dir", file_complete,
|
|
file_print_entry,
|
|
NULL, CF_DIRNAME | CF_NO_FUZZY
|
|
};
|
|
|
|
static CompletionDef resource_completion = {
|
|
"resource", file_complete,
|
|
file_print_entry,
|
|
NULL, CF_RESOURCE | CF_NO_FUZZY
|
|
};
|
|
#endif
|
|
|
|
void buffer_complete(CompleteState *cp)
|
|
{
|
|
QEmacsState *qs = cp->s->qe_state;
|
|
EditBuffer *b;
|
|
|
|
for (b = qs->first_buffer; b != NULL; b = b->next) {
|
|
if (!(b->flags & BF_SYSTEM))
|
|
complete_test(cp, b->name);
|
|
}
|
|
}
|
|
|
|
int buffer_print_entry(CompleteState *cp, EditState *s, const char *name)
|
|
{
|
|
EditBuffer *b = s->b;
|
|
EditBuffer *b1 = eb_find(name);
|
|
int len;
|
|
|
|
if (b1) {
|
|
b->cur_style = QE_STYLE_KEYWORD;
|
|
len = eb_puts(b, b1->name);
|
|
b->tab_width = max3(16, 2 + len, b->tab_width);
|
|
len += eb_putc(b, '\t');
|
|
if (*b1->filename) {
|
|
b->cur_style = QE_STYLE_COMMENT;
|
|
len += eb_puts(b, b1->filename);
|
|
}
|
|
b->cur_style = QE_STYLE_DEFAULT;
|
|
} else {
|
|
return eb_puts(b, name);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static CompletionDef buffer_completion = {
|
|
"buffer", buffer_complete, buffer_print_entry
|
|
};
|
|
|
|
static int default_completion_window_print_entry(CompleteState *cp, EditState *s, const char *name) {
|
|
return eb_puts(s->b, name);
|
|
}
|
|
|
|
static int default_completion_window_get_entry(EditState *s, char *dest, int size, int offset) {
|
|
int len = eb_fgets(s->b, dest, size, offset, &offset);
|
|
char *p = strchr(dest, '\t');
|
|
if (p != NULL)
|
|
len = p - dest;
|
|
dest[len] = '\0'; /* strip the TAB or trailing newline if any */
|
|
return len;
|
|
}
|
|
|
|
/* register a new completion method */
|
|
void qe_register_completion(CompletionDef *cp)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
CompletionDef **p;
|
|
|
|
for (p = &qs->first_completion;; p = &(*p)->next) {
|
|
if (*p == cp) {
|
|
/* completion is already registered, do nothing */
|
|
return;
|
|
}
|
|
if (*p == NULL) {
|
|
cp->next = NULL;
|
|
*p = cp;
|
|
break;
|
|
}
|
|
}
|
|
if (!cp->print_entry)
|
|
cp->print_entry = default_completion_window_print_entry;
|
|
if (!cp->get_entry)
|
|
cp->get_entry = default_completion_window_get_entry;
|
|
}
|
|
|
|
static CompletionDef *find_completion(const char *name)
|
|
{
|
|
CompletionDef *p;
|
|
|
|
if (name[0] != '\0') {
|
|
for (p = qe_state.first_completion; p != NULL; p = p->next) {
|
|
if (strequal(p->name, name))
|
|
return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void complete_start(CompleteState *cp, EditState *s, int start, int end,
|
|
EditState *target)
|
|
{
|
|
memset(cp, 0, sizeof(*cp));
|
|
cp->s = s;
|
|
cp->target = target;
|
|
cp->start = start;
|
|
cp->end = end;
|
|
cp->len = eb_get_region_contents(s->b, cp->start, cp->end,
|
|
cp->current, sizeof(cp->current));
|
|
}
|
|
|
|
/* XXX: should have a globbing option */
|
|
void complete_test(CompleteState *cp, const char *str)
|
|
{
|
|
int fuzzy = 0;
|
|
|
|
if (memcmp(str, cp->current, cp->len)) {
|
|
if (!qe_memicmp(str, cp->current, cp->len))
|
|
fuzzy = 1;
|
|
else
|
|
if (cp->fuzzy && strmem(str, cp->current, cp->len))
|
|
fuzzy = 2;
|
|
else
|
|
return;
|
|
}
|
|
add_string(&cp->cs, str, fuzzy);
|
|
}
|
|
|
|
static int completion_sort_func(const void *p1, const void *p2)
|
|
{
|
|
StringItem *item1 = *(StringItem **)p1;
|
|
StringItem *item2 = *(StringItem **)p2;
|
|
|
|
/* Group items by group order */
|
|
if (item1->group != item2->group)
|
|
return item1->group - item2->group;
|
|
/* Use natural sort: keep numbers in order */
|
|
return qe_strcollate(item1->str, item2->str);
|
|
}
|
|
|
|
static void complete_end(CompleteState *cp)
|
|
{
|
|
free_strings(&cp->cs);
|
|
}
|
|
|
|
/* mini buffer stuff */
|
|
|
|
typedef struct MinibufState {
|
|
QEModeData base;
|
|
|
|
void (*cb)(void *opaque, char *buf);
|
|
void *opaque;
|
|
|
|
EditState *completion_popup_window; /* XXX: should have a popup_window member */
|
|
int completion_stage;
|
|
int completion_flags;
|
|
int completion_start;
|
|
int completion_end;
|
|
CompletionDef *completion;
|
|
|
|
StringArray *history;
|
|
int history_index;
|
|
int history_saved_offset;
|
|
} MinibufState;
|
|
|
|
static ModeDef minibuffer_mode;
|
|
|
|
static inline MinibufState *minibuffer_get_state(EditState *e, int status) {
|
|
return qe_get_buffer_mode_data(e->b, &minibuffer_mode, status ? e : NULL);
|
|
}
|
|
|
|
void do_minibuffer_complete(EditState *s, int type)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
int count, i, match_len, c, start, end;
|
|
CompleteState cs;
|
|
StringItem **outputs;
|
|
EditState *e;
|
|
EditBuffer *b;
|
|
int w, h, h1, w1;
|
|
MinibufState *mb;
|
|
|
|
if ((mb = minibuffer_get_state(s, 1)) == NULL)
|
|
return;
|
|
|
|
if (!mb->completion)
|
|
return;
|
|
|
|
/* Remove highlighted selection. */
|
|
// XXX: Should complete based on point position,
|
|
// not necessarily full minibuffer contents
|
|
do_delete_selection(s);
|
|
|
|
/* XXX: if completion_popup_window already displayed, should page
|
|
* through the window, if at end, should remove focus from
|
|
* completion_popup_window or close it.
|
|
*/
|
|
|
|
if (type == COMPLETION_TAB && qs->last_cmd_func == qs->this_cmd_func) {
|
|
mb->completion_stage++;
|
|
if (mb->completion->flags & CF_NO_FUZZY)
|
|
mb->completion_stage = 2;
|
|
} else {
|
|
mb->completion_stage = 0;
|
|
}
|
|
|
|
/* check completion window */
|
|
check_window(&mb->completion_popup_window);
|
|
if (mb->completion_popup_window && mb->completion_stage > 1) {
|
|
/* toggle completion popup on TAB */
|
|
mb->completion_stage = 0;
|
|
qs->this_cmd_func = 0;
|
|
edit_close(&mb->completion_popup_window);
|
|
do_refresh(s);
|
|
return;
|
|
}
|
|
|
|
start = 0;
|
|
end = s->offset;
|
|
if (mb->completion_flags) {
|
|
/* XXX: completion select? */
|
|
int offset = end;
|
|
while ((start = offset) > 0) {
|
|
int c = eb_prevc(s->b, offset, &offset);
|
|
if (!qe_isalnum_(c) && c != '-')
|
|
break;
|
|
}
|
|
}
|
|
mb->completion_start = start;
|
|
mb->completion_end = end;
|
|
complete_start(&cs, s, start, end, s->target_window);
|
|
cs.completion = mb->completion;
|
|
if (!(mb->completion->flags & CF_NO_FUZZY))
|
|
cs.fuzzy = mb->completion_stage;
|
|
(*mb->completion->enumerate)(&cs);
|
|
count = cs.cs.nb_items;
|
|
outputs = cs.cs.items;
|
|
#if 0
|
|
printf("count=%d\n", count);
|
|
for (i = 0; i < count; i++)
|
|
printf("out[%d]=%s\n", i, outputs[i]->str);
|
|
#endif
|
|
/* compute the longest match len */
|
|
match_len = cs.len;
|
|
|
|
if (count > 0) {
|
|
for (; (c = outputs[0]->str[match_len]) != '\0'; match_len++) {
|
|
for (i = 1; i < count; i++) {
|
|
if (outputs[i]->str[match_len] != c)
|
|
break;
|
|
}
|
|
if (i < count)
|
|
break;
|
|
}
|
|
/* Clip incomplete UTF-8 character before match_len */
|
|
for (i = cs.len; i < match_len; ) {
|
|
int clen = utf8_length[(unsigned char)outputs[0]->str[i]];
|
|
if (i + clen > match_len)
|
|
match_len = i;
|
|
else
|
|
i += clen;
|
|
}
|
|
}
|
|
if (match_len > cs.len) {
|
|
/* add the possible chars */
|
|
// XXX: potential utf-8 issue?
|
|
// XXX: replace the completed part, not necessarily at the start (use mark?)
|
|
// XXX: should delete region and insert as utf-8
|
|
// XXX: should not replace if fuzzy match?
|
|
eb_replace(s->b, cs.start, cs.end - cs.start, outputs[0]->str, match_len);
|
|
s->offset = cs.start + match_len;
|
|
mb->completion_end = s->offset;
|
|
if (type == COMPLETION_OTHER) {
|
|
/* mark the region with extra common characters */
|
|
do_mark_region(s, cs.start + match_len, cs.start + cs.len);
|
|
}
|
|
} else {
|
|
if (count > 1) {
|
|
/* if more than one match, then display them in a new popup
|
|
buffer */
|
|
if (!mb->completion_popup_window) {
|
|
char buf[60];
|
|
|
|
b = eb_new("*completion*",
|
|
BF_SYSTEM | BF_UTF8 | BF_TRANSIENT | BF_STYLE1);
|
|
b->default_mode = &list_mode;
|
|
w1 = qs->screen->width;
|
|
h1 = qs->screen->height - qs->status_height;
|
|
w = (w1 * 3) / 4;
|
|
h = (h1 * 3) / 4;
|
|
e = edit_new(b, (w1 - w) / 2, (h1 - h) / 2, w, h, WF_POPUP);
|
|
snprintf(buf, sizeof buf, "Select a %s:", mb->completion->name);
|
|
e->caption = qe_strdup(buf);
|
|
e->target_window = s;
|
|
mb->completion_popup_window = e;
|
|
do_refresh(e);
|
|
}
|
|
} else
|
|
if (count == 0 || type != COMPLETION_OTHER) {
|
|
/* close the popup when minibuf contents matches nothing */
|
|
edit_close(&mb->completion_popup_window);
|
|
do_refresh(s);
|
|
}
|
|
}
|
|
if (mb->completion_popup_window) {
|
|
/* modify the list with the current matches */
|
|
e = mb->completion_popup_window;
|
|
b = e->b;
|
|
qsort(outputs, count, sizeof(StringItem *), completion_sort_func);
|
|
b->flags &= ~BF_READONLY;
|
|
eb_delete(b, 0, b->total_size);
|
|
b->tab_width = 4;
|
|
for (i = 0; i < count; i++) {
|
|
eb_putc(b, ' '); /* XXX: should use window margins */
|
|
mb->completion->print_entry(&cs, e, outputs[i]->str);
|
|
eb_putc(b, '\n');
|
|
}
|
|
b->flags |= BF_READONLY;
|
|
e->mouse_force_highlight = 1;
|
|
e->force_highlight = 1;
|
|
e->offset = 0;
|
|
}
|
|
complete_end(&cs);
|
|
}
|
|
|
|
static int eb_match_string_reverse(EditBuffer *b, int offset, const char *str,
|
|
int *offsetp)
|
|
{
|
|
int len = strlen(str);
|
|
|
|
while (len > 0) {
|
|
if (offset <= 0 || eb_prevc(b, offset, &offset) != str[--len])
|
|
return 0;
|
|
}
|
|
*offsetp = offset;
|
|
return 1;
|
|
}
|
|
|
|
void do_minibuffer_electric(EditState *s, int key)
|
|
{
|
|
int c, offset, stop;
|
|
MinibufState *mb = minibuffer_get_state(s, 0);
|
|
|
|
if (mb && mb->completion && (mb->completion->flags & CF_FILENAME)) {
|
|
stop = s->offset;
|
|
c = eb_prevc(s->b, s->offset, &offset);
|
|
if (c == '/') {
|
|
if (eb_match_string_reverse(s->b, offset, "http:", &stop)
|
|
|| eb_match_string_reverse(s->b, offset, "https:", &stop)
|
|
|| eb_match_string_reverse(s->b, offset, "ftp:", &stop)) {
|
|
/* nothing, stop already updated */
|
|
}
|
|
eb_delete(s->b, 0, stop);
|
|
}
|
|
}
|
|
do_char(s, key, 1);
|
|
}
|
|
|
|
/* space does completion only if a completion method is defined */
|
|
void do_minibuffer_complete_space(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
MinibufState *mb = minibuffer_get_state(s, 0);
|
|
|
|
if (!mb || !mb->completion || (mb->completion->flags & CF_SPACE_OK)) {
|
|
do_char(s, ' ', 1);
|
|
} else
|
|
if (check_window(&mb->completion_popup_window)
|
|
&& qs->last_cmd_func == qs->this_cmd_func) {
|
|
/* page through the list */
|
|
// XXX: should close the popup at the bottom of the list
|
|
do_scroll_up_down(mb->completion_popup_window, 2);
|
|
} else {
|
|
do_minibuffer_complete(s, COMPLETION_SPACE);
|
|
}
|
|
}
|
|
|
|
static void do_minibuffer_char(EditState *s, int key, int argval)
|
|
{
|
|
MinibufState *mb = minibuffer_get_state(s, 0);
|
|
|
|
do_char(s, key, argval);
|
|
if (mb && check_window(&mb->completion_popup_window)) {
|
|
/* automatic filtering of completion list */
|
|
// XXX: should prevent auto-completion
|
|
do_minibuffer_complete(s, COMPLETION_OTHER);
|
|
}
|
|
}
|
|
|
|
/* scroll in completion popup */
|
|
void do_minibuffer_scroll_up_down(EditState *s, int dir)
|
|
{
|
|
MinibufState *mb = minibuffer_get_state(s, 0);
|
|
|
|
if (mb && check_window(&mb->completion_popup_window)) {
|
|
mb->completion_popup_window->force_highlight = 1;
|
|
do_scroll_up_down(mb->completion_popup_window, dir);
|
|
}
|
|
}
|
|
|
|
static void minibuffer_set_str(EditState *s, int start, int end, const char *str)
|
|
{
|
|
int len;
|
|
|
|
/* Replace the completion trigger zone */
|
|
/* XXX: should insert utf-8? */
|
|
len = strlen(str);
|
|
eb_replace(s->b, start, end - start, str, len);
|
|
s->offset = start + len;
|
|
}
|
|
|
|
/* CG: should use buffer of responses */
|
|
static StringArray *get_history(const char *name)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
HistoryEntry *p;
|
|
|
|
if (name[0] == '\0')
|
|
return NULL;
|
|
for (p = qs->first_history; p != NULL; p = p->next) {
|
|
if (strequal(p->name, name))
|
|
return &p->history;
|
|
}
|
|
/* not found: allocate history list */
|
|
p = qe_mallocz(HistoryEntry);
|
|
if (!p)
|
|
return NULL;
|
|
pstrcpy(p->name, sizeof(p->name), name);
|
|
p->next = qs->first_history;
|
|
qs->first_history = p;
|
|
return &p->history;
|
|
}
|
|
|
|
void do_minibuffer_history(EditState *s, int n)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
MinibufState *mb;
|
|
StringArray *hist;
|
|
int index;
|
|
char *str;
|
|
char buf[1024];
|
|
|
|
if ((mb = minibuffer_get_state(s, 0)) == NULL)
|
|
return;
|
|
|
|
/* if completion visible, move in it */
|
|
if (check_window(&mb->completion_popup_window)) {
|
|
mb->completion_popup_window->force_highlight = 1;
|
|
do_up_down(mb->completion_popup_window, n);
|
|
return;
|
|
}
|
|
|
|
hist = mb->history;
|
|
if (!hist)
|
|
return;
|
|
|
|
index = mb->history_index + n;
|
|
if (index < 0 || index >= hist->nb_items)
|
|
return;
|
|
|
|
if (qs->last_cmd_func != (CmdFunc)do_minibuffer_history) {
|
|
/* save currently edited line */
|
|
eb_get_contents(s->b, buf, sizeof(buf));
|
|
set_string(hist, hist->nb_items - 1, buf, 0);
|
|
mb->history_saved_offset = s->offset;
|
|
}
|
|
/* insert history text */
|
|
mb->history_index = index;
|
|
str = hist->items[index]->str;
|
|
minibuffer_set_str(s, 0, s->b->total_size, str);
|
|
if (index == hist->nb_items - 1) {
|
|
s->offset = mb->history_saved_offset;
|
|
}
|
|
}
|
|
|
|
void do_minibuffer_get_binary(EditState *s)
|
|
{
|
|
unsigned long offset;
|
|
|
|
if (s->target_window) {
|
|
eb_read(s->target_window->b, s->target_window->offset,
|
|
&offset, sizeof(offset));
|
|
eb_printf(s->b, "%lu", offset);
|
|
}
|
|
}
|
|
|
|
void do_minibuffer_exit(EditState *s, int do_abort)
|
|
{
|
|
char buf[4096], *retstr;
|
|
MinibufState *mb;
|
|
StringArray *hist;
|
|
EditState *cw;
|
|
EditState *target;
|
|
void (*cb)(void *opaque, char *buf);
|
|
void *opaque;
|
|
|
|
if ((mb = minibuffer_get_state(s, 1)) == NULL)
|
|
return;
|
|
|
|
cw = check_window(&mb->completion_popup_window);
|
|
|
|
if (!do_abort) {
|
|
/* if completion is activated, then select current file only if
|
|
the selection is highlighted */
|
|
if (cw && cw->force_highlight) {
|
|
int len;
|
|
len = mb->completion->get_entry(cw, buf, sizeof(buf), list_get_offset(cw) + 1);
|
|
if (len > 0) {
|
|
// insert completion string (delete highlighted part)
|
|
minibuffer_set_str(s, mb->completion_start, mb->completion_end, buf);
|
|
}
|
|
if (mb->completion->flags & CF_NO_AUTO_SUBMIT) {
|
|
edit_close(&mb->completion_popup_window);
|
|
return;
|
|
}
|
|
}
|
|
|
|
eb_get_contents(s->b, buf, sizeof(buf));
|
|
|
|
/* Append response to history list */
|
|
hist = mb->history;
|
|
if (hist && hist->nb_items > 0) {
|
|
/* if null string, do not insert in history */
|
|
hist->nb_items--;
|
|
qe_free(&hist->items[hist->nb_items]);
|
|
if (buf[0] != '\0')
|
|
add_string(hist, buf, 0);
|
|
}
|
|
}
|
|
|
|
/* remove completion popup if present */
|
|
if (cw) {
|
|
edit_close(&mb->completion_popup_window);
|
|
cw = NULL;
|
|
do_refresh(s);
|
|
}
|
|
|
|
cb = mb->cb;
|
|
opaque = mb->opaque;
|
|
target = s->target_window;
|
|
mb->cb = NULL;
|
|
mb->opaque = NULL;
|
|
|
|
/* Close the minibuffer window */
|
|
s->b->flags |= BF_TRANSIENT;
|
|
edit_close(&s);
|
|
|
|
/* Force status update and call the callback */
|
|
if (do_abort) {
|
|
put_status(target, "!Canceled.");
|
|
(*cb)(opaque, NULL);
|
|
} else {
|
|
put_status(target, "!");
|
|
retstr = qe_strdup(buf);
|
|
(*cb)(opaque, retstr);
|
|
}
|
|
}
|
|
|
|
/* Start minibuffer editing. When editing is finished, the callback is
|
|
called with an allocated string. If the string is null, it means
|
|
editing was aborted. */
|
|
void minibuffer_edit(EditState *e, const char *input, const char *prompt,
|
|
StringArray *hist, const char *completion_name,
|
|
void (*cb)(void *opaque, char *buf), void *opaque)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
MinibufState *mb;
|
|
EditState *s;
|
|
EditBuffer *b;
|
|
int len;
|
|
|
|
/* check if already in minibuffer editing */
|
|
if (e->flags & WF_MINIBUF) {
|
|
put_status(NULL, "|Already editing in minibuffer");
|
|
cb(opaque, NULL);
|
|
return;
|
|
}
|
|
|
|
b = eb_new("*minibuf*", BF_SYSTEM | BF_SAVELOG | BF_UTF8);
|
|
b->default_mode = &minibuffer_mode;
|
|
|
|
s = edit_new(b, 0, qs->screen->height - qs->status_height,
|
|
qs->screen->width, qs->status_height, WF_MINIBUF);
|
|
s->target_window = e;
|
|
s->prompt = qe_strdup(prompt);
|
|
s->bidir = 0;
|
|
s->default_style = QE_STYLE_MINIBUF;
|
|
/* XXX: should come from mode.default_wrap */
|
|
s->wrap = WRAP_TRUNCATE;
|
|
|
|
/* add default input */
|
|
if (input) {
|
|
/* Default input should already be encoded as utf-8 */
|
|
len = strlen(input);
|
|
eb_write(b, 0, (u8 *)input, len);
|
|
s->offset = len;
|
|
}
|
|
|
|
mb = minibuffer_get_state(s, 0);
|
|
if (mb) {
|
|
mb->completion_popup_window = NULL;
|
|
mb->completion = NULL;
|
|
if (completion_name) {
|
|
if (*completion_name == '.') {
|
|
mb->completion_flags = 1;
|
|
completion_name++;
|
|
}
|
|
mb->completion = find_completion(completion_name);
|
|
}
|
|
mb->history = hist;
|
|
mb->history_saved_offset = 0;
|
|
if (hist) {
|
|
mb->history_index = hist->nb_items;
|
|
add_string(hist, "", 0);
|
|
}
|
|
mb->cb = cb;
|
|
mb->opaque = opaque;
|
|
qs->active_window = s;
|
|
}
|
|
}
|
|
|
|
static void minibuffer_mode_free(EditBuffer *b, void *state)
|
|
{
|
|
/* If minibuffer is destroyed, call callback with NULL pointer */
|
|
MinibufState *mb = state;
|
|
void (*cb)(void *opaque, char *buf);
|
|
void *opaque;
|
|
|
|
if (!mb)
|
|
return;
|
|
|
|
if (check_window(&mb->completion_popup_window))
|
|
edit_close(&mb->completion_popup_window);
|
|
|
|
cb = mb->cb;
|
|
opaque = mb->opaque;
|
|
mb->cb = NULL;
|
|
mb->opaque = NULL;
|
|
|
|
if (cb) {
|
|
put_status(NULL, "!Abort.");
|
|
(*cb)(opaque, NULL);
|
|
}
|
|
}
|
|
|
|
void minibuffer_init(void)
|
|
{
|
|
/* populate and register minibuffer mode and commands */
|
|
memcpy(&minibuffer_mode, &text_mode, sizeof(ModeDef));
|
|
minibuffer_mode.name = "minibuffer";
|
|
minibuffer_mode.mode_probe = NULL;
|
|
minibuffer_mode.buffer_instance_size = sizeof(MinibufState);
|
|
minibuffer_mode.mode_free = minibuffer_mode_free;
|
|
minibuffer_mode.scroll_up_down = do_minibuffer_scroll_up_down;
|
|
qe_register_mode(&minibuffer_mode, MODEF_NOCMD | MODEF_VIEW);
|
|
qe_register_cmd_table(minibuffer_commands, &minibuffer_mode);
|
|
}
|
|
|
|
/* popup paging mode */
|
|
|
|
static ModeDef popup_mode;
|
|
|
|
/* Verify that window still exists, return argument or NULL,
|
|
* update handle if window is invalid.
|
|
*/
|
|
EditState *check_window(EditState **sp)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditState *e;
|
|
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (e == *sp)
|
|
return e;
|
|
}
|
|
return *sp = NULL;
|
|
}
|
|
|
|
void do_popup_exit(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
|
|
if (s->flags & WF_POPUP) {
|
|
/* only do this for a popup? */
|
|
s->b->flags |= BF_TRANSIENT;
|
|
edit_close(&s);
|
|
|
|
do_refresh(qs->active_window);
|
|
}
|
|
}
|
|
|
|
/* show a popup on a readonly buffer */
|
|
EditState *show_popup(EditState *s, EditBuffer *b, const char *caption)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e;
|
|
int w, h, w1, h1;
|
|
|
|
/* Prevent recursion */
|
|
if (s && s->b == b)
|
|
return s;
|
|
|
|
/* XXX: generic function to open popup ? */
|
|
w1 = qs->screen->width;
|
|
h1 = qs->screen->height - qs->status_height;
|
|
w = (w1 * 4) / 5;
|
|
h = (h1 * 3) / 4;
|
|
|
|
b->default_mode = &popup_mode;
|
|
e = edit_new(b, (w1 - w) / 2, (h1 - h) / 2, w, h, WF_POPUP);
|
|
if (caption)
|
|
e->caption = qe_strdup(caption);
|
|
/* XXX: should come from mode.default_wrap */
|
|
e->wrap = WRAP_TRUNCATE;
|
|
e->target_window = s;
|
|
qs->active_window = e;
|
|
do_refresh(e);
|
|
return e;
|
|
}
|
|
|
|
static void popup_init(void)
|
|
{
|
|
/* popup mode inherits from text mode */
|
|
memcpy(&popup_mode, &text_mode, sizeof(ModeDef));
|
|
popup_mode.name = "popup";
|
|
popup_mode.mode_probe = NULL;
|
|
qe_register_mode(&popup_mode, MODEF_VIEW);
|
|
qe_register_cmd_table(popup_commands, &popup_mode);
|
|
}
|
|
|
|
#ifndef CONFIG_TINY
|
|
/* insert a window to the left. Close all windows which are totally
|
|
under it (XXX: should try to move them first */
|
|
EditState *insert_window_left(EditBuffer *b, int width, int flags)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditState *e, *e_next, *e_new;
|
|
|
|
for (e = qs->first_window; e != NULL; e = e_next) {
|
|
e_next = e->next_window;
|
|
if (e->flags & WF_MINIBUF)
|
|
continue;
|
|
if (e->x2 <= width) {
|
|
edit_close(&e);
|
|
} else
|
|
if (e->x1 < width) {
|
|
e->x1 = width;
|
|
}
|
|
}
|
|
|
|
e_new = edit_new(b, 0, 0, width, qs->height - qs->status_height,
|
|
flags | WF_POPLEFT | WF_RSEPARATOR);
|
|
/* XXX: WRAP_AUTO is a better choice? */
|
|
e_new->wrap = WRAP_TRUNCATE;
|
|
do_refresh(e_new);
|
|
return e_new;
|
|
}
|
|
|
|
/* return a window on the side of window 's' */
|
|
EditState *find_window(EditState *s, int key, EditState *def)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e;
|
|
|
|
/* CG: Should compute cursor position to disambiguate
|
|
* non regular window layouts
|
|
*/
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (e->flags & WF_MINIBUF)
|
|
continue;
|
|
if (e->y1 < s->y2 && e->y2 > s->y1) {
|
|
/* horizontal overlap */
|
|
if (key == KEY_RIGHT && e->x1 == s->x2)
|
|
return e;
|
|
if (key == KEY_LEFT && e->x2 == s->x1)
|
|
return e;
|
|
}
|
|
if (e->x1 < s->x2 && e->x2 > s->x1) {
|
|
/* vertical overlap */
|
|
if (key == KEY_UP && e->y2 == s->y1)
|
|
return e;
|
|
if (key == KEY_DOWN && e->y1 == s->y2)
|
|
return e;
|
|
}
|
|
}
|
|
return def;
|
|
}
|
|
|
|
void do_find_window(EditState *s, int key)
|
|
{
|
|
s->qe_state->active_window = find_window(s, key, s);
|
|
}
|
|
#endif
|
|
|
|
/* Give a good guess to the user for the next buffer */
|
|
static EditBuffer *predict_switch_to_buffer(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditBuffer *b;
|
|
|
|
/* try and switch to the last buffer attached to the window */
|
|
b = check_buffer(&s->last_buffer);
|
|
if (b)
|
|
return b;
|
|
|
|
/* else try and switch to a buffer not shown in any window */
|
|
for (b = qs->first_buffer; b != NULL; b = b->next) {
|
|
if (!(b->flags & BF_SYSTEM)) {
|
|
if (!eb_find_window(b, NULL))
|
|
return b;
|
|
}
|
|
}
|
|
/* otherwise select current buffer. */
|
|
return s->b;
|
|
}
|
|
|
|
void do_switch_to_buffer(EditState *s, const char *bufname)
|
|
{
|
|
EditBuffer *b;
|
|
|
|
if (s->flags & WF_MINIBUF)
|
|
return;
|
|
|
|
/* XXX: Default buffer charset should be selectable */
|
|
b = eb_find_new(bufname, BF_SAVELOG | BF_UTF8);
|
|
if (b)
|
|
switch_to_buffer(s, b);
|
|
}
|
|
|
|
void do_toggle_read_only(EditState *s)
|
|
{
|
|
s->b->flags ^= BF_READONLY;
|
|
}
|
|
|
|
void do_not_modified(EditState *s, int argval)
|
|
{
|
|
s->b->modified = (argval != NO_ARG);
|
|
}
|
|
|
|
static void kill_buffer_confirm_cb(void *opaque, char *reply)
|
|
{
|
|
int yes_replied;
|
|
|
|
if (!reply)
|
|
return;
|
|
yes_replied = strequal(reply, "yes");
|
|
qe_free(&reply);
|
|
if (!yes_replied)
|
|
return;
|
|
qe_kill_buffer(opaque);
|
|
}
|
|
|
|
void do_kill_buffer(EditState *s, const char *bufname, int force)
|
|
{
|
|
char buf[1024];
|
|
EditBuffer *b;
|
|
|
|
b = eb_find(bufname);
|
|
if (!b) {
|
|
put_status(s, "No buffer %s", bufname);
|
|
} else {
|
|
/* if modified and associated to a filename, then ask */
|
|
if (!force && b->modified && b->filename[0] != '\0') {
|
|
snprintf(buf, sizeof(buf),
|
|
"Buffer %s modified; kill anyway? (yes or no) ", bufname);
|
|
minibuffer_edit(s, NULL, buf, NULL, NULL,
|
|
kill_buffer_confirm_cb, b);
|
|
} else {
|
|
qe_kill_buffer(b);
|
|
}
|
|
}
|
|
}
|
|
|
|
void qe_kill_buffer(EditBuffer *b)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditState *e;
|
|
EditBuffer *b1 = NULL;
|
|
|
|
if (!b)
|
|
return;
|
|
|
|
/* Check for windows showing the buffer:
|
|
* - Emacs makes any window showing the killed buffer switch to
|
|
* another buffer.
|
|
* - An alternative is to delete windows showing the buffer, but we
|
|
* cannot delete the main window, so switch to the scratch buffer.
|
|
*/
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (e->last_buffer == b) {
|
|
e->last_buffer = NULL;
|
|
}
|
|
if (e->b == b) {
|
|
if (!b1) {
|
|
/* find a new buffer to switch to */
|
|
for (b1 = qs->first_buffer; b1 != NULL; b1 = b1->next) {
|
|
if (b1 != b && !(b1->flags & BF_SYSTEM))
|
|
break;
|
|
}
|
|
if (!b1) {
|
|
b1 = eb_new("*scratch*", BF_SAVELOG | BF_UTF8);
|
|
}
|
|
}
|
|
switch_to_buffer(e, b1);
|
|
}
|
|
}
|
|
|
|
if (b->flags & BF_SYSTEM) {
|
|
int i;
|
|
for (i = 0; i < NB_YANK_BUFFERS; i++) {
|
|
if (qs->yank_buffers[i] == b)
|
|
qs->yank_buffers[i] = NULL;
|
|
}
|
|
}
|
|
|
|
/* now we can safely delete buffer */
|
|
eb_free(&b);
|
|
|
|
do_refresh(qs->first_window);
|
|
}
|
|
|
|
/* return TRUE if absolute path. works for files and URLs */
|
|
static int is_abs_path(const char *path)
|
|
{
|
|
size_t prefix;
|
|
|
|
if (*path == '/')
|
|
return 1;
|
|
|
|
/* Accept as absolute a drive or protocol followed by `/` */
|
|
prefix = strcspn(path, "/:");
|
|
if (path[prefix] == ':' && path[prefix + 1] == '/')
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* canonicalize the path for a given window and make it absolute */
|
|
void canonicalize_absolute_path(EditState *s, char *buf, int buf_size, const char *path1)
|
|
{
|
|
return canonicalize_absolute_buffer_path(s ? s->b : NULL, s ? s->offset : 0, buf, buf_size, path1);
|
|
}
|
|
|
|
void canonicalize_absolute_buffer_path(EditBuffer *b, int offset, char *buf, int buf_size, const char *path1)
|
|
{
|
|
char cwd[MAX_FILENAME_SIZE];
|
|
char path[MAX_FILENAME_SIZE];
|
|
char *homedir;
|
|
|
|
if (!is_abs_path(path1)) {
|
|
if (*path1 == '~') {
|
|
if (path1[1] == '\0' || path1[1] == '/') {
|
|
homedir = getenv("HOME");
|
|
if (homedir) {
|
|
pstrcpy(path, sizeof(path), homedir);
|
|
#ifdef CONFIG_WIN32
|
|
path_win_to_unix(path);
|
|
#endif
|
|
remove_slash(path);
|
|
pstrcat(path, sizeof(path), path1 + 1);
|
|
path1 = path;
|
|
}
|
|
} else {
|
|
/* CG: should get info from getpwnam */
|
|
#ifdef CONFIG_DARWIN
|
|
pstrcpy(path, sizeof(path), "/Users/");
|
|
#else
|
|
pstrcpy(path, sizeof(path), "/home/");
|
|
#endif
|
|
pstrcat(path, sizeof(path), path1 + 1);
|
|
path1 = path;
|
|
}
|
|
} else {
|
|
/* CG: not sufficient for windows drives */
|
|
if (!b || !get_default_path(b, offset, cwd, sizeof(cwd))) {
|
|
if (!getcwd(cwd, sizeof(cwd)))
|
|
strcpy(cwd, ".");
|
|
#ifdef CONFIG_WIN32
|
|
path_win_to_unix(cwd);
|
|
#endif
|
|
}
|
|
makepath(path, sizeof(path), cwd, path1);
|
|
path1 = path;
|
|
}
|
|
}
|
|
canonicalize_path(buf, buf_size, path1);
|
|
}
|
|
|
|
/* compute default path for find/save buffer */
|
|
char *get_default_path(EditBuffer *b, int offset, char *buf, int buf_size)
|
|
{
|
|
char buf1[MAX_FILENAME_SIZE];
|
|
const char *filename;
|
|
|
|
/* dispatch to mode specific handler if any */
|
|
if (b->default_mode
|
|
&& b->default_mode->get_default_path
|
|
&& b->default_mode->get_default_path(b, offset, buf, buf_size)) {
|
|
return buf;
|
|
}
|
|
|
|
if ((b->flags & BF_SYSTEM)
|
|
|| b->name[0] == '*'
|
|
|| b->filename[0] == '\0') {
|
|
filename = "a";
|
|
} else {
|
|
filename = b->filename;
|
|
}
|
|
/* XXX: should just retrieve the current directory */
|
|
canonicalize_absolute_path(NULL, buf1, sizeof(buf1), filename);
|
|
splitpath(buf, buf_size, NULL, 0, buf1);
|
|
return buf;
|
|
}
|
|
|
|
/* should have: rawbuf[len] == '\0' */
|
|
static int probe_mode(EditState *s, EditBuffer *b,
|
|
ModeDef **modes, int nb_modes,
|
|
int *scores, int min_score,
|
|
const char *filename, int st_errno, int st_mode,
|
|
long total_size, const uint8_t *rawbuf, int len,
|
|
QECharset *charset, EOLType eol_type)
|
|
{
|
|
u8 buf[4097];
|
|
QEmacsState *qs = s->qe_state;
|
|
char fname[MAX_FILENAME_SIZE];
|
|
ModeDef *m;
|
|
ModeProbeData probe_data;
|
|
int found_modes;
|
|
const uint8_t *p;
|
|
|
|
if (!modes || !scores || nb_modes < 1)
|
|
return 0;
|
|
|
|
found_modes = 0;
|
|
*modes = NULL;
|
|
*scores = 0;
|
|
|
|
probe_data.b = b;
|
|
probe_data.buf = buf;
|
|
probe_data.buf_size = len;
|
|
probe_data.real_filename = filename;
|
|
probe_data.st_errno = st_errno;
|
|
probe_data.st_mode = st_mode;
|
|
probe_data.total_size = total_size;
|
|
probe_data.filename = reduce_filename(fname, sizeof(fname),
|
|
get_basename(filename));
|
|
/* CG: should pass EditState? QEmacsState ? */
|
|
|
|
/* XXX: Should use eb_get_range_contents to deal with charset and
|
|
* eol_type instead of hand coding this conversion */
|
|
probe_data.eol_type = eol_type;
|
|
probe_data.charset = charset;
|
|
charset_decode_init(&probe_data.charset_state, charset, eol_type);
|
|
|
|
/* XXX: Should handle eol_type and transcode \r and \r\n */
|
|
if (charset == &charset_utf8
|
|
|| charset == &charset_raw
|
|
|| charset == &charset_8859_1) {
|
|
probe_data.buf = rawbuf;
|
|
probe_data.buf_size = len;
|
|
} else {
|
|
int offset = 0;
|
|
u8 *bufp = buf;
|
|
|
|
while (offset < len) {
|
|
int ch = probe_data.charset_state.table[rawbuf[offset]];
|
|
offset++;
|
|
if (ch == ESCAPE_CHAR) {
|
|
probe_data.charset_state.p = rawbuf + offset - 1;
|
|
ch = probe_data.charset_state.decode_func(&probe_data.charset_state);
|
|
offset = probe_data.charset_state.p - rawbuf;
|
|
}
|
|
bufp += utf8_encode((char *)bufp, ch);
|
|
if (bufp > buf + sizeof(buf) - MAX_CHAR_BYTES - 1)
|
|
break;
|
|
}
|
|
probe_data.buf = buf;
|
|
probe_data.buf_size = bufp - buf;
|
|
*bufp = '\0';
|
|
}
|
|
|
|
/* Skip the BOM if present */
|
|
if (probe_data.buf_size >= 3
|
|
&& probe_data.buf[0] == 0xEF
|
|
&& probe_data.buf[1] == 0xBB
|
|
&& probe_data.buf[2] == 0xBF) {
|
|
probe_data.buf += 3;
|
|
probe_data.buf_size -= 3;
|
|
}
|
|
|
|
charset_decode_close(&probe_data.charset_state);
|
|
|
|
p = memchr(probe_data.buf, '\n', probe_data.buf_size);
|
|
probe_data.line_len = p ? p - probe_data.buf : probe_data.buf_size;
|
|
|
|
for (m = qs->first_mode; m != NULL; m = m->next) {
|
|
if (m->mode_probe) {
|
|
int score = m->mode_probe(m, &probe_data);
|
|
if (score > min_score) {
|
|
int i;
|
|
/* sort appropriate modes by insertion in modes array */
|
|
for (i = 0; i < found_modes; i++) {
|
|
if (scores[i] < score)
|
|
break;
|
|
}
|
|
if (i < nb_modes) {
|
|
if (found_modes >= nb_modes)
|
|
found_modes = nb_modes - 1;
|
|
if (i < found_modes) {
|
|
memmove(modes + i + 1, modes + i,
|
|
(found_modes - i) * sizeof(*modes));
|
|
memmove(scores + i + 1, scores + i,
|
|
(found_modes - i) * sizeof(*scores));
|
|
}
|
|
modes[i] = m;
|
|
scores[i] = score;
|
|
found_modes++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return found_modes;
|
|
}
|
|
|
|
EditState *qe_find_target_window(EditState *s, int activate) {
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e;
|
|
|
|
/* Find the target window for some commands run from the dired window */
|
|
if (s->flags & WF_POPUP) {
|
|
e = check_window(&s->target_window);
|
|
if (e) {
|
|
if (activate && qs->active_window == s)
|
|
qs->active_window = e;
|
|
}
|
|
s->b->flags |= BF_TRANSIENT;
|
|
edit_close(&s);
|
|
s = e;
|
|
do_refresh(s);
|
|
}
|
|
#ifndef CONFIG_TINY
|
|
if (s && (s->flags & WF_POPLEFT) && s->x1 == 0) {
|
|
e = find_window(s, KEY_RIGHT, NULL);
|
|
if (e) {
|
|
if (activate && qs->active_window == s)
|
|
qs->active_window = e;
|
|
s = e;
|
|
}
|
|
}
|
|
#endif
|
|
return s;
|
|
}
|
|
|
|
/* Select appropriate mode for buffer:
|
|
* iff dir == 0, select best mode
|
|
* iff dir > 0, select next mode
|
|
* iff dir < 0, select previous mode
|
|
*/
|
|
void do_set_next_mode(EditState *s, int dir)
|
|
{
|
|
if (s->flags & (WF_POPUP | WF_MINIBUF))
|
|
return;
|
|
|
|
/* next-mode from the dired window applies to the target window */
|
|
s = qe_find_target_window(s, 0);
|
|
qe_set_next_mode(s, dir, 1);
|
|
}
|
|
|
|
void qe_set_next_mode(EditState *s, int dir, int status)
|
|
{
|
|
u8 buf[4097];
|
|
int size;
|
|
ModeDef *modes[32];
|
|
int scores[32];
|
|
int i, nb, found;
|
|
EditBuffer *b;
|
|
|
|
if (s->flags & WF_MINIBUF)
|
|
return;
|
|
|
|
#ifndef CONFIG_TINY
|
|
/* Find target window for POPLEFT pane */
|
|
if ((s->flags & WF_POPLEFT) && s->x1 == 0) {
|
|
EditState *e = find_window(s, KEY_RIGHT, NULL);
|
|
if (e) {
|
|
s = e;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
b = s->b;
|
|
size = eb_read(b, 0, buf, sizeof(buf) - 1);
|
|
buf[size] = '\0';
|
|
|
|
nb = probe_mode(s, b, modes, countof(modes), scores, 2,
|
|
b->filename, 0, b->st_mode, b->total_size,
|
|
buf, size, b->charset, b->eol_type);
|
|
found = 0;
|
|
if (dir && nb > 0) {
|
|
for (i = 0; i < nb; i++) {
|
|
if (s->mode == modes[i]) {
|
|
found = (i + dir) % nb;
|
|
if (found < 0)
|
|
found += nb;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
edit_set_mode(s, modes[found]);
|
|
if (status) {
|
|
put_status(s, "Mode is now %s, score=%d",
|
|
modes[found]->name, scores[found]);
|
|
}
|
|
}
|
|
|
|
/* Load a file and attach buffer to window `s`.
|
|
* Return -1 if loading failed.
|
|
* Return 0 if file or resource was already loaded,
|
|
* Return 1 if file or resource was newly loaded,
|
|
* Return 2 if buffer was created for a new file.
|
|
* Should take bits from enumeration instead of booleans.
|
|
*/
|
|
int qe_load_file(EditState *s, const char *filename1, int lflags, int bflags)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
u8 buf[4097];
|
|
char filename[MAX_FILENAME_SIZE];
|
|
int st_mode, buf_size, mode_score;
|
|
ModeDef *selected_mode;
|
|
EditBuffer *b;
|
|
EditBufferDataType *bdt;
|
|
FILE *f;
|
|
struct stat st;
|
|
EOLType eol_type = EOL_UNIX;
|
|
QECharset *charset = &charset_utf8;
|
|
|
|
#ifndef CONFIG_TINY
|
|
/* when exploring from a popleft dired buffer, load a directory or
|
|
* file pattern into the same pane, but load a regular file into the view pane
|
|
*/
|
|
if ((s->flags & WF_POPUP)
|
|
|| (!is_directory(filename) &&
|
|
((lflags & LF_NOWILDCARD) || !is_filepattern(filename)))) {
|
|
s = qe_find_target_window(s, 1);
|
|
}
|
|
#endif
|
|
|
|
if (s->flags & (WF_POPUP | WF_MINIBUF))
|
|
return - 1;
|
|
|
|
if (lflags & LF_SPLIT_WINDOW) {
|
|
/* Split window if window large enough and not empty */
|
|
/* XXX: should check s->height units */
|
|
if (s->height > 10 && s->b->total_size > 0) {
|
|
EditState *e = qe_split_window(s, SW_STACKED, 50);
|
|
if (e) {
|
|
qs->active_window = s = e;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((lflags & LF_LOAD_RESOURCE) && !strchr(filename1, '/')) {
|
|
if (find_resource_file(filename, sizeof(filename), filename1)) {
|
|
put_status(s, "Cannot find resource file '%s'", filename1);
|
|
return -1;
|
|
}
|
|
} else {
|
|
/* compute full name */
|
|
canonicalize_absolute_path((lflags & LF_CWD_RELATIVE) ? NULL : s,
|
|
filename, sizeof(filename), filename1);
|
|
}
|
|
|
|
/* If file already loaded in existing buffer, switch to that */
|
|
b = eb_find_file(filename);
|
|
if (b != NULL) {
|
|
switch_to_buffer(s, b);
|
|
return 0;
|
|
}
|
|
|
|
/* We are going to try and load a new file: potentially delete the
|
|
* current buffer if requested.
|
|
*/
|
|
if ((lflags & LF_KILL_BUFFER) && s->b && !s->b->modified) {
|
|
s->b->flags |= BF_TRANSIENT;
|
|
}
|
|
|
|
/* Create new buffer with unique name from filename */
|
|
b = eb_new(get_basename(filename), BF_SAVELOG | bflags);
|
|
eb_set_filename(b, filename);
|
|
|
|
/* XXX: should actually initialize SAVED_DATA area in new buffer */
|
|
s->offset = 0;
|
|
s->wrap = WRAP_AUTO; /* default mode may override this */
|
|
|
|
/* First we try to read the first block to determine the data type */
|
|
if (stat(filename, &st) < 0) {
|
|
int st_errno = errno;
|
|
/* XXX: default charset should be selectable. Should have auto
|
|
* charset transparent support for both utf8 and latin1.
|
|
* Use utf8 for now */
|
|
eb_set_charset(b, &charset_utf8, b->eol_type);
|
|
/* XXX: dired_mode_probe will check for wildcards in real_filename */
|
|
/* Try to determine the desired mode based on the filename. */
|
|
b->st_mode = st_mode = S_IFREG;
|
|
buf[0] = '\0';
|
|
buf_size = 0;
|
|
probe_mode(s, b, &selected_mode, 1, &mode_score, 2,
|
|
b->filename, st_errno, b->st_mode, b->total_size,
|
|
buf, buf_size, b->charset, b->eol_type);
|
|
|
|
/* Attach buffer to window, will set default_mode
|
|
* XXX: this will also load the file, incorrect for non raw modes
|
|
*/
|
|
b->default_mode = selected_mode;
|
|
switch_to_buffer(s, b);
|
|
if (b->data_type == &raw_data_type)
|
|
put_status(s, "(New file)");
|
|
do_load_qerc(s, s->b->filename);
|
|
return 2;
|
|
} else {
|
|
b->st_mode = st_mode = st.st_mode;
|
|
buf_size = 0;
|
|
f = NULL;
|
|
|
|
if (S_ISREG(st_mode)) {
|
|
f = fopen(filename, "r");
|
|
if (!f)
|
|
goto fail;
|
|
buf_size = fread(buf, 1, sizeof(buf) - 1, f);
|
|
if (buf_size <= 0 && ferror(f)) {
|
|
fclose(f);
|
|
f = NULL;
|
|
goto fail;
|
|
}
|
|
/* autodetect buffer charset */
|
|
/* XXX: should enforce 32 bit alignment of buf */
|
|
charset = detect_charset(buf, buf_size, &eol_type);
|
|
}
|
|
buf[buf_size] = '\0';
|
|
if (!probe_mode(s, b, &selected_mode, 1, &mode_score, 2,
|
|
filename, 0, b->st_mode, st.st_size,
|
|
buf, buf_size, charset, eol_type)) {
|
|
fclose(f);
|
|
f = NULL;
|
|
goto fail;
|
|
}
|
|
bdt = selected_mode->data_type;
|
|
if (bdt == &raw_data_type)
|
|
eb_set_charset(b, charset, eol_type);
|
|
|
|
if (f) {
|
|
/* XXX: should use f to load buffer if raw_data_type */
|
|
fclose(f);
|
|
f = NULL;
|
|
}
|
|
|
|
b->default_mode = selected_mode;
|
|
/* attaching the buffer to the window will set the default_mode
|
|
* which in turn will load the data.
|
|
* XXX: This is an ugly side effect, ineffective for
|
|
* asynchronous shell buffers.
|
|
* XXX: should instead instantiate the mode on the buffer and
|
|
* test the resulting data_mode, loading the file if raw_mode
|
|
* selected.
|
|
*/
|
|
if (!(lflags & LF_NOSELECT)) {
|
|
/* XXX: bug: file loading will be delayed until buffer is
|
|
* attached to a window.
|
|
*/
|
|
switch_to_buffer(s, b);
|
|
}
|
|
if (access(b->filename, W_OK)) {
|
|
b->flags |= BF_READONLY;
|
|
}
|
|
do_load_qerc(s, s->b->filename);
|
|
|
|
/* XXX: invalid place */
|
|
edit_invalidate(s, 0);
|
|
return 1;
|
|
}
|
|
|
|
fail:
|
|
eb_free(&b);
|
|
|
|
put_status(s, "Could not open '%s': %s",
|
|
filename, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
void qe_save_open_files(EditState *s, EditBuffer *b)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditBuffer *b1;
|
|
|
|
eb_puts(b, "// open files:\n");
|
|
for (b1 = qs->first_buffer; b1 != NULL; b1 = b1->next) {
|
|
if (!(b1->flags & BF_SYSTEM) && *b1->filename)
|
|
eb_printf(b, "find_file(\"%s\");\n", b1->filename);
|
|
}
|
|
eb_putc(b, '\n');
|
|
}
|
|
|
|
#if 0
|
|
static void load_progress_cb(void *opaque, int size)
|
|
{
|
|
EditState *s = opaque;
|
|
EditBuffer *b = s->b;
|
|
|
|
if (size >= 1024 && !b->probed) {
|
|
qe_set_next_mode(s, 0, 0);
|
|
}
|
|
}
|
|
|
|
static void load_completion_cb(void *opaque, int err)
|
|
{
|
|
EditState *s = opaque;
|
|
|
|
/* CG: potential problem: EXXX may be negative, as in Haiku */
|
|
if (err == -ENOENT || err == -ENOTDIR) {
|
|
put_status(s, "(New file)");
|
|
} else
|
|
if (err == -EISDIR) {
|
|
s->b->st_mode = S_IFDIR;
|
|
} else
|
|
if (err < 0) {
|
|
put_status(s, "Could not read file");
|
|
}
|
|
if (!s->b->probed) {
|
|
qe_set_next_mode(s, 0, 0);
|
|
}
|
|
edit_display(s->qe_state);
|
|
dpy_flush(&global_screen);
|
|
}
|
|
#endif
|
|
|
|
void do_find_file(EditState *s, const char *filename, int bflags)
|
|
{
|
|
qe_load_file(s, filename, 0, bflags);
|
|
}
|
|
|
|
void do_find_file_other_window(EditState *s, const char *filename, int bflags)
|
|
{
|
|
qe_load_file(s, filename, LF_SPLIT_WINDOW, bflags);
|
|
}
|
|
|
|
void do_find_alternate_file(EditState *s, const char *filename, int bflags)
|
|
{
|
|
qe_load_file(s, filename, LF_KILL_BUFFER, bflags);
|
|
}
|
|
|
|
void do_find_file_noselect(EditState *s, const char *filename, int bflags)
|
|
{
|
|
qe_load_file(s, filename, LF_NOSELECT, bflags);
|
|
}
|
|
|
|
void do_load_file_from_path(EditState *s, const char *filename, int bflags)
|
|
{
|
|
qe_load_file(s, filename, LF_LOAD_RESOURCE, bflags);
|
|
}
|
|
|
|
void do_insert_file(EditState *s, const char *filename)
|
|
{
|
|
FILE *f;
|
|
int size, lastsize = s->b->total_size;
|
|
|
|
f = fopen(filename, "r");
|
|
if (!f) {
|
|
put_status(s, "Could not open file '%s'", filename);
|
|
return;
|
|
}
|
|
/* CG: file charset will not be converted to buffer charset */
|
|
/* CG: should load in a separate buffer, auto-detect charset and
|
|
* copy buffer contents with charset translation
|
|
*/
|
|
size = eb_raw_buffer_load1(s->b, f, s->offset);
|
|
fclose(f);
|
|
|
|
/* mark the insert chunk */
|
|
s->b->mark = s->offset;
|
|
s->offset += s->b->total_size - lastsize;
|
|
|
|
if (size < 0) {
|
|
put_status(s, "Error reading '%s'", filename);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void do_set_visited_file_name(EditState *s, const char *filename,
|
|
const char *renamefile)
|
|
{
|
|
char path[MAX_FILENAME_SIZE];
|
|
|
|
canonicalize_absolute_path(s, path, sizeof(path), filename);
|
|
if (*renamefile == 'y' && *s->b->filename) {
|
|
if (rename(s->b->filename, path))
|
|
put_status(s, "Cannot rename file to %s", path);
|
|
}
|
|
eb_set_filename(s->b, path);
|
|
}
|
|
|
|
static void put_save_message(EditState *s, const char *filename, int nb)
|
|
{
|
|
if (nb >= 0) {
|
|
put_status(s, "Wrote %d bytes to %s", nb, filename);
|
|
} else {
|
|
put_status(s, "Could not write %s", filename);
|
|
}
|
|
}
|
|
|
|
void do_save_buffer(EditState *s)
|
|
{
|
|
if (!s->b->modified) {
|
|
/* CG: This behaviour bugs me! */
|
|
put_status(s, "(No changes need to be saved)");
|
|
return;
|
|
}
|
|
put_save_message(s, s->b->filename, eb_save_buffer(s->b));
|
|
}
|
|
|
|
void do_write_file(EditState *s, const char *filename)
|
|
{
|
|
do_set_visited_file_name(s, filename, "n");
|
|
/* CG: Override bogus behaviour on unmodified buffers */
|
|
s->b->modified = 1;
|
|
do_save_buffer(s);
|
|
}
|
|
|
|
void do_write_region(EditState *s, const char *filename)
|
|
{
|
|
char absname[MAX_FILENAME_SIZE];
|
|
|
|
/* deactivate region hilite */
|
|
s->region_style = 0;
|
|
|
|
canonicalize_absolute_path(s, absname, sizeof(absname), filename);
|
|
put_save_message(s, filename,
|
|
eb_write_buffer(s->b, s->b->mark, s->offset, filename));
|
|
}
|
|
|
|
enum QSState {
|
|
QS_ASK,
|
|
QS_NOSAVE,
|
|
QS_SAVE,
|
|
};
|
|
|
|
typedef struct QuitState {
|
|
enum QSState state;
|
|
int modified;
|
|
EditBuffer *b;
|
|
} QuitState;
|
|
|
|
static void quit_examine_buffers(QuitState *is);
|
|
static void quit_key(void *opaque, int ch);
|
|
static void quit_confirm_cb(void *opaque, char *reply);
|
|
|
|
void do_exit_qemacs(EditState *s, int argval)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
QuitState *is;
|
|
|
|
if (argval != NO_ARG) {
|
|
url_exit();
|
|
return;
|
|
}
|
|
|
|
is = qe_mallocz(QuitState);
|
|
if (!is)
|
|
return;
|
|
|
|
/* scan each buffer and ask to save it if it was modified */
|
|
is->modified = 0;
|
|
is->state = QS_ASK;
|
|
is->b = qs->first_buffer;
|
|
|
|
qe_grab_keys(quit_key, is);
|
|
quit_examine_buffers(is);
|
|
}
|
|
|
|
/* analyse next buffer and ask question if needed */
|
|
static void quit_examine_buffers(QuitState *is)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditBuffer *b;
|
|
|
|
while (is->b != NULL) {
|
|
b = is->b;
|
|
if (!(b->flags & BF_SYSTEM) && b->filename[0] != '\0' && b->modified) {
|
|
switch (is->state) {
|
|
case QS_ASK:
|
|
/* XXX: display cursor */
|
|
put_status(qs->active_window,
|
|
"Save file %s? (y, n, !, ., q) ", b->filename);
|
|
dpy_flush(&global_screen);
|
|
/* will wait for a key */
|
|
return;
|
|
case QS_NOSAVE:
|
|
is->modified = 1;
|
|
break;
|
|
case QS_SAVE:
|
|
eb_save_buffer(b);
|
|
break;
|
|
}
|
|
}
|
|
is->b = is->b->next;
|
|
}
|
|
qe_ungrab_keys();
|
|
|
|
/* now asks for confirmation or exit directly */
|
|
if (is->modified) {
|
|
minibuffer_edit(qs->active_window,
|
|
NULL, "Modified buffers exist; exit anyway? (yes or no) ",
|
|
NULL, NULL, quit_confirm_cb, NULL);
|
|
edit_display(&qe_state);
|
|
dpy_flush(&global_screen);
|
|
} else {
|
|
#ifndef CONFIG_TINY
|
|
if (use_session_file)
|
|
do_save_session(qs->active_window, 0);
|
|
#endif
|
|
qe_free(&is);
|
|
url_exit();
|
|
}
|
|
}
|
|
|
|
static void quit_key(void *opaque, int ch)
|
|
{
|
|
QuitState *is = opaque;
|
|
EditBuffer *b;
|
|
|
|
switch (ch) {
|
|
case 'y':
|
|
case ' ':
|
|
/* save buffer */
|
|
goto do_save;
|
|
case 'n':
|
|
case KEY_DELETE:
|
|
is->modified = 1;
|
|
break;
|
|
case 'q':
|
|
case KEY_RET:
|
|
is->state = QS_NOSAVE;
|
|
is->modified = 1;
|
|
break;
|
|
case '!':
|
|
/* save all buffers */
|
|
is->state = QS_SAVE;
|
|
goto do_save;
|
|
case '.':
|
|
/* save current and exit */
|
|
is->state = QS_NOSAVE;
|
|
do_save:
|
|
b = is->b;
|
|
eb_save_buffer(b);
|
|
break;
|
|
case KEY_CTRL('g'):
|
|
/* abort */
|
|
put_status(NULL, "Quit");
|
|
dpy_flush(&global_screen);
|
|
qe_ungrab_keys();
|
|
return;
|
|
default:
|
|
/* get another key */
|
|
return;
|
|
}
|
|
is->b = is->b->next;
|
|
quit_examine_buffers(is);
|
|
}
|
|
|
|
static void quit_confirm_cb(qe__unused__ void *opaque, char *reply)
|
|
{
|
|
if (!reply)
|
|
return;
|
|
if (reply[0] == 'y' || reply[0] == 'Y')
|
|
url_exit();
|
|
qe_free(&reply);
|
|
}
|
|
|
|
/*----------------*/
|
|
|
|
int get_glyph_width(QEditScreen *screen, EditState *s, QETermStyle style, int c)
|
|
{
|
|
QEStyleDef styledef;
|
|
QEFont *font;
|
|
int width = 1;
|
|
|
|
get_style(s, &styledef, style);
|
|
font = select_font(screen, styledef.font_style, styledef.font_size);
|
|
if (font) {
|
|
width = glyph_width(screen, font, c);
|
|
release_font(screen, font);
|
|
}
|
|
return width;
|
|
}
|
|
|
|
int get_line_height(QEditScreen *screen, EditState *s, QETermStyle style)
|
|
{
|
|
QEStyleDef styledef;
|
|
QEFont *font;
|
|
int height = 1;
|
|
|
|
get_style(s, &styledef, style);
|
|
font = select_font(screen, styledef.font_style, styledef.font_size);
|
|
if (font) {
|
|
height = font->ascent + font->descent;
|
|
release_font(screen, font);
|
|
}
|
|
return height;
|
|
}
|
|
|
|
void edit_invalidate(EditState *s, int all)
|
|
{
|
|
/* invalidate the modeline buffer */
|
|
s->modeline_shadow[0] = '\0';
|
|
s->display_invalid = 1;
|
|
if (all) {
|
|
EditState *e;
|
|
for (e = s->qe_state->first_window; e != NULL; e = e->next_window) {
|
|
if (e->b == s->b) {
|
|
s->modeline_shadow[0] = '\0';
|
|
s->display_invalid = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* refresh the screen, s1 can be any edit window */
|
|
void do_refresh(EditState *s1)
|
|
{
|
|
/* CG: s1 may be NULL */
|
|
QEmacsState *qs = s1 ? s1->qe_state : &qe_state;
|
|
EditState *e;
|
|
int new_status_height, new_mode_line_height, content_height;
|
|
int width, height, resized;
|
|
|
|
if (qs->complete_refresh) {
|
|
dpy_invalidate(qs->screen);
|
|
}
|
|
|
|
/* recompute various dimensions */
|
|
if (qs->screen->media & CSS_MEDIA_TTY) {
|
|
qs->separator_width = 1;
|
|
} else {
|
|
qs->separator_width = 4;
|
|
}
|
|
qs->border_width = 1; /* XXX: adapt to display type */
|
|
|
|
width = qs->screen->width;
|
|
height = qs->screen->height;
|
|
new_status_height = get_line_height(qs->screen, NULL, QE_STYLE_STATUS);
|
|
new_mode_line_height = get_line_height(qs->screen, NULL, QE_STYLE_MODE_LINE);
|
|
content_height = height;
|
|
if (!qs->hide_status)
|
|
content_height -= new_status_height;
|
|
|
|
/* Prevent potential division overflow */
|
|
width = max(1, width);
|
|
height = max(1, height);
|
|
content_height = max(1, content_height);
|
|
|
|
resized = 0;
|
|
|
|
/* see if resize is necessary */
|
|
if (qs->width != width ||
|
|
qs->height != height ||
|
|
qs->status_height != new_status_height ||
|
|
qs->mode_line_height != new_mode_line_height ||
|
|
qs->content_height != content_height) {
|
|
|
|
/* do the resize */
|
|
resized = 1;
|
|
qs->complete_refresh = 1;
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (e->flags & WF_MINIBUF) {
|
|
/* first resize minibuffer if present */
|
|
e->x1 = 0;
|
|
e->y1 = content_height;
|
|
e->x2 = width;
|
|
e->y2 = height;
|
|
} else
|
|
if (qs->height == 0 || qs->width == 0 || qs->content_height == 0) {
|
|
/* needed only to init the window size for the first time */
|
|
e->x1 = 0;
|
|
e->y1 = 0;
|
|
e->y2 = content_height;
|
|
e->x2 = width;
|
|
} else {
|
|
/* NOTE: to ensure that no rounding errors are made,
|
|
we resize the absolute coordinates */
|
|
e->x1 = (e->x1 * width + qs->width / 2) / qs->width;
|
|
e->x2 = (e->x2 * width + qs->width / 2) / qs->width;
|
|
e->y1 = (e->y1 * content_height + qs->content_height / 2) / qs->content_height;
|
|
e->y2 = (e->y2 * content_height + qs->content_height / 2) / qs->content_height;
|
|
}
|
|
}
|
|
|
|
qs->width = width;
|
|
qs->height = height;
|
|
qs->status_height = new_status_height;
|
|
qs->mode_line_height = new_mode_line_height;
|
|
qs->content_height = content_height;
|
|
}
|
|
/* compute client area */
|
|
for (e = qs->first_window; e != NULL; e = e->next_window)
|
|
compute_client_area(e);
|
|
|
|
/* invalidate all the edit windows and draw borders */
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
edit_invalidate(e, 0);
|
|
e->borders_invalid = 1;
|
|
}
|
|
/* invalidate status line */
|
|
qs->status_shadow[0] = '\0';
|
|
|
|
if (resized) {
|
|
/* CG: should compute column count w/ default count */
|
|
put_status(NULL, "Screen is now %d by %d (%d rows)",
|
|
width, height, height / new_status_height);
|
|
}
|
|
}
|
|
|
|
void do_refresh_complete(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
|
|
qs->complete_refresh = 1;
|
|
|
|
if (s->qe_state->last_cmd_func == (CmdFunc)do_refresh_complete) {
|
|
do_center_cursor(s, 1);
|
|
} else {
|
|
do_refresh(s);
|
|
}
|
|
}
|
|
|
|
void do_other_window(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e;
|
|
|
|
e = s->next_window;
|
|
if (!e)
|
|
e = qs->first_window;
|
|
qs->active_window = e;
|
|
}
|
|
|
|
void do_previous_window(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e;
|
|
|
|
for (e = qs->first_window; e->next_window != NULL; e = e->next_window) {
|
|
if (e->next_window == s)
|
|
break;
|
|
}
|
|
qs->active_window = e;
|
|
}
|
|
|
|
/* Delete a window and try to resize other windows so that it gets
|
|
covered. If force is not true, do not accept to kill window if it
|
|
is the only window or if it is the minibuffer window. */
|
|
void do_delete_window(EditState *s, int force)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e, *e1 = NULL;
|
|
int count, pass, x1, y1, x2, y2;
|
|
|
|
count = 0;
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (!(e->flags & (WF_POPUP | WF_MINIBUF)))
|
|
count++;
|
|
}
|
|
/* cannot close minibuf or if single window */
|
|
if (((s->flags & WF_MINIBUF) || count <= 1) && !force)
|
|
return;
|
|
|
|
if (!(s->flags & WF_POPUP)) {
|
|
/* Try to merge the window with adjacent windows.
|
|
* If this cannot be done, just leave a hole and force full
|
|
* redisplay.
|
|
*/
|
|
x1 = s->x1;
|
|
x2 = s->x2;
|
|
y1 = s->y1;
|
|
y2 = s->y2;
|
|
|
|
for (pass = 0; pass < 2; pass++) {
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (e == s || (e->flags & (WF_POPUP | WF_MINIBUF)))
|
|
continue;
|
|
|
|
if (x1 == e->x2 && y1 == e->y1 && y2 >= e->y2) {
|
|
/* partial vertical split along the left border */
|
|
e->x2 = x2;
|
|
e->flags &= ~WF_RSEPARATOR;
|
|
e->flags |= s->flags & WF_RSEPARATOR;
|
|
y1 = e->y2;
|
|
} else
|
|
if (x2 == e->x1 && y1 == e->y1 && y2 >= e->y2) {
|
|
/* partial vertical split along the right border */
|
|
e->x1 = x1;
|
|
y1 = e->y2;
|
|
} else
|
|
if (y1 == e->y2 && x1 == e->x1 && x2 >= e->x2) {
|
|
/* partial horizontal split along the top border */
|
|
e->y2 = y2;
|
|
x1 = e->x2;
|
|
} else
|
|
if (y2 == e->y1 && x1 == e->x1 && x2 >= e->x2) {
|
|
/* partial horizontal split along bottom border */
|
|
e->y1 = y1;
|
|
x1 = e->x2;
|
|
} else {
|
|
continue;
|
|
}
|
|
compute_client_area(e1 = e);
|
|
}
|
|
if (x1 == x2 || y1 == y2)
|
|
break;
|
|
}
|
|
if (x1 != x2 && y1 != y2)
|
|
qs->complete_refresh = 1;
|
|
}
|
|
if (qs->active_window == s)
|
|
qs->active_window = e1 ? e1 : qs->first_window;
|
|
|
|
edit_close(&s);
|
|
if (qs->first_window)
|
|
do_refresh(qs->first_window);
|
|
}
|
|
|
|
void do_delete_other_windows(EditState *s, int all)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e;
|
|
|
|
if (s->flags & (WF_POPUP | WF_MINIBUF))
|
|
return;
|
|
|
|
for (;;) {
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (!(e->flags & WF_MINIBUF) && e != s)
|
|
break;
|
|
}
|
|
if (e == NULL)
|
|
break;
|
|
/* rescan after closing a window because another window could
|
|
* be closed as a side effect
|
|
*/
|
|
edit_close(&e);
|
|
}
|
|
if (all) {
|
|
edit_close(&s);
|
|
} else {
|
|
/* resize to whole screen */
|
|
s->y1 = 0;
|
|
s->x1 = 0;
|
|
s->x2 = qs->width;
|
|
s->y2 = qs->height - qs->status_height;
|
|
s->flags &= ~(WF_RSEPARATOR | WF_POPLEFT);
|
|
compute_client_area(s);
|
|
do_refresh(s);
|
|
}
|
|
}
|
|
|
|
void do_hide_window(EditState *s, int set)
|
|
{
|
|
if (set)
|
|
s->flags |= WF_HIDDEN;
|
|
else
|
|
s->flags &= ~WF_HIDDEN;
|
|
}
|
|
|
|
void do_delete_hidden_windows(EditState *s)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e, *e1;
|
|
|
|
for (e = qs->first_window; e != NULL; e = e1) {
|
|
e1 = e->next_window;
|
|
if (e->flags & WF_HIDDEN)
|
|
edit_close(&e);
|
|
}
|
|
}
|
|
|
|
/* XXX: add minimum size test and refuse to split if reached */
|
|
EditState *qe_split_window(EditState *s, int side_by_side, int prop)
|
|
{
|
|
EditState *e;
|
|
int w, h, w1, h1;
|
|
|
|
/* cannot split minibuf or popup */
|
|
if (s->flags & (WF_POPUP | WF_MINIBUF))
|
|
return NULL;
|
|
|
|
if (prop <= 0)
|
|
return NULL;
|
|
|
|
/* This will clone mode and mode data to the newly created window */
|
|
generic_save_window_data(s);
|
|
w = s->x2 - s->x1;
|
|
h = s->y2 - s->y1;
|
|
if (side_by_side) {
|
|
w1 = (w * min(prop, 100) + 50) / 100;
|
|
e = edit_new(s->b, s->x1 + w1, s->y1,
|
|
w - w1, h, WF_MODELINE | (s->flags & WF_RSEPARATOR));
|
|
if (!e)
|
|
return NULL;
|
|
s->x2 = s->x1 + w1;
|
|
s->flags |= WF_RSEPARATOR;
|
|
} else {
|
|
h1 = (h * min(prop, 100) + 50) / 100;
|
|
e = edit_new(s->b, s->x1, s->y1 + h1,
|
|
w, h - h1, WF_MODELINE | (s->flags & WF_RSEPARATOR));
|
|
if (!e)
|
|
return NULL;
|
|
s->y2 = s->y1 + h1;
|
|
}
|
|
compute_client_area(s);
|
|
|
|
/* reposition new window in window list just after s */
|
|
edit_attach(e, s->next_window);
|
|
|
|
do_refresh(s);
|
|
return e;
|
|
}
|
|
|
|
void do_split_window(EditState *s, int side_by_side, int prop)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e = qe_split_window(s, side_by_side, prop == NO_ARG ? 50 : prop);
|
|
|
|
if (e && qs->flag_split_window_change_focus)
|
|
qs->active_window = e;
|
|
}
|
|
|
|
void do_create_window(EditState *s, const char *filename, const char *layout)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
static const char * const names[] = {
|
|
"x1:", "y1:", "x2:", "y2:", "flags:", "wrap:",
|
|
"offset:", "offset.col:", "mark:", "mark.col:", "top:", "top.col:",
|
|
"active:",
|
|
};
|
|
int args[] = { 0, 0, 0, 0, WF_MODELINE, WRAP_AUTO, 0, 0, 0, 0, 0, 0, 0 };
|
|
ModeDef *m = NULL;
|
|
int i, n, x1, y1, x2, y2, flags;
|
|
enum WrapType wrap;
|
|
const char *p = layout;
|
|
EditBuffer *b1;
|
|
|
|
b1 = eb_find_file(filename);
|
|
if (!b1) {
|
|
put_status(s, "create_window: no such file loaded: %s", filename);
|
|
return;
|
|
}
|
|
|
|
for (n = 0; *p; n++) {
|
|
while (qe_isblank(*p))
|
|
p++;
|
|
for (i = 0; i < countof(names); i++) {
|
|
if (strstart(p, names[i], &p)) {
|
|
n = i;
|
|
break;
|
|
}
|
|
}
|
|
if (strstart(p, "mode:", &p)) {
|
|
m = qe_find_mode(p, 0);
|
|
break;
|
|
}
|
|
if (n >= countof(args))
|
|
break;
|
|
|
|
args[n] = strtol(p, (char **)&p, 0);
|
|
while (qe_isblank(*p))
|
|
p++;
|
|
if (*p == ',')
|
|
p++;
|
|
}
|
|
x1 = scale(args[0], qs->width, 1000);
|
|
y1 = scale(args[1], qs->height - qs->status_height, 1000);
|
|
x2 = scale(args[2], qs->width, 1000);
|
|
y2 = scale(args[3], qs->height - qs->status_height, 1000);
|
|
flags = args[4];
|
|
wrap = (enum WrapType)args[5];
|
|
|
|
s = edit_new(b1, x1, y1, x2 - x1, y2 - y1, flags);
|
|
if (m)
|
|
edit_set_mode(s, m);
|
|
s->wrap = wrap;
|
|
s->offset = clamp(eb_goto_pos(b1, args[6], args[7]), 0, b1->total_size);
|
|
s->b->mark = clamp(eb_goto_pos(b1, args[8], args[9]), 0, b1->total_size);
|
|
s->offset_top = clamp(eb_goto_pos(b1, args[10], args[11]), 0, b1->total_size);
|
|
if (args[12])
|
|
qs->active_window = s;
|
|
|
|
do_refresh(s);
|
|
}
|
|
|
|
void qe_save_window_layout(EditState *s, EditBuffer *b)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
const EditState *e;
|
|
int offset_row, offset_col;
|
|
int mark_row, mark_col;
|
|
int top_row, top_col;
|
|
|
|
eb_puts(b, "// window layout:\n");
|
|
/* Get rid of default window */
|
|
// XXX: should simplify layout management
|
|
// XXX: should save mark, offset, offset_top
|
|
eb_puts(b, "delete_other_windows();\n");
|
|
eb_puts(b, "hide_window();\n");
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (*e->b->filename) {
|
|
eb_get_pos(e->b, &offset_row, &offset_col, e->offset);
|
|
eb_get_pos(e->b, &mark_row, &mark_col, e->b->mark);
|
|
eb_get_pos(e->b, &top_row, &top_col, e->offset_top);
|
|
eb_printf(b, "create_window(\"%s\", "
|
|
"\"%d,%d,%d,%d flags:%d wrap:%d",
|
|
e->b->filename,
|
|
scale(e->x1, 1000, qs->width),
|
|
scale(e->y1, 1000, qs->height - qs->status_height),
|
|
scale(e->x2, 1000, qs->width),
|
|
scale(e->y2, 1000, qs->height - qs->status_height),
|
|
e->flags, e->wrap);
|
|
if (e->offset)
|
|
eb_printf(b, " offset:%d,%d", offset_row, offset_col);
|
|
if (e->b->mark)
|
|
eb_printf(b, " mark:%d,%d", mark_row, mark_col);
|
|
if (e->offset_top)
|
|
eb_printf(b, " top:%d,%d", top_row, top_col);
|
|
if (e == qs->active_window)
|
|
eb_printf(b, " active:1");
|
|
eb_printf(b, " mode:%s\");\n", e->mode->name);
|
|
}
|
|
}
|
|
eb_puts(b, "delete_hidden_windows();\n");
|
|
eb_putc(b, '\n');
|
|
}
|
|
|
|
/* help */
|
|
|
|
void do_describe_key_briefly(EditState *s)
|
|
{
|
|
put_status(s, "Describe key: ");
|
|
key_ctx.describe_key = 1;
|
|
}
|
|
|
|
EditBuffer *new_help_buffer(void)
|
|
{
|
|
EditBuffer *b;
|
|
|
|
b = eb_find("*Help*");
|
|
if (b) {
|
|
eb_clear(b);
|
|
} else {
|
|
b = eb_new("*Help*", BF_SYSTEM | BF_UTF8 | BF_STYLE1);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
void do_help_for_help(EditState *s)
|
|
{
|
|
EditBuffer *b;
|
|
|
|
b = new_help_buffer();
|
|
if (!b)
|
|
return;
|
|
|
|
eb_puts(b,
|
|
"QEmacs help for help - Press q to quit:\n"
|
|
"\n"
|
|
"C-h C-h Show this help\n"
|
|
"C-h b Display table of all key bindings\n"
|
|
"C-h c Describe key briefly\n"
|
|
);
|
|
b->flags |= BF_READONLY;
|
|
show_popup(s, b, "QEmacs help for help - Press q to quit:");
|
|
}
|
|
|
|
#ifdef CONFIG_WIN32
|
|
|
|
void qe_event_init(void)
|
|
{
|
|
}
|
|
|
|
#else
|
|
|
|
/* we install a signal handler to set poll_flag to one so that we can
|
|
avoid polling too often in some cases */
|
|
|
|
int qe__fast_test_event_poll_flag = 0;
|
|
|
|
static void poll_action(qe__unused__ int sig)
|
|
{
|
|
qe__fast_test_event_poll_flag = 1;
|
|
}
|
|
|
|
/* init event system */
|
|
void qe_event_init(void)
|
|
{
|
|
struct sigaction sigact;
|
|
struct itimerval itimer;
|
|
|
|
#ifdef SA_RESTART
|
|
sigact.sa_flags = SA_RESTART;
|
|
#else
|
|
#warning SA_RESTART not defined
|
|
#endif
|
|
sigact.sa_handler = poll_action;
|
|
sigemptyset(&sigact.sa_mask);
|
|
sigaction(SIGVTALRM, &sigact, NULL);
|
|
|
|
itimer.it_interval.tv_sec = 0;
|
|
itimer.it_interval.tv_usec = 20 * 1000; /* 50 times per second */
|
|
itimer.it_value = itimer.it_interval;
|
|
setitimer(ITIMER_VIRTUAL, &itimer, NULL);
|
|
}
|
|
|
|
/* see also qe_fast_test_event() */
|
|
int qe__is_user_input_pending(void)
|
|
{
|
|
QEditScreen *s = &global_screen;
|
|
return s->dpy.dpy_is_user_input_pending(s);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifndef CONFIG_TINY
|
|
|
|
void window_get_min_size(EditState *s, int *w_ptr, int *h_ptr)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
int w, h;
|
|
|
|
/* XXX: currently, fixed height */
|
|
w = 8;
|
|
h = 8;
|
|
if (s->flags & WF_MODELINE)
|
|
h += qs->mode_line_height;
|
|
*w_ptr = w;
|
|
*h_ptr = h;
|
|
}
|
|
|
|
/* resize a window on bottom right edge */
|
|
void window_resize(EditState *s, int target_w, int target_h)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
EditState *e;
|
|
int delta_y, delta_x, min_w, min_h, new_h, new_w;
|
|
|
|
delta_x = target_w - (s->x2 - s->x1);
|
|
delta_y = target_h - (s->y2 - s->y1);
|
|
|
|
/* then see if we can resize without having too small windows */
|
|
window_get_min_size(s, &min_w, &min_h);
|
|
if (target_w < min_w ||
|
|
target_h < min_h)
|
|
return;
|
|
/* check if moving would uncover empty regions */
|
|
if ((s->x2 >= qs->screen->width && delta_x != 0) ||
|
|
(s->y2 >= qs->screen->height - qs->status_height && delta_y != 0))
|
|
return;
|
|
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if ((e->flags & WF_MINIBUF) || e == s)
|
|
continue;
|
|
window_get_min_size(e, &min_w, &min_h);
|
|
if (e->y1 == s->y2) {
|
|
new_h = e->y2 - e->y1 - delta_y;
|
|
goto test_h;
|
|
} else if (e->y2 == s->y2) {
|
|
new_h = e->y2 - e->y1 + delta_y;
|
|
test_h:
|
|
if (new_h < min_h)
|
|
return;
|
|
}
|
|
if (e->x1 == s->x2) {
|
|
new_w = e->x2 - e->x1 - delta_x;
|
|
goto test_w;
|
|
} else if (e->x2 == s->x2) {
|
|
new_w = e->x2 - e->x1 + delta_x;
|
|
test_w:
|
|
if (new_w < min_w)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* now everything is OK, we can resize all windows */
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if ((e->flags & WF_MINIBUF) || e == s)
|
|
continue;
|
|
if (e->y1 == s->y2)
|
|
e->y1 += delta_y;
|
|
else if (e->y2 == s->y2)
|
|
e->y2 += delta_y;
|
|
if (e->x1 == s->x2)
|
|
e->x1 += delta_x;
|
|
else if (e->x2 == s->x2)
|
|
e->x2 += delta_x;
|
|
compute_client_area(e);
|
|
}
|
|
s->x2 = s->x1 + target_w;
|
|
s->y2 = s->y1 + target_h;
|
|
compute_client_area(s);
|
|
}
|
|
|
|
/* mouse handling */
|
|
|
|
#define MOTION_NONE 0
|
|
#define MOTION_MODELINE 1
|
|
#define MOTION_RSEPARATOR 2
|
|
#define MOTION_TEXT 3
|
|
|
|
static int motion_type = MOTION_NONE;
|
|
static EditState *motion_target;
|
|
static int motion_x, motion_y;
|
|
|
|
static int check_motion_target(qe__unused__ EditState *s)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditState *e;
|
|
/* first verify that window is always valid */
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
if (e == motion_target)
|
|
break;
|
|
}
|
|
return e != NULL;
|
|
}
|
|
|
|
/* remove temporary selection colorization and selection area */
|
|
static void save_selection(void)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditState *e;
|
|
int selection_showed;
|
|
|
|
selection_showed = 0;
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
selection_showed |= e->show_selection;
|
|
e->show_selection = 0;
|
|
}
|
|
if (selection_showed && motion_type == MOTION_TEXT) {
|
|
motion_type = MOTION_NONE;
|
|
e = motion_target;
|
|
if (!check_motion_target(e))
|
|
return;
|
|
do_kill_region(e, 1);
|
|
}
|
|
}
|
|
|
|
/* XXX: need a more general scheme for other modes such as HTML/image */
|
|
/* CG: remove this */
|
|
void wheel_scroll_up_down(EditState *s, int dir)
|
|
{
|
|
int line_height;
|
|
|
|
/* only apply to text modes */
|
|
if (!s->mode->display_line)
|
|
return;
|
|
|
|
line_height = get_line_height(s->screen, s, QE_STYLE_DEFAULT);
|
|
perform_scroll_up_down(s, dir * WHEEL_SCROLL_STEP * line_height);
|
|
}
|
|
|
|
void qe_mouse_event(QEEvent *ev)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
EditState *e;
|
|
int mouse_x, mouse_y;
|
|
mouse_x = ev->button_event.x;
|
|
mouse_y = ev->button_event.y;
|
|
|
|
switch (ev->type) {
|
|
case QE_BUTTON_RELEASE_EVENT:
|
|
save_selection();
|
|
motion_type = MOTION_NONE;
|
|
break;
|
|
|
|
case QE_BUTTON_PRESS_EVENT:
|
|
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
|
/* test if mouse is inside the text area */
|
|
if (mouse_x >= e->xleft && mouse_x < e->xleft + e->width &&
|
|
mouse_y >= e->ytop && mouse_y < e->ytop + e->height) {
|
|
if (e->mode->mouse_goto) {
|
|
switch (ev->button_event.button) {
|
|
case QE_BUTTON_LEFT:
|
|
save_selection();
|
|
e->mode->mouse_goto(e, mouse_x - e->xleft,
|
|
mouse_y - e->ytop);
|
|
motion_type = MOTION_TEXT;
|
|
motion_x = 0; /* indicate first move */
|
|
motion_target = e;
|
|
break;
|
|
case QE_BUTTON_MIDDLE:
|
|
save_selection();
|
|
e->mode->mouse_goto(e, mouse_x - e->xleft,
|
|
mouse_y - e->ytop);
|
|
do_yank(e);
|
|
break;
|
|
case QE_WHEEL_UP:
|
|
wheel_scroll_up_down(e, -1);
|
|
break;
|
|
case QE_WHEEL_DOWN:
|
|
wheel_scroll_up_down(e, 1);
|
|
break;
|
|
}
|
|
edit_display(qs);
|
|
dpy_flush(qs->screen);
|
|
}
|
|
break;
|
|
}
|
|
/* test if inside modeline */
|
|
if ((e->flags & WF_MODELINE) &&
|
|
mouse_x >= e->xleft && mouse_x < e->xleft + e->width &&
|
|
mouse_y >= e->ytop + e->height &&
|
|
mouse_y < e->ytop + e->height + qs->mode_line_height) {
|
|
/* mark that motion can occur */
|
|
motion_type = MOTION_MODELINE;
|
|
motion_target = e;
|
|
motion_y = e->ytop + e->height;
|
|
break;
|
|
}
|
|
/* test if inside right window separator */
|
|
if ((e->flags & WF_RSEPARATOR) &&
|
|
mouse_x >= e->x2 - qs->separator_width && mouse_x < e->x2 &&
|
|
mouse_y >= e->ytop && mouse_y < e->ytop + e->height) {
|
|
/* mark that motion can occur */
|
|
motion_type = MOTION_RSEPARATOR;
|
|
motion_target = e;
|
|
motion_x = e->x2 - qs->separator_width;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case QE_MOTION_EVENT:
|
|
switch (motion_type) {
|
|
case MOTION_NONE:
|
|
default:
|
|
break;
|
|
case MOTION_TEXT:
|
|
{
|
|
e = motion_target;
|
|
if (!check_motion_target(e)) {
|
|
e->show_selection = 0;
|
|
motion_type = MOTION_NONE;
|
|
} else {
|
|
/* put a mark if first move */
|
|
if (!motion_x) {
|
|
/* test needed for list mode */
|
|
if (e->b)
|
|
e->b->mark = e->offset;
|
|
motion_x = 1;
|
|
}
|
|
/* highlight selection */
|
|
e->show_selection = 1;
|
|
if (mouse_x >= e->xleft && mouse_x < e->xleft + e->width &&
|
|
mouse_y >= e->ytop && mouse_y < e->ytop + e->height) {
|
|
/* if inside the buffer, then update cursor
|
|
position */
|
|
e->mode->mouse_goto(e, mouse_x - e->xleft,
|
|
mouse_y - e->ytop);
|
|
edit_display(qs);
|
|
dpy_flush(qs->screen);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MOTION_MODELINE:
|
|
if ((mouse_y / 8) != (motion_y / 8)) {
|
|
if (!check_motion_target(motion_target)) {
|
|
motion_type = MOTION_NONE;
|
|
} else {
|
|
motion_y = mouse_y;
|
|
window_resize(motion_target,
|
|
motion_target->x2 - motion_target->x1,
|
|
motion_y - motion_target->y1);
|
|
do_refresh(qs->first_window);
|
|
edit_display(qs);
|
|
dpy_flush(qs->screen);
|
|
}
|
|
}
|
|
break;
|
|
case MOTION_RSEPARATOR:
|
|
if ((mouse_x / 8) != (motion_x / 8)) {
|
|
if (!check_motion_target(motion_target)) {
|
|
motion_type = MOTION_NONE;
|
|
} else {
|
|
motion_x = mouse_x;
|
|
window_resize(motion_target,
|
|
motion_x - motion_target->x1,
|
|
motion_target->y2 - motion_target->y1);
|
|
do_refresh(qs->first_window);
|
|
edit_display(qs);
|
|
dpy_flush(qs->screen);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* put key in the unget buffer so that get_key() will return it */
|
|
void unget_key(int key)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
qs->ungot_key = key;
|
|
}
|
|
|
|
/* handle an event sent by the GUI */
|
|
void qe_handle_event(QEEvent *ev)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
|
|
switch (ev->type) {
|
|
case QE_KEY_EVENT:
|
|
qe_key_process(ev->key_event.key);
|
|
break;
|
|
case QE_EXPOSE_EVENT:
|
|
do_refresh(qs->first_window);
|
|
goto redraw;
|
|
case QE_UPDATE_EVENT:
|
|
redraw:
|
|
edit_display(qs);
|
|
dpy_flush(qs->screen);
|
|
break;
|
|
#ifndef CONFIG_TINY
|
|
case QE_BUTTON_PRESS_EVENT:
|
|
case QE_BUTTON_RELEASE_EVENT:
|
|
case QE_MOTION_EVENT:
|
|
qe_mouse_event(ev);
|
|
break;
|
|
case QE_SELECTION_CLEAR_EVENT:
|
|
save_selection();
|
|
goto redraw;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* text mode */
|
|
|
|
#if 0
|
|
int detect_binary(const u8 *buf, int size)
|
|
{
|
|
int i, c;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
c = buf[i];
|
|
if (c < 32 && (c != '\r' && c != '\n' && c != '\t' && c != '\e'))
|
|
return 1;
|
|
}
|
|
/* Treat very long sequences of identical characters as binary */
|
|
for (i = 0; i < size; i++) {
|
|
if (buf[i] != buf[0])
|
|
break;
|
|
}
|
|
if (i == size && size >= 2048 && buf[0] != '\n')
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int text_mode_probe(qe__unused__ ModeDef *mode,
|
|
qe__unused__ ModeProbeData *p)
|
|
{
|
|
if (mode->extensions) {
|
|
if (match_extension(p->filename, mode->extensions))
|
|
return 80;
|
|
else
|
|
return 1;
|
|
}
|
|
#if 0
|
|
/* text mode inappropriate for huge binary files */
|
|
if (detect_binary(p->buf, p->buf_size) && p->total_size > 1000000)
|
|
return 0;
|
|
else
|
|
#endif
|
|
return 20;
|
|
}
|
|
|
|
static int generic_mode_init(EditState *s)
|
|
{
|
|
s->offset = min(s->offset, s->b->total_size);
|
|
s->offset_top = min(s->offset_top, s->b->total_size);
|
|
eb_add_callback(s->b, eb_offset_callback, &s->offset, 0);
|
|
eb_add_callback(s->b, eb_offset_callback, &s->offset_top, 0);
|
|
set_colorize_func(s, NULL, NULL);
|
|
return 0;
|
|
}
|
|
|
|
/* Save window generic window data and mode */
|
|
static int generic_save_window_data(EditState *s)
|
|
{
|
|
EditBuffer *b = s->b;
|
|
|
|
if (!b->saved_data
|
|
&& !(b->saved_data = qe_mallocz_array(u8, SAVED_DATA_SIZE))) {
|
|
return -1;
|
|
}
|
|
memcpy(b->saved_data, s, SAVED_DATA_SIZE);
|
|
b->saved_mode = s->mode;
|
|
return 0;
|
|
}
|
|
|
|
static void generic_mode_close(EditState *s)
|
|
{
|
|
s->hex_mode = 0;
|
|
s->hex_nibble = 0;
|
|
s->unihex_mode = 0;
|
|
s->insert = 1;
|
|
s->wrap = WRAP_AUTO;
|
|
|
|
/* free all callbacks or associated buffer data */
|
|
set_colorize_func(s, NULL, NULL);
|
|
eb_free_callback(s->b, eb_offset_callback, &s->offset);
|
|
eb_free_callback(s->b, eb_offset_callback, &s->offset_top);
|
|
|
|
/* Free crcs should when switching display modes */
|
|
qe_free(&s->line_shadow);
|
|
s->shadow_nb_lines = 0;
|
|
}
|
|
|
|
ModeDef text_mode = {
|
|
.name = "text",
|
|
.mode_probe = text_mode_probe,
|
|
|
|
.display_line = text_display_line,
|
|
.backward_offset = text_backward_offset,
|
|
|
|
.move_up_down = text_move_up_down,
|
|
.move_left_right = text_move_left_right_visual,
|
|
.move_bol = text_move_bol,
|
|
.move_eol = text_move_eol,
|
|
.move_bof = text_move_bof,
|
|
.move_eof = text_move_eof,
|
|
.move_word_left_right = text_move_word_left_right,
|
|
.scroll_up_down = text_scroll_up_down,
|
|
.mouse_goto = text_mouse_goto,
|
|
.write_char = text_write_char,
|
|
};
|
|
|
|
/* find a resource file */
|
|
int find_resource_file(char *path, int path_size, const char *pattern)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
FindFileState *ffst;
|
|
int ret;
|
|
|
|
ffst = find_file_open(qs->res_path, pattern, FF_PATH);
|
|
if (!ffst)
|
|
return -1;
|
|
ret = find_file_next(ffst, path, path_size);
|
|
|
|
find_file_close(&ffst);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/******************************************************/
|
|
|
|
void do_load_config_file(EditState *e, const char *file)
|
|
{
|
|
QEmacsState *qs = e->qe_state;
|
|
FindFileState *ffst;
|
|
char filename[MAX_FILENAME_SIZE];
|
|
|
|
if (file && *file) {
|
|
parse_config_file(e, file);
|
|
do_refresh(e);
|
|
return;
|
|
}
|
|
|
|
ffst = find_file_open(qs->res_path, "config", FF_PATH | FF_NODIR);
|
|
if (!ffst)
|
|
return;
|
|
while (find_file_next(ffst, filename, sizeof(filename)) == 0) {
|
|
parse_config_file(e, filename);
|
|
}
|
|
find_file_close(&ffst);
|
|
}
|
|
|
|
/* Load .qerc files in all parent directories of filename */
|
|
/* CG: should keep a cache of failed attempts */
|
|
void do_load_qerc(EditState *e, const char *filename)
|
|
{
|
|
char buf[MAX_FILENAME_SIZE];
|
|
char *p = buf;
|
|
QEmacsState *qs = e->qe_state;
|
|
EditState *saved = qs->active_window;
|
|
|
|
for (;;) {
|
|
pstrcpy(buf, sizeof(buf), filename);
|
|
p = strchr(p, '/');
|
|
if (!p)
|
|
break;
|
|
p += 1;
|
|
pstrcpy(p, buf + sizeof(buf) - p, ".qerc");
|
|
qs->active_window = e;
|
|
parse_config_file(e, buf);
|
|
}
|
|
if (check_window(&saved))
|
|
qs->active_window = saved;
|
|
}
|
|
|
|
/******************************************************/
|
|
/* command line option handling */
|
|
static CmdLineOptionDef *first_cmd_options;
|
|
|
|
void qe_register_cmd_line_options(CmdLineOptionDef *table)
|
|
{
|
|
CmdLineOptionDef **pp, *p;
|
|
|
|
/* link command line options table at end of list */
|
|
for (pp = &first_cmd_options; *pp != NULL; pp = &p->u.next) {
|
|
p = *pp;
|
|
if (p == table)
|
|
return; /* already registered */
|
|
while (p->desc != NULL)
|
|
p++;
|
|
}
|
|
*pp = table;
|
|
}
|
|
|
|
/******************************************************/
|
|
|
|
const char str_version[] = "QEmacs version " QE_VERSION;
|
|
const char str_credits[] = "Copyright (c) 2000-2003 Fabrice Bellard\n"
|
|
"Copyright (c) 2000-2020 Charlie Gordon\n";
|
|
|
|
static void show_version(void)
|
|
{
|
|
printf("%s\n%s\n"
|
|
"QEmacs comes with ABSOLUTELY NO WARRANTY.\n"
|
|
"You may redistribute copies of QEmacs\n"
|
|
"under the terms of the GNU Lesser General Public License.\n",
|
|
str_version, str_credits);
|
|
exit(1);
|
|
}
|
|
|
|
static void show_usage(void)
|
|
{
|
|
CmdLineOptionDef *p;
|
|
|
|
printf("Usage: qe [OPTIONS] [filename ...]\n"
|
|
"\n"
|
|
"Options:\n"
|
|
"\n");
|
|
|
|
/* print all registered command line options */
|
|
for (p = first_cmd_options; p != NULL; p = p->u.next) {
|
|
while (p->desc != NULL) {
|
|
const char *s = p->desc;
|
|
bstr_t shortname = bstr_token(s, '|', &s);
|
|
bstr_t name = bstr_token(s, '|', &s);
|
|
bstr_t argname = bstr_token(s, '|', &s);
|
|
bstr_t help = bstr_make(s);
|
|
int pos = printf(" ");
|
|
|
|
if (shortname.len)
|
|
pos += printf(" -%.*s", shortname.len, shortname.s);
|
|
if (name.len)
|
|
pos += printf(" --%.*s", name.len, name.s);
|
|
if (argname.len)
|
|
pos += printf(" %.*s", argname.len, argname.s);
|
|
if (pos < 22)
|
|
printf("%*s", pos - 22, "");
|
|
printf(" %.*s\n", help.len, help.s);
|
|
p++;
|
|
}
|
|
}
|
|
printf("\n"
|
|
"Report bugs to bug@qemacs.org. First, please see the Bugs\n"
|
|
"section of the QEmacs manual or the file BUGS.\n");
|
|
exit(1);
|
|
}
|
|
|
|
static int parse_command_line(int argc, char **argv)
|
|
{
|
|
int _optind;
|
|
|
|
for (_optind = 1; _optind < argc;) {
|
|
const char *arg, *r, *_optarg;
|
|
CmdLineOptionDef *p;
|
|
bstr_t opt1;
|
|
bstr_t opt2;
|
|
|
|
r = arg = argv[_optind];
|
|
/* stop before first non option */
|
|
if (r[0] != '-')
|
|
break;
|
|
_optind++;
|
|
|
|
opt1.s = opt2.s = r + 1;
|
|
if (r[1] == '-') {
|
|
opt2.s++;
|
|
/* stop after `--' marker */
|
|
if (r[2] == '\0')
|
|
break;
|
|
}
|
|
/* parse optional argument specified with opt=arg or opt:arg syntax */
|
|
_optarg = NULL;
|
|
while (*r) {
|
|
if (*r == ':' || *r == '=') {
|
|
_optarg = r + 1;
|
|
break;
|
|
}
|
|
r++;
|
|
}
|
|
opt1.len = r - opt1.s;
|
|
opt2.len = r - opt2.s;
|
|
|
|
for (p = first_cmd_options; p != NULL; p = p->u.next) {
|
|
while (p->desc != NULL) {
|
|
const char *s = p->desc;
|
|
bstr_t shortname = bstr_token(s, '|', &s);
|
|
bstr_t name = bstr_token(s, '|', &s);
|
|
bstr_t argname = bstr_token(s, '|', &s);
|
|
if (bstr_equal(opt1, shortname) || bstr_equal(opt2, name)) {
|
|
if (argname.len && _optarg == NULL) {
|
|
if (_optind >= argc) {
|
|
put_status(NULL,
|
|
"cmdline argument %.*s expected for --%.*s",
|
|
argname.len, argname.s, name.len, name.s);
|
|
goto next_cmd;
|
|
}
|
|
_optarg = argv[_optind++];
|
|
}
|
|
switch (p->type) {
|
|
case CMD_LINE_TYPE_BOOL:
|
|
*p->u.int_ptr = qe_strtobool(_optarg, 1);
|
|
break;
|
|
case CMD_LINE_TYPE_INT:
|
|
*p->u.int_ptr = strtol(_optarg, NULL, 0);
|
|
break;
|
|
case CMD_LINE_TYPE_STRING:
|
|
*p->u.string_ptr = _optarg;
|
|
break;
|
|
case CMD_LINE_TYPE_FVOID:
|
|
p->u.func_noarg();
|
|
break;
|
|
case CMD_LINE_TYPE_FARG:
|
|
p->u.func_arg(_optarg);
|
|
break;
|
|
case CMD_LINE_TYPE_NONE:
|
|
case CMD_LINE_TYPE_NEXT:
|
|
break;
|
|
}
|
|
goto next_cmd;
|
|
}
|
|
p++;
|
|
}
|
|
}
|
|
put_status(NULL, "unknown cmdline option '%s'", arg);
|
|
next_cmd: ;
|
|
}
|
|
|
|
return _optind;
|
|
}
|
|
|
|
void do_add_resource_path(EditState *s, const char *path)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
pstrcat(qs->res_path, sizeof(qs->res_path), ":");
|
|
pstrcat(qs->res_path, sizeof(qs->res_path), path);
|
|
}
|
|
|
|
void set_user_option(const char *user)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
char path[MAX_FILENAME_SIZE];
|
|
const char *home_path;
|
|
|
|
qs->user_option = user;
|
|
|
|
/* compute resources path */
|
|
qs->res_path[0] = '\0';
|
|
|
|
/* put current directory first if qe invoked as ./qe */
|
|
if (stristart(qs->argv[0], "./qe", NULL)) {
|
|
if (!getcwd(path, sizeof(path)))
|
|
strcpy(path, ".");
|
|
pstrcat(qs->res_path, sizeof(qs->res_path), path);
|
|
pstrcat(qs->res_path, sizeof(qs->res_path), ":");
|
|
}
|
|
|
|
/* put user directory before standard list */
|
|
if (user) {
|
|
/* use ~USER/.qe instead of ~/.qe */
|
|
/* CG: should get user homedir */
|
|
#ifdef CONFIG_DARWIN
|
|
snprintf(path, sizeof(path), "/Users/%s", user);
|
|
#else
|
|
snprintf(path, sizeof(path), "/home/%s", user);
|
|
#endif
|
|
home_path = path;
|
|
} else {
|
|
home_path = getenv("HOME");
|
|
}
|
|
if (home_path) {
|
|
pstrcat(qs->res_path, sizeof(qs->res_path), home_path);
|
|
pstrcat(qs->res_path, sizeof(qs->res_path), "/.qe:");
|
|
}
|
|
|
|
pstrcat(qs->res_path, sizeof(qs->res_path),
|
|
CONFIG_QE_DATADIR ":"
|
|
CONFIG_QE_PREFIX "/share/qe" ":"
|
|
CONFIG_QE_PREFIX "/lib/qe" ":"
|
|
"/usr/share/qe" ":"
|
|
"/usr/lib/qe");
|
|
}
|
|
|
|
void set_tty_charset(const char *name)
|
|
{
|
|
qe_free(&qe_state.tty_charset);
|
|
qe_state.tty_charset = qe_strdup(name);
|
|
}
|
|
|
|
static CmdLineOptionDef cmd_options[] = {
|
|
CMD_LINE_FVOID("h", "help", show_usage,
|
|
"display this help message and exit"),
|
|
CMD_LINE_FVOID("?", "", show_usage, ""),
|
|
CMD_LINE_BOOL("q", "no-init-file", &no_init_file,
|
|
"do not load config files"),
|
|
CMD_LINE_BOOL("nc", "no-crc", &disable_crc,
|
|
"do not use crc based display cacheing"),
|
|
CMD_LINE_BOOL("1", "single-window", &single_window,
|
|
"keep a single window when loading multiple files"),
|
|
CMD_LINE_BOOL("nw", "no-windows", &force_tty,
|
|
"force tty terminal usage"),
|
|
CMD_LINE_FARG("c", "charset", "CHARSET", set_tty_charset,
|
|
"specify tty charset"),
|
|
CMD_LINE_BOOL("s", "use-session", &use_session_file,
|
|
"load and save session files"),
|
|
CMD_LINE_FARG("u", "user", "USER", set_user_option,
|
|
"load ~USER/.qe/config instead of your own"),
|
|
CMD_LINE_FVOID("V", "version", show_version,
|
|
"display version information and exit"),
|
|
#ifndef CONFIG_TINY
|
|
CMD_LINE_BOOL("", "free-all", &free_everything,
|
|
"free all structures upon exit"),
|
|
#endif
|
|
CMD_LINE_LINK()
|
|
};
|
|
|
|
/* default key bindings */
|
|
|
|
#include "qeconfig.h"
|
|
|
|
#if (defined(__GNUC__) || defined(__TINYC__)) && defined(CONFIG_INIT_CALLS)
|
|
|
|
static void init_all_modules(void)
|
|
{
|
|
int (*initcall)(void);
|
|
int (**ptr)(void);
|
|
|
|
ptr = (int (**)(void))(void *)&qe__initcall_first;
|
|
for (;;) {
|
|
#if defined(__BOUNDS_CHECKING_ON)
|
|
/* NOTE: if bound checking is on, a NULL is inserted between
|
|
each initialized 'void *' */
|
|
ptr++;
|
|
#endif
|
|
ptr++;
|
|
initcall = *ptr;
|
|
if (initcall == NULL)
|
|
break;
|
|
(*initcall)();
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void exit_all_modules(void)
|
|
{
|
|
/* CG: Should call in reverse order! */
|
|
int (*exitcall)(void);
|
|
int (**ptr)(void);
|
|
|
|
ptr = (int (**)(void))(void *)&qe__exitcall_first;
|
|
for (;;) {
|
|
#if defined(__BOUNDS_CHECKING_ON)
|
|
/* NOTE: if bound checking is on, a NULL is inserted between
|
|
each initialized 'void *' */
|
|
ptr++;
|
|
#endif
|
|
ptr++;
|
|
exitcall = *ptr;
|
|
if (exitcall == NULL)
|
|
break;
|
|
(*exitcall)();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#else
|
|
|
|
#ifndef SPLINT
|
|
#define qe_module_declare(fn) extern int module_ ## fn(void)
|
|
#include "modules.txt"
|
|
#undef qe_module_declare
|
|
#endif
|
|
|
|
static void init_all_modules(void)
|
|
{
|
|
#ifndef SPLINT
|
|
#define qe_module_declare(fn) module_ ## fn()
|
|
#include "modules.txt"
|
|
#undef qe_module_declare
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_DLL
|
|
|
|
static void load_all_modules(QEmacsState *qs)
|
|
{
|
|
QErrorContext ec;
|
|
FindFileState *ffst;
|
|
char filename[MAX_FILENAME_SIZE];
|
|
void *h;
|
|
void *sym;
|
|
int (*init_func)(void);
|
|
|
|
ec = qs->ec;
|
|
qs->ec.function = "load-all-modules";
|
|
|
|
ffst = find_file_open(qs->res_path, "*.so", FF_PATH | FF_NODIR);
|
|
if (!ffst)
|
|
goto done;
|
|
|
|
while (!find_file_next(ffst, filename, sizeof(filename))) {
|
|
h = dlopen(filename, RTLD_LAZY);
|
|
if (!h) {
|
|
char *error = dlerror();
|
|
put_status(NULL, "Could not open module '%s': %s",
|
|
filename, error);
|
|
continue;
|
|
}
|
|
#if 0
|
|
/* Writing: init_func = (int (*)(void))dlsym(handle, "xxx");
|
|
* would seem more natural, but the C99 standard leaves
|
|
* casting from "void *" to a function pointer undefined.
|
|
* The assignment used below is the POSIX.1-2003 (Technical
|
|
* Corrigendum 1) workaround; see the Rationale for the
|
|
* POSIX specification of dlsym().
|
|
*/
|
|
*(void **)&init_func = dlsym(h, "__qe_module_init");
|
|
//init_func = (int (*)(void))dlsym(h, "__qe_module_init");
|
|
#else
|
|
/* This kludge gets rid of compile and lint warnings */
|
|
sym = dlsym(h, "__qe_module_init");
|
|
memcpy(&init_func, &sym, sizeof(sym));
|
|
#endif
|
|
if (!init_func) {
|
|
dlclose(h);
|
|
put_status(NULL,
|
|
"Could not find qemacs initializer in module '%s'",
|
|
filename);
|
|
continue;
|
|
}
|
|
|
|
/* all is OK: we can init the module now */
|
|
(*init_func)();
|
|
}
|
|
find_file_close(&ffst);
|
|
|
|
done:
|
|
qs->ec = ec;
|
|
}
|
|
|
|
#endif
|
|
|
|
typedef struct QEArgs {
|
|
int argc;
|
|
char **argv;
|
|
} QEArgs;
|
|
|
|
static CompletionDef charset_completion = {
|
|
"charset", charset_complete
|
|
};
|
|
|
|
static CompletionDef color_completion = {
|
|
"color", color_complete
|
|
};
|
|
|
|
/* init function */
|
|
static void qe_init(void *opaque)
|
|
{
|
|
QEmacsState *qs = &qe_state;
|
|
QEArgs *args = opaque;
|
|
int argc = args->argc;
|
|
char **argv = args->argv;
|
|
EditState *s;
|
|
EditBuffer *b;
|
|
QEDisplay *dpy;
|
|
int i, _optind;
|
|
#if !defined(CONFIG_TINY) && !defined(CONFIG_WIN32)
|
|
int is_player, session_loaded = 0;
|
|
#endif
|
|
|
|
qs->ec.function = "qe-init";
|
|
qs->macro_key_index = -1; /* no macro executing */
|
|
qs->ungot_key = -1; /* no unget key */
|
|
|
|
qs->argc = argc;
|
|
qs->argv = argv;
|
|
|
|
qs->hilite_region = 1;
|
|
qs->default_tab_width = DEFAULT_TAB_WIDTH;
|
|
qs->default_fill_column = DEFAULT_FILL_COLUMN;
|
|
qs->mmap_threshold = MIN_MMAP_SIZE;
|
|
qs->max_load_size = MAX_LOAD_SIZE;
|
|
|
|
/* setup resource path */
|
|
set_user_option(NULL);
|
|
|
|
eb_init();
|
|
charset_init();
|
|
init_input_methods();
|
|
#ifdef CONFIG_ALL_KMAPS
|
|
{
|
|
char filename[MAX_FILENAME_SIZE];
|
|
if (find_resource_file(filename, sizeof(filename), "kmaps") >= 0)
|
|
load_input_methods(filename);
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_UNICODE_JOIN
|
|
{
|
|
char filename[MAX_FILENAME_SIZE];
|
|
if (find_resource_file(filename, sizeof(filename), "ligatures") >= 0)
|
|
load_ligatures(filename);
|
|
}
|
|
#endif
|
|
|
|
/* init basic modules */
|
|
qe_register_mode(&text_mode, MODEF_VIEW);
|
|
qe_register_cmd_table(basic_commands, NULL);
|
|
qe_mode_set_key(NULL, "ESC ESC ESC", "keyboard-quit");
|
|
qe_register_cmd_line_options(cmd_options);
|
|
|
|
qe_register_completion(&command_completion);
|
|
qe_register_completion(&charset_completion);
|
|
qe_register_completion(&mode_completion);
|
|
qe_register_completion(&style_completion);
|
|
qe_register_completion(&style_property_completion);
|
|
qe_register_completion(&file_completion);
|
|
#ifndef CONFIG_TINY
|
|
qe_register_completion(&dir_completion);
|
|
qe_register_completion(&resource_completion);
|
|
#endif
|
|
qe_register_completion(&buffer_completion);
|
|
qe_register_completion(&color_completion);
|
|
|
|
minibuffer_init();
|
|
popup_init();
|
|
|
|
/* init all external modules in link order */
|
|
init_all_modules();
|
|
|
|
#ifdef CONFIG_DLL
|
|
/* load all dynamic modules */
|
|
load_all_modules(qs);
|
|
#endif
|
|
|
|
#if !defined(CONFIG_TINY) && !defined(CONFIG_WIN32)
|
|
#if 0
|
|
/* see if invoked as player */
|
|
{
|
|
const char *p;
|
|
|
|
p = get_basename(argv[0]);
|
|
if (strequal(p, "ffplay"))
|
|
is_player = 1;
|
|
else
|
|
is_player = 0;
|
|
}
|
|
#else
|
|
/* Start in dired mode when invoked with no arguments */
|
|
is_player = 1;
|
|
#endif
|
|
#endif
|
|
|
|
/* init of the editor state */
|
|
qs->screen = &global_screen;
|
|
|
|
/* create first buffer */
|
|
b = eb_new("*scratch*", BF_SAVELOG | BF_UTF8);
|
|
|
|
/* will be positionned by do_refresh() */
|
|
s = edit_new(b, 0, 0, 0, 0, WF_MODELINE);
|
|
|
|
/* at this stage, no screen is defined. Initialize a
|
|
* null display driver to have a consistent state
|
|
* else many commands such as put_status would crash.
|
|
*/
|
|
screen_init(&global_screen, NULL, screen_width, screen_height);
|
|
|
|
/* handle options */
|
|
_optind = parse_command_line(argc, argv);
|
|
|
|
/* load config file unless command line option given */
|
|
if (!no_init_file) {
|
|
do_load_config_file(s, NULL);
|
|
s = qs->active_window;
|
|
}
|
|
|
|
qe_key_init(&key_ctx);
|
|
|
|
/* select the suitable display manager */
|
|
for (;;) {
|
|
dpy = probe_display();
|
|
if (!dpy) {
|
|
fprintf(stderr, "No suitable display found, exiting\n");
|
|
exit(1);
|
|
}
|
|
if (screen_init(&global_screen, dpy, screen_width, screen_height) < 0) {
|
|
/* Just disable the display and try another */
|
|
//fprintf(stderr, "Could not initialize display '%s', exiting\n",
|
|
// dpy->name);
|
|
dpy->dpy_probe = NULL;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
put_status(NULL, "%s display %dx%d",
|
|
dpy->name, qs->screen->width, qs->screen->height);
|
|
|
|
qe_event_init();
|
|
|
|
#ifndef CONFIG_TINY
|
|
if (use_session_file) {
|
|
session_loaded = !qe_load_session(s);
|
|
s = qs->active_window;
|
|
}
|
|
#endif
|
|
do_refresh(s);
|
|
|
|
/* load file(s) */
|
|
for (i = _optind; i < argc; ) {
|
|
int line_num = 0, col_num = 0;
|
|
char *arg, *p;
|
|
|
|
arg = argv[i++];
|
|
|
|
if (*arg == '+' && i < argc) {
|
|
if (strequal(arg, "+eval")) {
|
|
do_eval_expression(s, argv[i++], NO_ARG);
|
|
s = qs->active_window;
|
|
continue;
|
|
}
|
|
if (strequal(arg, "+load")) {
|
|
/* load script file */
|
|
parse_config_file(s, argv[i++]);
|
|
s = qs->active_window;
|
|
continue;
|
|
}
|
|
/* Handle +linenumber[,column] before file */
|
|
line_num = strtol(arg + 1, &p, 10);
|
|
if (*p == ',')
|
|
col_num = strtol(p + 1, NULL, 10);
|
|
arg = argv[i++];
|
|
}
|
|
/* load filename relative to qe current directory */
|
|
/* XXX: should split windows evenly */
|
|
qe_load_file(s, arg,
|
|
single_window ? LF_CWD_RELATIVE :
|
|
LF_CWD_RELATIVE | LF_SPLIT_WINDOW,
|
|
0);
|
|
s = qs->active_window;
|
|
if (line_num)
|
|
do_goto_line(s, line_num, col_num);
|
|
}
|
|
|
|
#if !defined(CONFIG_TINY) && !defined(CONFIG_WIN32)
|
|
if (is_player && !session_loaded && (_optind >= argc || S_ISDIR(s->b->st_mode))) {
|
|
/* if player, go to directory mode by default if no file selected */
|
|
do_dired(s, NO_ARG);
|
|
s = qs->active_window;
|
|
}
|
|
#endif
|
|
|
|
put_status(s, "QEmacs %s - Press F1 for help", QE_VERSION);
|
|
|
|
edit_display(qs);
|
|
dpy_flush(&global_screen);
|
|
|
|
b = eb_find("*errors*");
|
|
if (b != NULL) {
|
|
show_popup(s, b, "Errors");
|
|
edit_display(qs);
|
|
dpy_flush(&global_screen);
|
|
}
|
|
qs->ec.function = NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_WIN32
|
|
int main1(int argc, char **argv)
|
|
#else
|
|
int main(int argc, char **argv)
|
|
#endif
|
|
{
|
|
QEArgs args;
|
|
|
|
args.argc = argc;
|
|
args.argv = argv;
|
|
|
|
url_main_loop(qe_init, &args);
|
|
|
|
#ifdef CONFIG_ALL_KMAPS
|
|
unload_input_methods();
|
|
#endif
|
|
#ifdef CONFIG_UNICODE_JOIN
|
|
unload_ligatures();
|
|
#endif
|
|
#ifndef CONFIG_TINY
|
|
if (free_everything) {
|
|
/* free all structures for valgrind */
|
|
QEmacsState *qs = &qe_state;
|
|
|
|
while (qs->input_methods) {
|
|
InputMethod *p = qs->input_methods;
|
|
qs->input_methods = p->next;
|
|
if (p->data)
|
|
qe_free(&p);
|
|
}
|
|
while (qs->first_window) {
|
|
EditState *e = qs->first_window;
|
|
edit_close(&e);
|
|
}
|
|
while (qs->first_buffer) {
|
|
EditBuffer *b = qs->first_buffer;
|
|
eb_free(&b);
|
|
}
|
|
while (qs->first_cmd) {
|
|
CmdDef *d = qs->first_cmd;
|
|
CmdDef *d1 = d;
|
|
while (d1->name)
|
|
d1++;
|
|
qs->first_cmd = d1->action.next;
|
|
/* free xxx-mode commands and macros */
|
|
/* XXX: should use CmdDef flag */
|
|
if (d->name && !d[1].name && d[1].val) {
|
|
qe_free((char **)&d->name);
|
|
qe_free((char **)&d->spec);
|
|
qe_free(&d);
|
|
}
|
|
}
|
|
while (qs->first_key) {
|
|
KeyDef *p = qs->first_key;
|
|
qs->first_key = p->next;
|
|
qe_free(&p);
|
|
}
|
|
while (qs->first_mode) {
|
|
ModeDef *m = qs->first_mode;
|
|
qs->first_mode = m->next;
|
|
|
|
while (m->first_key) {
|
|
KeyDef *p = m->first_key;
|
|
m->first_key = p->next;
|
|
qe_free(&p);
|
|
}
|
|
}
|
|
css_free_colors();
|
|
free_font_cache(&global_screen);
|
|
qe_free(&qs->buffer_cache);
|
|
qs->buffer_cache_size = qs->buffer_cache_len = 0;
|
|
}
|
|
#endif
|
|
dpy_close(&global_screen);
|
|
|
|
return 0;
|
|
}
|