aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/symbols.c
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2021-06-02 19:18:10 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2021-06-02 19:18:10 +0200
commitffbc962c0a241f7d70b48162f92f2023fe6c043f (patch)
treed97dd89b680162b935cb8aae223405ffd0d9e16a /src/symbols.c
parent3939a60c73694d5b76d60f61354e0aeb60b270c6 (diff)
downloadsciteco-ffbc962c0a241f7d70b48162f92f2023fe6c043f.tar.gz
renamed scintilla.[ch] to symbols.[ch]: fixes builds on case-insensitive file systems
* There is a "Scintilla.h" as well. * should fix macOS and builds on native Windows hosts * It wasn't practical to refer to the Scintilla includes using paths since the Scintilla location is configurable (--with-scintilla). So we'd have to write something like #include <include/Scintilla.h>. For Scinterm we cannot avoid collisions neither as its path is also configurable (--with-scinterm). Effectively, we must prevent name clashes across SciTECO and all of Scintilla and Scinterm.
Diffstat (limited to 'src/symbols.c')
-rw-r--r--src/symbols.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/src/symbols.c b/src/symbols.c
new file mode 100644
index 0000000..003d745
--- /dev/null
+++ b/src/symbols.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2012-2021 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "sciteco.h"
+#include "string-utils.h"
+#include "error.h"
+#include "parser.h"
+#include "core-commands.h"
+#include "undo.h"
+#include "expressions.h"
+#include "interface.h"
+#include "symbols.h"
+
+teco_symbol_list_t teco_symbol_list_scintilla = {NULL, 0};
+teco_symbol_list_t teco_symbol_list_scilexer = {NULL, 0};
+
+/*
+ * FIXME: Could be static.
+ */
+TECO_DEFINE_UNDO_OBJECT_OWN(scintilla_message, teco_machine_scintilla_t, /* don't delete */);
+
+/** @memberof teco_symbol_list_t */
+void
+teco_symbol_list_init(teco_symbol_list_t *ctx, const teco_symbol_entry_t *entries, gint size,
+ gboolean case_sensitive)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->entries = entries;
+ ctx->size = size;
+ ctx->cmp_fnc = case_sensitive ? strncmp : g_ascii_strncasecmp;
+}
+
+/**
+ * @note Since symbol lists are presorted constant arrays we can do a simple
+ * binary search.
+ * This does not use bsearch() since we'd have to prepend `prefix` in front of
+ * the name.
+ *
+ * @memberof teco_symbol_list_t
+ */
+gint
+teco_symbol_list_lookup(teco_symbol_list_t *ctx, const gchar *name, const gchar *prefix)
+{
+ gsize name_len = strlen(name);
+
+ gsize prefix_skip = strlen(prefix);
+ if (!ctx->cmp_fnc(name, prefix, prefix_skip))
+ prefix_skip = 0;
+
+ gint left = 0;
+ gint right = ctx->size - 1;
+
+ while (left <= right) {
+ gint cur = left + (right-left)/2;
+ gint cmp = ctx->cmp_fnc(ctx->entries[cur].name + prefix_skip,
+ name, name_len + 1);
+
+ if (!cmp)
+ return ctx->entries[cur].value;
+
+ if (cmp > 0)
+ right = cur-1;
+ else /* cmp < 0 */
+ left = cur+1;
+ }
+
+ return -1;
+}
+
+/**
+ * Auto-complete a Scintilla symbol.
+ *
+ * @param ctx The symbol list.
+ * @param symbol The symbol to auto-complete or NULL.
+ * @param insert String to initialize with the completion.
+ * @return TRUE in case of an unambiguous completion.
+ *
+ * @memberof teco_symbol_list_t
+ */
+gboolean
+teco_symbol_list_auto_complete(teco_symbol_list_t *ctx, const gchar *symbol, teco_string_t *insert)
+{
+ memset(insert, 0, sizeof(*insert));
+
+ if (!symbol)
+ symbol = "";
+ gsize symbol_len = strlen(symbol);
+
+ if (G_UNLIKELY(!ctx->list))
+ for (gint i = ctx->size; i; i--)
+ ctx->list = g_list_prepend(ctx->list, (gchar *)ctx->entries[i-1].name);
+
+ /* NOTE: element data must not be freed */
+ g_autoptr(GList) glist = g_list_copy(ctx->list);
+ guint glist_len = 0;
+
+ gsize prefix_len = 0;
+
+ for (GList *entry = g_list_first(glist), *next = g_list_next(entry);
+ entry != NULL;
+ entry = next, next = entry ? g_list_next(entry) : NULL) {
+ if (g_ascii_strncasecmp(entry->data, symbol, symbol_len) != 0) {
+ glist = g_list_delete_link(glist, entry);
+ continue;
+ }
+
+ teco_string_t glist_str;
+ glist_str.data = (gchar *)glist->data + symbol_len;
+ glist_str.len = strlen(glist_str.data);
+
+ gsize len = teco_string_casediff(&glist_str, (gchar *)entry->data + symbol_len,
+ strlen(entry->data) - symbol_len);
+ if (!prefix_len || len < prefix_len)
+ prefix_len = len;
+
+ glist_len++;
+ }
+
+ if (prefix_len > 0) {
+ teco_string_init(insert, (gchar *)glist->data + symbol_len, prefix_len);
+ } else if (glist_len > 1) {
+ for (GList *entry = g_list_first(glist);
+ entry != NULL;
+ entry = g_list_next(entry)) {
+ teco_interface_popup_add(TECO_POPUP_PLAIN, entry->data,
+ strlen(entry->data), FALSE);
+ }
+
+ teco_interface_popup_show();
+ }
+
+ return glist_len == 1;
+}
+
+/*
+ * Command states
+ */
+
+/*
+ * FIXME: This state could be static.
+ */
+TECO_DECLARE_STATE(teco_state_scintilla_lparam);
+
+static gboolean
+teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, const teco_string_t *str, GError **error)
+{
+ if (teco_string_contains(str, '\0')) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Scintilla symbol names must not contain null-byte");
+ return FALSE;
+ }
+
+ g_auto(GStrv) symbols = g_strsplit(str->data, ",", -1);
+
+ if (!symbols[0])
+ return TRUE;
+ if (*symbols[0]) {
+ gint v = teco_symbol_list_lookup(&teco_symbol_list_scintilla, symbols[0], "SCI_");
+ if (v < 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Unknown Scintilla message symbol \"%s\"",
+ symbols[0]);
+ return FALSE;
+ }
+ scintilla->iMessage = v;
+ }
+
+ if (!symbols[1])
+ return TRUE;
+ if (*symbols[1]) {
+ gint v = teco_symbol_list_lookup(&teco_symbol_list_scilexer, symbols[1], "");
+ if (v < 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Unknown Scintilla Lexer symbol \"%s\"",
+ symbols[1]);
+ return FALSE;
+ }
+ scintilla->wParam = v;
+ }
+
+ if (!symbols[2])
+ return TRUE;
+ if (*symbols[2]) {
+ gint v = teco_symbol_list_lookup(&teco_symbol_list_scilexer, symbols[2], "");
+ if (v < 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Unknown Scintilla Lexer symbol \"%s\"",
+ symbols[2]);
+ return FALSE;
+ }
+ scintilla->lParam = v;
+ }
+
+ return TRUE;
+}
+
+static teco_state_t *
+teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_scintilla_lparam;
+
+ /*
+ * NOTE: This is more memory efficient than pushing the individual
+ * members of teco_machine_scintilla_t and we won't need to define
+ * undo methods for the Scintilla types.
+ */
+ if (ctx->parent.must_undo)
+ teco_undo_object_scintilla_message_push(&ctx->scintilla);
+ memset(&ctx->scintilla, 0, sizeof(ctx->scintilla));
+
+ if ((str->len > 0 && !teco_scintilla_parse_symbols(&ctx->scintilla, str, error)) ||
+ !teco_expressions_eval(FALSE, error))
+ return NULL;
+
+ teco_int_t value;
+
+ if (!ctx->scintilla.iMessage) {
+ if (!teco_expressions_args()) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "<ES> command requires at least a message code");
+ return NULL;
+ }
+
+ if (!teco_expressions_pop_num_calc(&value, 0, error))
+ return NULL;
+ ctx->scintilla.iMessage = value;
+ }
+ if (!ctx->scintilla.wParam) {
+ if (!teco_expressions_pop_num_calc(&value, 0, error))
+ return NULL;
+ ctx->scintilla.wParam = value;
+ }
+
+ return &teco_state_scintilla_lparam;
+}
+
+/* in cmdline.c */
+gboolean teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gchar key, GError **error);
+
+/*$ ES scintilla message
+ * -- Send Scintilla message
+ * [lParam[,wParam]]ESmessage[,wParam[,lParam]]$[lParam]$ -> result
+ *
+ * Send Scintilla message with code specified by symbolic
+ * name <message>, <wParam> and <lParam>.
+ * <wParam> may be symbolic when specified as part of the
+ * first string argument.
+ * If not it is popped from the stack.
+ * <lParam> may be specified as a constant string whose
+ * pointer is passed to Scintilla if specified as the second
+ * string argument.
+ * If the second string argument is empty, <lParam> is popped
+ * from the stack instead.
+ * Parameters popped from the stack may be omitted, in which
+ * case 0 is implied.
+ * The message's return value is pushed onto the stack.
+ *
+ * All messages defined by Scintilla (as C macros) can be
+ * used by passing their name as a string to ES
+ * (e.g. ESSCI_LINESONSCREEN...).
+ * The \(lqSCI_\(rq prefix may be omitted and message symbols
+ * are case-insensitive.
+ * Only the Scintilla lexer symbols (SCLEX_..., SCE_...)
+ * may be used symbolically with the ES command as <wParam>,
+ * other values must be passed as integers on the stack.
+ * In interactive mode, symbols may be auto-completed by
+ * pressing Tab.
+ * String-building characters are by default interpreted
+ * in the string arguments.
+ *
+ * .BR Warning :
+ * Almost all Scintilla messages may be dispatched using
+ * this command.
+ * \*(ST does not keep track of the editor state changes
+ * performed by these commands and cannot undo them.
+ * You should never use it to change the editor state
+ * (position changes, deletions, etc.) or otherwise
+ * rub out will result in an inconsistent editor state.
+ * There are however exceptions:
+ * - In the editor profile and batch mode in general,
+ * the ES command may be used freely.
+ * - In the ED hook macro (register \(lqED\(rq),
+ * when a file is added to the ring, most destructive
+ * operations can be performed since rubbing out the
+ * EB command responsible for the hook execution also
+ * removes the buffer from the ring again.
+ * - As part of function key macros that immediately
+ * terminate the command line.
+ */
+TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_symbols,
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_scintilla_symbols_process_edit_cmd,
+ .expectstring.last = FALSE
+);
+
+static teco_state_t *
+teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ if (ctx->mode > TECO_MODE_NORMAL)
+ return &teco_state_start;
+
+ if (teco_string_contains(str, '\0')) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Scintilla lParam string must not contain null-byte.");
+ return NULL;
+ }
+
+ if (!ctx->scintilla.lParam) {
+ if (str->len > 0) {
+ ctx->scintilla.lParam = (sptr_t)str->data;
+ } else {
+ teco_int_t v;
+ if (!teco_expressions_pop_num_calc(&v, 0, error))
+ return NULL;
+ ctx->scintilla.lParam = v;
+ }
+ }
+
+ teco_expressions_push(teco_interface_ssm(ctx->scintilla.iMessage,
+ ctx->scintilla.wParam,
+ ctx->scintilla.lParam));
+
+ return &teco_state_start;
+}
+
+TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_lparam);