aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/rb3str.c
blob: 889c52e3d8a5ff6c98ce3307cb38d3f909a16046 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
 * 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 <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib.h>

/*
 * NOTE: Must be included only once.
 */
//#include <rb3ptr.h>

#include "sciteco.h"
#include "interface.h"
#include "string-utils.h"
#include "rb3str.h"

static gint
teco_rb3str_cmp(const teco_rb3str_head_t *head, const teco_string_t *data)
{
	return teco_string_cmp(&head->key, data->data, data->len);
}

static gint
teco_rb3str_casecmp(const teco_rb3str_head_t *head, const teco_string_t *data)
{
	return teco_string_casecmp(&head->key, data->data, data->len);
}

/** @memberof teco_rb3str_tree_t */
teco_rb3str_head_t *
teco_rb3str_insert(teco_rb3str_tree_t *tree, gboolean case_sensitive, teco_rb3str_head_t *head)
{
	rb3_cmp *cmp = case_sensitive ? (rb3_cmp *)teco_rb3str_cmp : (rb3_cmp *)teco_rb3str_casecmp;
	return (teco_rb3str_head_t *)rb3_insert(tree, &head->head, cmp, &head->key);
}

/** @memberof teco_rb3str_tree_t */
teco_rb3str_head_t *
teco_rb3str_find(teco_rb3str_tree_t *tree, gboolean case_sensitive, const gchar *str, gsize len)
{
	rb3_cmp *cmp = case_sensitive ? (rb3_cmp *)teco_rb3str_cmp : (rb3_cmp *)teco_rb3str_casecmp;
	teco_string_t data = {(gchar *)str, len};
	return (teco_rb3str_head_t *)rb3_find(tree, cmp, &data);
}

/** @memberof teco_rb3str_tree_t */
teco_rb3str_head_t *
teco_rb3str_nfind(teco_rb3str_tree_t *tree, gboolean case_sensitive, const gchar *str, gsize len)
{
	rb3_cmp *cmp = case_sensitive ? (rb3_cmp *)teco_rb3str_cmp : (rb3_cmp *)teco_rb3str_casecmp;
	teco_string_t data = {(gchar *)str, len};

	/*
	 * This is based on rb3_INLINE_find() in rb3ptr.h.
	 * Alternatively, we might adapt/wrap teco_rb3str_cmp() in order to store
	 * the last element > data.
	 */
	struct rb3_head *parent = rb3_get_base(tree);
	struct rb3_head *res = NULL;
	int dir = RB3_LEFT;
        while (rb3_has_child(parent, dir)) {
                parent = rb3_get_child(parent, dir);
                int r = cmp(parent, &data);
                if (r == 0)
                        return (teco_rb3str_head_t *)parent;
                dir = (r < 0) ? RB3_RIGHT : RB3_LEFT;
		if (dir == RB3_LEFT)
			res = parent;
        }

	return (teco_rb3str_head_t *)res;
}

/**
 * Auto-complete string given the entries of a RB tree.
 *
 * @param tree The RB tree (root).
 * @param case_sensitive Whether to match case-sensitive.
 * @param str String to complete (not necessarily null-terminated).
 * @param str_len Length of characters in `str`.
 * @param restrict_len Limit completions to this size.
 * @param insert String to set with characters that can be autocompleted.
 * @return TRUE if the completion was unambiguous, else FALSE.
 *
 * @memberof teco_rb3str_tree_t
 */
gboolean
teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive,
                          const gchar *str, gsize str_len, gsize restrict_len, teco_string_t *insert)
{
	memset(insert, 0, sizeof(*insert));

	teco_string_diff_t diff = case_sensitive ? teco_string_diff : teco_string_casediff;
	teco_rb3str_head_t *first = NULL;
	gsize prefix_len = 0;
	guint prefixed_entries = 0;

	for (teco_rb3str_head_t *cur = teco_rb3str_nfind(tree, case_sensitive, str, str_len);
	     cur && cur->key.len >= str_len && diff(&cur->key, str, str_len) == str_len;
	     cur = teco_rb3str_get_next(cur)) {
		if (restrict_len && cur->key.len != restrict_len)
			continue;

		if (G_UNLIKELY(!first)) {
			first = cur;
			prefix_len = cur->key.len - str_len;
		} else {
			gsize len = diff(&cur->key, first->key.data, first->key.len) - str_len;
			if (len < prefix_len)
				prefix_len = len;
		}

		prefixed_entries++;
	}

	if (prefix_len > 0) {
		teco_string_init(insert, first->key.data + str_len, prefix_len);
	} else if (prefixed_entries > 1) {
		for (teco_rb3str_head_t *cur = first;
		     cur && cur->key.len >= str_len && diff(&cur->key, str, str_len) == str_len;
		     cur = teco_rb3str_get_next(cur)) {
			if (restrict_len && cur->key.len != restrict_len)
				continue;

			teco_interface_popup_add(TECO_POPUP_PLAIN,
			                         cur->key.data, cur->key.len, FALSE);
		}

		teco_interface_popup_show();
	}

	return prefixed_entries == 1;
}