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 -----------------------------------------------------------------------------*/
11 #include "stringbuf.h"
15 //-------------------------------------------------------------
16 // Syntax highlighting
17 //-------------------------------------------------------------
19 struct ic_highlight_env_s
{
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
;
43 (*highlighter
)( &henv
, s
, arg
);
48 //-------------------------------------------------------------
50 //-------------------------------------------------------------
52 static void pos_adjust( ic_highlight_env_t
* henv
, ssize_t
* ppos
, ssize_t
* plen
) {
55 if (pos
>= henv
->input_len
) { return; }
56 if (pos
>= 0 && len
>= 0) { return; } // already character positions
57 if (henv
->input
== NULL
) { return; }
60 // negative `pos` is used as the unicode character position (for easy interfacing with Haskell)
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; }
75 // and cache it to avoid quadratic behavior
76 henv
->cached_upos
= upos
;
77 henv
->cached_cpos
= cpos
;
80 // negative `len` is used as a unicode character length
84 while (ucount
< len
) {
85 ssize_t next
= str_next_ofs(henv
->input
, henv
->input_len
, pos
+ clen
, NULL
);
86 if (next
<= 0) { return; }
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
));
129 //-------------------------------------------------------------
131 //-------------------------------------------------------------
132 #define MAX_NESTING (64)
134 typedef struct brace_s
{
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];
144 const ssize_t brace_len
= ic_strlen(braces
);
145 for (long i
= 0; i
< ic_strlen(s
); i
++) {
148 bool found_open
= false;
149 for (ssize_t b
= 0; b
< brace_len
; b
+= 2) {
150 if (c
== braces
[b
]) {
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);
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
]) {
168 // unmatched close brace
169 attrbuf_update_at( attrs
, i
, 1, error_attr
);
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
);
178 if (open
[nesting
-1].close
!= c
) {
179 // unmatched open brace
180 attrbuf_update_at( attrs
, i
, 1, error_attr
);
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
);
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;
205 brace_t open
[MAX_NESTING
+1];
207 const ssize_t brace_len
= ic_strlen(braces
);
208 for (long i
= 0; i
< ic_strlen(s
); i
++) {
211 bool found_open
= false;
212 for (ssize_t b
= 0; b
< brace_len
; b
+= 2) {
213 if (c
== braces
[b
]) {
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);
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
]) {
231 // unmatched close brace
235 if (open
[nesting
-1].close
!= c
) {
236 // unmatched open brace
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
256 if (nesting
!= 0) { balanced
= false; }
257 if (is_balanced
!= NULL
) { *is_balanced
= balanced
; }