/* * Copyright (C) 2012-2023 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 "sciteco.h" #include "undo.h" #include "string-utils.h" /** * Get echoable (printable) version of a given string. * * This converts all control characters to printable * characters without tabs, line feeds, etc. * That's also why it can safely return a null-terminated string. * Useful for displaying Q-Register names and TECO code. * * @memberof teco_string_t */ gchar * teco_string_echo(const gchar *str, gsize len) { gchar *ret, *p; p = ret = g_malloc(len*2 + 1); for (guint i = 0; i < len; i++) { if (TECO_IS_CTL(str[i])) { *p++ = '^'; *p++ = TECO_CTL_ECHO(str[i]); } else { *p++ = str[i]; } } *p = '\0'; return ret; } /** @memberof teco_string_t */ void teco_string_get_coord(const gchar *str, guint pos, guint *line, guint *column) { *line = *column = 1; for (guint i = 0; i < pos; i++) { switch (str[i]) { case '\r': if (str[i+1] == '\n') i++; /* fall through */ case '\n': (*line)++; (*column) = 1; break; default: (*column)++; break; } } } /** @memberof teco_string_t */ gsize teco_string_diff(const teco_string_t *a, const gchar *b, gsize b_len) { gsize len = 0; while (len < a->len && len < b_len && a->data[len] == b[len]) len++; return len; } /** @memberof teco_string_t */ gsize teco_string_casediff(const teco_string_t *a, const gchar *b, gsize b_len) { gsize len = 0; while (len < a->len && len < b_len && g_ascii_tolower(a->data[len]) == g_ascii_tolower(b[len])) len++; return len; } /** @memberof teco_string_t */ gint teco_string_cmp(const teco_string_t *a, const gchar *b, gsize b_len) { for (guint i = 0; i < a->len; i++) { if (i == b_len) /* b is a prefix of a */ return 1; gint ret = (gint)a->data[i] - (gint)b[i]; if (ret != 0) /* a and b have a common prefix of length i */ return ret; } return a->len == b_len ? 0 : -1; } /** @memberof teco_string_t */ gint teco_string_casecmp(const teco_string_t *a, const gchar *b, gsize b_len) { for (guint i = 0; i < a->len; i++) { if (i == b_len) /* b is a prefix of a */ return 1; gint ret = (gint)g_ascii_tolower(a->data[i]) - (gint)g_ascii_tolower(b[i]); if (ret != 0) /* a and b have a common prefix of length i */ return ret; } return a->len == b_len ? 0 : -1; } /** * Find string after the last occurrence of any in a set of characters. * * @param str String to search through. * @param chars Null-terminated set of characters. * The null-byte itself is always considered part of the set. * @return A null-terminated suffix of str or NULL. * * @memberof teco_string_t */ const gchar * teco_string_last_occurrence(const teco_string_t *str, const gchar *chars) { teco_string_t ret = *str; if (!ret.len) return NULL; do { gint i = teco_string_rindex(&ret, *chars); if (i >= 0) { ret.data += i+1; ret.len -= i+1; } } while (*chars++); return ret.data; } TECO_DEFINE_UNDO_CALL(teco_string_truncate, teco_string_t *, gsize); TECO_DEFINE_UNDO_OBJECT(cstring, gchar *, g_strdup, g_free); static inline teco_string_t teco_string_copy(const teco_string_t str) { teco_string_t ret; teco_string_init(&ret, str.data, str.len); return ret; } #define DELETE(X) teco_string_clear(&(X)) TECO_DEFINE_UNDO_OBJECT(string, teco_string_t, teco_string_copy, DELETE); TECO_DEFINE_UNDO_OBJECT_OWN(string_own, teco_string_t, DELETE); #undef DELETE