/* * Copyright (C) 2012-2013 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "sciteco.h" #include "interface.h" #include "expressions.h" #include "parser.h" #include "qregisters.h" #include "ring.h" #include "goto.h" #include "undo.h" #include "symbols.h" #include "cmdline.h" static inline const gchar *process_edit_cmd(gchar key); static gchar *macro_echo(const gchar *macro); static gchar *filename_complete(const gchar *filename, gchar completed = ' '); static gchar *symbol_complete(SymbolList &list, const gchar *symbol, gchar completed = ' '); static const gchar *last_occurrence(const gchar *str, const gchar *chars = " \t\v\r\n\f<>,;@"); static inline gboolean filename_is_dir(const gchar *filename); gchar *cmdline = NULL; gint cmdline_pos = 0; static gchar *last_cmdline = NULL; bool quit_requested = false; namespace States { StateSaveCmdline save_cmdline; } void cmdline_keypress(gchar key) { gchar *old_cmdline = NULL; gint repl_pos = 0; const gchar *insert; gchar *echo; /* * Cleanup messages, popups, etc... */ interface.popup_clear(); interface.msg_clear(); /* * Process immediate editing commands */ insert = process_edit_cmd(key); /* * Parse/execute characters, one at a time so * undo tokens get emitted for the corresponding characters. */ cmdline_pos = cmdline ? strlen(cmdline)+1 : 1; String::append(cmdline, insert); while (cmdline[cmdline_pos-1]) { try { Execute::step(cmdline, cmdline_pos); } catch (ReplaceCmdline &r) { undo.pop(r.pos); old_cmdline = cmdline; cmdline = r.new_cmdline; cmdline_pos = repl_pos = r.pos; macro_pc = r.pos-1; continue; } catch (...) { if (old_cmdline) { undo.pop(repl_pos); g_free(cmdline); cmdline = old_cmdline; cmdline[strlen(cmdline)-1] = '\0'; old_cmdline = NULL; cmdline_pos = repl_pos; macro_pc = repl_pos-1; continue; } /* * Undo tokens may have been emitted * (or had to be) before the exception * is thrown. They must be executed so * as if the character had never been * inserted. */ undo.pop(cmdline_pos); cmdline[cmdline_pos-1] = '\0'; /* program counter could be messed up */ macro_pc = cmdline_pos - 1; break; } cmdline_pos++; } g_free(old_cmdline); /* * Echo command line */ echo = macro_echo(cmdline); interface.cmdline_update(echo); g_free(echo); } static inline const gchar * process_edit_cmd(gchar key) { static gchar insert[255]; gint cmdline_len = cmdline ? strlen(cmdline) : 0; insert[0] = key; insert[1] = '\0'; switch (key) { case '\b': if (cmdline_len) { undo.pop(cmdline_len); cmdline[cmdline_len - 1] = '\0'; macro_pc--; } *insert = '\0'; break; case CTL_KEY('W'): if (States::is_string()) { gchar wchars[interface.ssm(SCI_GETWORDCHARS)]; interface.ssm(SCI_GETWORDCHARS, 0, (sptr_t)wchars); /* rubout non-word chars */ while (strings[0] && strlen(strings[0]) > 0 && !strchr(wchars, cmdline[macro_pc-1])) undo.pop(macro_pc--); /* rubout word chars */ while (strings[0] && strlen(strings[0]) > 0 && strchr(wchars, cmdline[macro_pc-1])) undo.pop(macro_pc--); } else if (cmdline_len) { do undo.pop(macro_pc--); while (States::current != &States::start); } cmdline[macro_pc] = '\0'; *insert = '\0'; break; case CTL_KEY('U'): if (States::is_string()) { while (strings[0] && strlen(strings[0]) > 0) undo.pop(macro_pc--); cmdline[macro_pc] = '\0'; *insert = '\0'; } break; case CTL_KEY('T'): if (States::is_string()) { const gchar *filename = last_occurrence(strings[0]); gchar *new_chars = filename_complete(filename); *insert = '\0'; if (new_chars) g_stpcpy(insert, new_chars); g_free(new_chars); } break; case '\t': if (States::is_file()) { gchar complete = escape_char == '{' ? ' ' : escape_char; gchar *new_chars = filename_complete(strings[0], complete); *insert = '\0'; if (new_chars) g_stpcpy(insert, new_chars); g_free(new_chars); } else if (States::current == &States::scintilla_symbols) { const gchar *symbol = last_occurrence(strings[0], ","); SymbolList &list = symbol == strings[0] ? Symbols::scintilla : Symbols::scilexer; gchar *new_chars = symbol_complete(list, symbol, ','); *insert = '\0'; if (new_chars) g_stpcpy(insert, new_chars); g_free(new_chars); } break; case '\x1B': if (States::current == &States::start && cmdline && cmdline[cmdline_len - 1] == '\x1B') { *insert = '\0'; if (Goto::skip_label) { interface.msg(Interface::MSG_ERROR, "Label \"%s\" not found", Goto::skip_label); break; } if (quit_requested) { /* FIXME */ exit(EXIT_SUCCESS); } undo.clear(); interface.ssm(SCI_EMPTYUNDOBUFFER); Goto::table->clear(); expressions.clear(); g_free(last_cmdline); last_cmdline = cmdline; cmdline = NULL; macro_pc = 0; } break; #ifdef SIGTSTP case CTL_KEY('Z'): /* * does not raise signal if handling of * special characters temporarily disabled in terminal * (Curses), or command-line is detached from * terminal (GTK+) */ raise(SIGTSTP); *insert = '\0'; break; #endif } return insert; } void cmdline_fnmacro(const gchar *name) { gchar macro_name[1 + strlen(name) + 1]; QRegister *reg; macro_name[0] = CTL_KEY('F'); g_strlcpy(macro_name + 1, name, sizeof(macro_name) - 1); reg = QRegisters::globals[macro_name]; if (reg) { gchar *macro = reg->get_string(); cmdline_keypress(macro); g_free(macro); } } const gchar * get_eol(void) { switch (interface.ssm(SCI_GETEOLMODE)) { case SC_EOL_CR: return "\r"; case SC_EOL_CRLF: return "\r\n"; case SC_EOL_LF: default: return "\n"; } } static gchar * macro_echo(const gchar *macro) { gchar *result, *rp; if (!macro) return g_strdup(""); rp = result = (gchar *)g_malloc(strlen(macro)*5 + 1); for (const gchar *p = macro; *p; p++) { switch (*p) { case '\x1B': *rp++ = '$'; break; case '\r': rp = g_stpcpy(rp, ""); break; case '\n': rp = g_stpcpy(rp, ""); break; case '\t': rp = g_stpcpy(rp, ""); break; default: if (IS_CTL(*p)) { *rp++ = '^'; *rp++ = CTL_ECHO(*p); } else { *rp++ = *p; } } } *rp = '\0'; return result; } static gchar * filename_complete(const gchar *filename, gchar completed) { gchar *dirname, *basename; GDir *dir; GList *files = NULL, *matching; GCompletion *completion; gchar *new_prefix; gchar *insert = NULL; if (!filename) filename = ""; if (is_glob_pattern(filename)) return NULL; dirname = g_path_get_dirname(filename); dir = g_dir_open(dirname, 0, NULL); if (!dir) { g_free(dirname); return NULL; } if (*dirname != *filename) *dirname = '\0'; while ((basename = (gchar *)g_dir_read_name(dir))) { gchar *filename = g_build_filename(dirname, basename, NIL); if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { gchar *new_filename; new_filename = g_strconcat(filename, G_DIR_SEPARATOR_S, NIL); g_free(filename); filename = new_filename; } files = g_list_prepend(files, filename); } g_free(dirname); g_dir_close(dir); completion = g_completion_new(NULL); g_completion_add_items(completion, files); matching = g_completion_complete(completion, filename, &new_prefix); if (new_prefix && strlen(new_prefix) > strlen(filename)) insert = g_strdup(new_prefix + strlen(filename)); g_free(new_prefix); if (!insert && g_list_length(matching) > 1) { matching = g_list_sort(matching, (GCompareFunc)g_strcmp0); for (GList *file = g_list_first(matching); file != NULL; file = g_list_next(file)) { Interface::PopupEntryType type; bool in_buffer = false; if (filename_is_dir((gchar *)file->data)) { type = Interface::POPUP_DIRECTORY; } else { type = Interface::POPUP_FILE; /* FIXME: inefficient */ in_buffer = ring.find((gchar *)file->data); } interface.popup_add(type, (gchar *)file->data, in_buffer); } interface.popup_show(); } else if (g_list_length(matching) == 1 && !filename_is_dir((gchar *)g_list_first(matching)->data)) { String::append(insert, completed); } g_completion_free(completion); for (GList *file = g_list_first(files); file; file = g_list_next(file)) g_free(file->data); g_list_free(files); return insert; } static gchar * symbol_complete(SymbolList &list, const gchar *symbol, gchar completed) { GList *glist; GList *matching; GCompletion *completion; gchar *new_prefix; gchar *insert = NULL; if (!symbol) symbol = ""; glist = list.get_glist(); if (!glist) return NULL; completion = g_completion_new(NULL); g_completion_add_items(completion, glist); matching = g_completion_complete(completion, symbol, &new_prefix); if (new_prefix && strlen(new_prefix) > strlen(symbol)) insert = g_strdup(new_prefix + strlen(symbol)); g_free(new_prefix); if (!insert && g_list_length(matching) > 1) { for (GList *entry = g_list_first(matching); entry != NULL; entry = g_list_next(entry)) { interface.popup_add(Interface::POPUP_PLAIN, (gchar *)entry->data); } interface.popup_show(); } else if (g_list_length(matching) == 1) { String::append(insert, completed); } g_completion_free(completion); return insert; } /* * Command states */ /*$ * *q -- Save last command line * * Only at the very beginning of a command-line, this command * may be used to save the last command line as a string in * Q-Register . */ State * StateSaveCmdline::got_register(QRegister ®) throw (Error) { BEGIN_EXEC(&States::start); reg.undo_set_string(); reg.set_string(last_cmdline); return &States::start; } /* * Auxiliary functions */ static const gchar * last_occurrence(const gchar *str, const gchar *chars) { if (!str) return NULL; while (*chars) { const gchar *p = strrchr(str, *chars++); if (p) str = p+1; } return str; } static inline gboolean filename_is_dir(const gchar *filename) { return g_str_has_suffix(filename, G_DIR_SEPARATOR_S); }