remove gcc for MacOS in conan.yml
[liba.git] / lua / isocline / src / highlight.c
blobb5096f4f204b7a6bc872f76fbdf8cc60fd0610a1
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 -----------------------------------------------------------------------------*/
8 #include <string.h>
9 #include "common.h"
10 #include "term.h"
11 #include "stringbuf.h"
12 #include "attr.h"
13 #include "bbcode.h"
15 //-------------------------------------------------------------
16 // Syntax highlighting
17 //-------------------------------------------------------------
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
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 );
48 //-------------------------------------------------------------
49 // Client interface
50 //-------------------------------------------------------------
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; }
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;
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;
74 *ppos = pos = cpos;
75 // and cache it to avoid quadratic behavior
76 henv->cached_upos = upos;
77 henv->cached_cpos = cpos;
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;
90 *plen = clen;
91 // and update cache if possible
92 if (henv->cached_cpos == pos) {
93 henv->cached_upos += ucount;
94 henv->cached_cpos += clen;
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);
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 ));
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);
121 for( ssize_t i = 0; i < len; i++) {
122 attrbuf_update_at(henv->attrs, i, 1, attrbuf_attr_at(attrs,i));
125 sbuf_free(out);
126 attrbuf_free(attrs);
129 //-------------------------------------------------------------
130 // Brace matching
131 //-------------------------------------------------------------
132 #define MAX_NESTING (64)
134 typedef struct brace_s {
135 char close;
136 bool at_cursor;
137 ssize_t pos;
138 } brace_t;
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)
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;
161 if (found_open) { continue; }
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);
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--;
178 if (open[nesting-1].close != c) {
179 // unmatched open brace
180 attrbuf_update_at( attrs, i, 1, error_attr);
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);
192 break;
196 // note: don't mark further unmatched open braces as in error
200 ic_private ssize_t find_matching_brace(const char* s, ssize_t cursor_pos, const char* braces, bool* is_balanced)
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;
224 if (found_open) { continue; }
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;
234 else {
235 if (open[nesting-1].close != c) {
236 // unmatched open brace
237 balanced = false;
239 else {
240 // matching brace
241 nesting--;
242 if (i == cursor_pos - 1) {
243 // found matching open brace
244 match = open[nesting].pos + 1;
246 else if (open[nesting].at_cursor) {
247 // found matching close brace
248 match = i + 1;
252 break;
256 if (nesting != 0) { balanced = false; }
257 if (is_balanced != NULL) { *is_balanced = balanced; }
258 return match;