Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/extern/isocline/src/highlight.c
2727 views
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2021, Daan Leijen
3
This is free software; you can redistribute it and/or modify it
4
under the terms of the MIT License. A copy of the license can be
5
found in the "LICENSE" file at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
8
#include <string.h>
9
#include "common.h"
10
#include "term.h"
11
#include "stringbuf.h"
12
#include "attr.h"
13
#include "bbcode.h"
14
15
//-------------------------------------------------------------
16
// Syntax highlighting
17
//-------------------------------------------------------------
18
19
struct ic_highlight_env_s {
20
attrbuf_t* attrs;
21
const char* input;
22
ssize_t input_len;
23
bbcode_t* bbcode;
24
alloc_t* mem;
25
ssize_t cached_upos; // cached unicode position
26
ssize_t cached_cpos; // corresponding utf-8 byte position
27
};
28
29
30
ic_private void highlight( alloc_t* mem, bbcode_t* bb, const char* s, attrbuf_t* attrs, ic_highlight_fun_t* highlighter, void* arg ) {
31
const ssize_t len = ic_strlen(s);
32
if (len <= 0) return;
33
attrbuf_set_at(attrs,0,len,attr_none()); // fill to length of s
34
if (highlighter != NULL) {
35
ic_highlight_env_t henv;
36
henv.attrs = attrs;
37
henv.input = s;
38
henv.input_len = len;
39
henv.bbcode = bb;
40
henv.mem = mem;
41
henv.cached_cpos = 0;
42
henv.cached_upos = 0;
43
(*highlighter)( &henv, s, arg );
44
}
45
}
46
47
48
//-------------------------------------------------------------
49
// Client interface
50
//-------------------------------------------------------------
51
52
static void pos_adjust( ic_highlight_env_t* henv, ssize_t* ppos, ssize_t* plen ) {
53
ssize_t pos = *ppos;
54
ssize_t len = *plen;
55
if (pos >= henv->input_len) return;
56
if (pos >= 0 && len >= 0) return; // already character positions
57
if (henv->input == NULL) return;
58
59
if (pos < 0) {
60
// negative `pos` is used as the unicode character position (for easy interfacing with Haskell)
61
ssize_t upos = -pos;
62
ssize_t cpos = 0;
63
ssize_t ucount = 0;
64
if (henv->cached_upos <= upos) { // if we have a cached position, start from there
65
ucount = henv->cached_upos;
66
cpos = henv->cached_cpos;
67
}
68
while ( ucount < upos ) {
69
ssize_t next = str_next_ofs(henv->input, henv->input_len, cpos, NULL);
70
if (next <= 0) return;
71
ucount++;
72
cpos += next;
73
}
74
*ppos = pos = cpos;
75
// and cache it to avoid quadratic behavior
76
henv->cached_upos = upos;
77
henv->cached_cpos = cpos;
78
}
79
if (len < 0) {
80
// negative `len` is used as a unicode character length
81
len = -len;
82
ssize_t ucount = 0;
83
ssize_t clen = 0;
84
while (ucount < len) {
85
ssize_t next = str_next_ofs(henv->input, henv->input_len, pos + clen, NULL);
86
if (next <= 0) return;
87
ucount++;
88
clen += next;
89
}
90
*plen = len = clen;
91
// and update cache if possible
92
if (henv->cached_cpos == pos) {
93
henv->cached_upos += ucount;
94
henv->cached_cpos += clen;
95
}
96
}
97
}
98
99
static void highlight_attr(ic_highlight_env_t* henv, ssize_t pos, ssize_t count, attr_t attr ) {
100
if (henv==NULL) return;
101
pos_adjust(henv,&pos,&count);
102
if (pos < 0 || count <= 0) return;
103
attrbuf_update_at(henv->attrs, pos, count, attr);
104
}
105
106
ic_public void ic_highlight(ic_highlight_env_t* henv, long pos, long count, const char* style ) {
107
if (henv == NULL || style==NULL || style[0]==0 || pos < 0) return;
108
highlight_attr(henv,pos,count,bbcode_style( henv->bbcode, style ));
109
}
110
111
ic_public void ic_highlight_formatted(ic_highlight_env_t* henv, const char* s, const char* fmt) {
112
if (s==NULL || s[0] == 0 || fmt==NULL) return;
113
attrbuf_t* attrs = attrbuf_new(henv->mem);
114
stringbuf_t* out = sbuf_new(henv->mem); // todo: avoid allocating out?
115
if (attrs!=NULL && out != NULL) {
116
bbcode_append( henv->bbcode, fmt, out, attrs);
117
const ssize_t len = ic_strlen(s);
118
if (sbuf_len(out) != len) {
119
debug_msg("highlight: formatted string content differs from the original input:\n original: %s\n formatted: %s\n", s, fmt);
120
}
121
for( ssize_t i = 0; i < len; i++) {
122
attrbuf_update_at(henv->attrs, i, 1, attrbuf_attr_at(attrs,i));
123
}
124
}
125
sbuf_free(out);
126
attrbuf_free(attrs);
127
}
128
129
//-------------------------------------------------------------
130
// Brace matching
131
//-------------------------------------------------------------
132
#define MAX_NESTING (64)
133
134
typedef struct brace_s {
135
char close;
136
bool at_cursor;
137
ssize_t pos;
138
} brace_t;
139
140
ic_private void highlight_match_braces(const char* s, attrbuf_t* attrs, ssize_t cursor_pos, const char* braces, attr_t match_attr, attr_t error_attr)
141
{
142
brace_t open[MAX_NESTING+1];
143
ssize_t nesting = 0;
144
const ssize_t brace_len = ic_strlen(braces);
145
for (long i = 0; i < ic_strlen(s); i++) {
146
const char c = s[i];
147
// push open brace
148
bool found_open = false;
149
for (ssize_t b = 0; b < brace_len; b += 2) {
150
if (c == braces[b]) {
151
// open brace
152
if (nesting >= MAX_NESTING) return; // give up
153
open[nesting].close = braces[b+1];
154
open[nesting].pos = i;
155
open[nesting].at_cursor = (i == cursor_pos - 1);
156
nesting++;
157
found_open = true;
158
break;
159
}
160
}
161
if (found_open) continue;
162
163
// pop to closing brace and potentially highlight
164
for (ssize_t b = 1; b < brace_len; b += 2) {
165
if (c == braces[b]) {
166
// close brace
167
if (nesting <= 0) {
168
// unmatched close brace
169
attrbuf_update_at( attrs, i, 1, error_attr);
170
}
171
else {
172
// can we fix an unmatched brace where we can match by popping just one?
173
if (open[nesting-1].close != c && nesting > 1 && open[nesting-2].close == c) {
174
// assume previous open brace was wrong
175
attrbuf_update_at(attrs, open[nesting-1].pos, 1, error_attr);
176
nesting--;
177
}
178
if (open[nesting-1].close != c) {
179
// unmatched open brace
180
attrbuf_update_at( attrs, i, 1, error_attr);
181
}
182
else {
183
// matching brace
184
nesting--;
185
if (i == cursor_pos - 1 || (open[nesting].at_cursor && open[nesting].pos != i - 1)) {
186
// highlight matching brace
187
attrbuf_update_at(attrs, open[nesting].pos, 1, match_attr);
188
attrbuf_update_at(attrs, i, 1, match_attr);
189
}
190
}
191
}
192
break;
193
}
194
}
195
}
196
// note: don't mark further unmatched open braces as in error
197
}
198
199
200
ic_private ssize_t find_matching_brace(const char* s, ssize_t cursor_pos, const char* braces, bool* is_balanced)
201
{
202
if (is_balanced != NULL) { *is_balanced = false; }
203
bool balanced = true;
204
ssize_t match = -1;
205
brace_t open[MAX_NESTING+1];
206
ssize_t nesting = 0;
207
const ssize_t brace_len = ic_strlen(braces);
208
for (long i = 0; i < ic_strlen(s); i++) {
209
const char c = s[i];
210
// push open brace
211
bool found_open = false;
212
for (ssize_t b = 0; b < brace_len; b += 2) {
213
if (c == braces[b]) {
214
// open brace
215
if (nesting >= MAX_NESTING) return -1; // give up
216
open[nesting].close = braces[b+1];
217
open[nesting].pos = i;
218
open[nesting].at_cursor = (i == cursor_pos - 1);
219
nesting++;
220
found_open = true;
221
break;
222
}
223
}
224
if (found_open) continue;
225
226
// pop to closing brace
227
for (ssize_t b = 1; b < brace_len; b += 2) {
228
if (c == braces[b]) {
229
// close brace
230
if (nesting <= 0) {
231
// unmatched close brace
232
balanced = false;
233
}
234
else {
235
if (open[nesting-1].close != c) {
236
// unmatched open brace
237
balanced = false;
238
}
239
else {
240
// matching brace
241
nesting--;
242
if (i == cursor_pos - 1) {
243
// found matching open brace
244
match = open[nesting].pos + 1;
245
}
246
else if (open[nesting].at_cursor) {
247
// found matching close brace
248
match = i + 1;
249
}
250
}
251
}
252
break;
253
}
254
}
255
}
256
if (nesting != 0) { balanced = false; }
257
if (is_balanced != NULL) { *is_balanced = balanced; }
258
return match;
259
}
260
261