/*
* Copyright (C) 2012-2024 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 .
*/
#pragma once
#include
#include
#include "sciteco.h"
#include "undo.h"
/**
* Upper-case SciTECO command character.
*
* There are implementations in glib (g_ascii_toupper() and g_unichar_toupper()) and libc,
* but this implementation is sufficient for all letters used by SciTECO commands.
*/
static inline gunichar
teco_ascii_toupper(gunichar chr)
{
return chr >= 'a' && chr <= 'z' ? chr & ~0x20 : chr;
}
static inline gchar *
teco_strv_remove(gchar **strv, guint i)
{
gchar *ret = strv[i];
do
strv[i] = strv[i+1];
while (strv[++i]);
return ret;
}
/**
* An 8-bit clean null-terminated string.
*
* This is similar to GString, but the container does not need to be allocated
* and the allocation length is not stored.
* Just like GString, teco_string_t are always null-terminated but at the
* same time 8-bit clean (can contain null-characters).
* It may or may not contain UTF-8 byte sequences.
*
* The API is designed such that teco_string_t operations operate on plain
* (null-terminated) C strings, a single character or character array as well as
* on other teco_string_t.
* Input strings will thus usually be specified using a const gchar * and gsize
* and are not necessarily null-terminated.
* A target teco_string_t::data is always null-terminated and thus safe to pass
* to functions expecting traditional null-terminated C strings if you can
* guarantee that it contains no null-character other than the trailing one.
*
* @warning For consistency with C idioms the underlying character type is
* `char`, which might be signed!
* Accessing individual characters may yield signed integers and that sign
* might be preserved when upcasting to a larger signed integer.
* In this case you should always cast to `guchar` first.
*/
typedef struct {
/**
* g_malloc() or g_string_chunk_insert()-allocated null-terminated string.
* The pointer is guaranteed to be non-NULL after initialization.
*/
gchar *data;
/** Length of `data` without the trailing null-byte in bytes. */
gsize len;
} teco_string_t;
/** @memberof teco_string_t */
static inline void
teco_string_init(teco_string_t *target, const gchar *str, gsize len)
{
target->data = g_malloc(len + 1);
if (str)
memcpy(target->data, str, len);
target->len = len;
target->data[target->len] = '\0';
}
/**
* Allocate a teco_string_t using GStringChunk.
*
* Such strings must not be freed/cleared individually and it is NOT allowed
* to call teco_string_append() and teco_string_truncate() on them.
* On the other hand, they are stored faster and more memory efficient.
*
* @memberof teco_string_t
*/
static inline void
teco_string_init_chunk(teco_string_t *target, const gchar *str, gssize len, GStringChunk *chunk)
{
target->data = g_string_chunk_insert_len(chunk, str, len);
target->len = len;
}
/**
* @note Rounding up the length turned out to bring no benefits,
* at least with glibc's malloc().
*
* @memberof teco_string_t
*/
static inline void
teco_string_append(teco_string_t *target, const gchar *str, gsize len)
{
target->data = g_realloc(target->data, target->len + len + 1);
if (str)
memcpy(target->data + target->len, str, len);
target->len += len;
target->data[target->len] = '\0';
}
/** @memberof teco_string_t */
static inline void
teco_string_append_c(teco_string_t *str, gchar chr)
{
teco_string_append(str, &chr, sizeof(chr));
}
/** @memberof teco_string_t */
static inline void
teco_string_append_wc(teco_string_t *target, gunichar chr)
{
/* 4 bytes should be enough, but we better follow the documentation */
target->data = g_realloc(target->data, target->len + 6 + 1);
target->len += g_unichar_to_utf8(chr, target->data+target->len);
target->data[target->len] = '\0';
}
/**
* @fixme Should this also realloc str->data?
*
* @memberof teco_string_t
*/
static inline void
teco_string_truncate(teco_string_t *str, gsize len)
{
g_assert(len <= str->len);
if (len) {
str->data[len] = '\0';
} else {
g_free(str->data);
str->data = NULL;
}
str->len = len;
}
/** @memberof teco_string_t */
void undo__teco_string_truncate(teco_string_t *, gsize);
gchar *teco_string_echo(const gchar *str, gsize len);
void teco_string_get_coord(const gchar *str, guint pos, guint *line, guint *column);
typedef gsize (*teco_string_diff_t)(const teco_string_t *a, const gchar *b, gsize b_len);
gsize teco_string_diff(const teco_string_t *a, const gchar *b, gsize b_len);
gsize teco_string_casediff(const teco_string_t *a, const gchar *b, gsize b_len);
typedef gint (*teco_string_cmp_t)(const teco_string_t *a, const gchar *b, gsize b_len);
gint teco_string_cmp(const teco_string_t *a, const gchar *b, gsize b_len);
gint teco_string_casecmp(const teco_string_t *a, const gchar *b, gsize b_len);
/** @memberof teco_string_t */
static inline gboolean
teco_string_contains(const teco_string_t *str, gchar chr)
{
return str->data && memchr(str->data, chr, str->len);
}
/**
* Get index of character in string.
*
* @return Index of character in string. 0 refers to the first character.
* In case of search failure, a negative value is returned.
*
* @memberof teco_string_t
*/
static inline gint
teco_string_rindex(const teco_string_t *str, gchar chr)
{
gint i;
for (i = str->len-1; i >= 0 && str->data[i] != chr; i--);
return i;
}
const gchar *teco_string_last_occurrence(const teco_string_t *str, const gchar *chars);
/** @memberof teco_string_t */
static inline void
teco_string_clear(teco_string_t *str)
{
g_free(str->data);
}
TECO_DECLARE_UNDO_OBJECT(cstring, gchar *);
#define teco_undo_cstring(VAR) \
(*teco_undo_object_cstring_push(&(VAR)))
TECO_DECLARE_UNDO_OBJECT(string, teco_string_t);
#define teco_undo_string(VAR) \
(*teco_undo_object_string_push(&(VAR)))
TECO_DECLARE_UNDO_OBJECT(string_own, teco_string_t);
#define teco_undo_string_own(VAR) \
(*teco_undo_object_string_own_push(&(VAR)))
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(teco_string_t, teco_string_clear);