-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlib-whitespace.c
339 lines (307 loc) · 8.28 KB
/
lib-whitespace.c
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
* Copyright Neil Brown ©2019-2023 <[email protected]>
* May be distributed under terms of GPLv2 - see file:COPYING
*
* This module provides highlighting of interesting white-space,
* and possibly other spacing-related issues.
* Currently:
* tabs are in a different colour (yellow-80+80)
* unicode spaces a different colour (red+80-80)
* space at EOL are RED (red)
* TAB after space are RED (red-80)
* anything beyond configured line length is RED (red-80+50 "whitespace-width" or 80)
* non-space as first char RED if configured ("whitespace-intent-space")
* >=8 spaces RED if configured ("whitespace-max-spaces")
* blank line adjacent to blank or start/end of file if configured ("whitespace-single-blank-lines")
*
* This is achieved by capturing the "start-of-line" attribute request,
* reporting attributes that apply to leading chars, and placing a
* mark with a "render:whitespace" attribute at the next interesting place, if
* there is one.
*/
#define _GNU_SOURCE /* for asprintf */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <wctype.h>
#define PANE_DATA_TYPE struct ws_info
#include "core.h"
struct ws_info {
struct mark *mymark;
int mycol;
int warn_width;
int max_spaces;
bool indent_space;
bool single_blanks;
};
#include "core-pane.h"
/* a0 and 2007 are non-breaking an not in iswblank, but I want them. */
#define ISWBLANK(c) ((c) == 0xa0 || (c) == 0x2007 || iswblank(c))
static void choose_next(struct pane *focus safe, struct mark *pm safe,
struct ws_info *ws safe, int skip)
{
struct mark *m = ws->mymark;
if (m == NULL) {
m = mark_dup(pm);
ws->mymark = m;
} else
mark_to_mark(m, pm);
while (skip > 0) {
/* Need to look beyond the current location */
wint_t ch;
ch = doc_next(focus, m);
skip -= 1;
if (ch == '\t')
ws->mycol = (ws->mycol | 7) + 1;
else if (ch != WEOF && !is_eol(ch))
ws->mycol += 1;
else
skip = 0;
}
while(1) {
int cnt, rewind, rewindcol, col;
wint_t ch = doc_following(focus, m);
wint_t ch2;
if (ch == WEOF || is_eol(ch)) {
if (ws->mycol > 0)
break;
if (ch == WEOF || !ws->single_blanks)
break;
/* A blank line must not be preceeded or followed
* by EOF of a blank line.
*/
doc_next(focus, m);
ch = doc_following(focus, m);
doc_prev(focus, m);
if (ch != WEOF && !is_eol(ch)) {
/* Next line isn't blank. need to check behind */
ch = doc_prev(focus, m);
if (ch == WEOF) {
/* blank line at start of file */
} else if (!is_eol(ch)) {
/* should be impossible, so ignore */
doc_next(focus, m);
break;
} else {
ch = doc_prior(focus, m);
doc_next(focus, m);
if (ch != WEOF && ch != '\n')
/* previous line not blank either */
break;
}
}
attr_set_str(&m->attrs, "render:whitespace",
"bg:red,vis-nl");
attr_set_int(&m->attrs, "attr-len", 1);
return;
}
if (ws->mycol >= ws->warn_width) {
/* everything from here is an error */
attr_set_str(&m->attrs, "render:whitespace",
"bg:red-80+50");
attr_set_int(&m->attrs, "attr-len", INT_MAX);
return;
}
if (!ISWBLANK(ch)) {
/* Nothing to highlight here, move forward */
doc_next(focus, m);
ws->mycol++;
continue;
}
/* If only spaces/tabs until EOL, then RED,
* else keep looking
*/
cnt = 0;
rewind = INT_MAX;
rewindcol = 0;
col = ws->mycol;
while ((ch = doc_next(focus, m)) != WEOF &&
ISWBLANK(ch)) {
if (ch != ' ' && cnt < rewind) {
/* This may be different colours depending on
* what we find, so remember this location.
*/
rewind = cnt;
rewindcol = col;
}
if (ch == '\t')
col = (ws->mycol|7)+1;
else
col += 1;
cnt += 1;
}
if (ch != WEOF)
doc_prev(focus, m);
if (ws->mycol == 0 && ws->indent_space && rewind == 0) {
/* Indents must be space, but this is something else,
* so highlight all the indent
*/
doc_move(focus, m, -cnt);
attr_set_str(&m->attrs, "render:whitespace",
"bg:red");
attr_set_int(&m->attrs, "attr-len", cnt);
return;
}
if (cnt > ws->max_spaces && rewind > ws->max_spaces) {
/* Too many spaces - not too loud a highlight*/
doc_move(focus, m, -cnt);
if (rewind < cnt)
cnt = rewind;
attr_set_str(&m->attrs, "render:whitespace",
"bg:red+60");
attr_set_int(&m->attrs, "attr-len", cnt);
return;
}
/*
* 'm' is just after last blank. ch is next (non-blank)
* char. 'cnt' is the number of blanks.
* 'rewind' is distance from start where first non-space seen.
*/
if (ch == WEOF || is_eol(ch)) {
/* Blanks all the way to EOL. This is highlighted unless
* point is at EOL
*/
struct mark *p = call_ret(mark, "doc:point",
focus);
if (p && mark_same(m, p))
ch = 'x';
}
if (ch == WEOF || is_eol(ch)) {
doc_move(focus, m, -cnt);
/* Set the blanks at EOL to red */
attr_set_str(&m->attrs, "render:whitespace",
"bg:red");
attr_set_int(&m->attrs, "attr-len", cnt);
return;
}
if (rewind > cnt) {
/* no non-space, nothing to do here */
ws->mycol = col;
continue;
}
/* That first blank is no RED, set normal colour */
doc_move(focus, m, rewind - cnt);
ws->mycol = rewindcol;
/* handle tab */
/* If previous is non-tab, then RED, else YELLOW */
ch = doc_prior(focus, m);
ch2 = doc_following(focus, m);
if (ch2 == '\t' && ch != WEOF && ch != '\t' && ISWBLANK(ch))
/* Tab after non-tab blank - bad */
attr_set_str(&m->attrs, "render:whitespace",
"bg:red-80");
else if (ch2 == '\t')
attr_set_str(&m->attrs, "render:whitespace",
"bg:yellow-80+80");
else
/* non-space or tab, must be unicode blank of some sort */
attr_set_str(&m->attrs, "render:whitespace",
"bg:red-80+80");
attr_set_int(&m->attrs, "attr-len", 1);
return;
}
attr_set_str(&m->attrs, "render:whitespace", NULL);
}
DEF_CMD(ws_attrs)
{
struct ws_info *ws = ci->home->data;
if (!ci->str || !ci->mark)
return Enoarg;
if (strcmp(ci->str, "start-of-line") == 0) {
if (ws->mymark)
mark_free(ws->mymark);
ws->mymark = NULL;
ws->mycol = 0;
choose_next(ci->focus, ci->mark, ws, 0);
return Efallthrough;
}
if (ci->mark == ws->mymark &&
strcmp(ci->str, "render:whitespace") == 0) {
char *s = strsave(ci->focus, ci->str2);
int len = attr_find_int(ci->mark->attrs, "attr-len");
if (len <= 0)
len = 1;
choose_next(ci->focus, ci->mark, ws, len);
return comm_call(ci->comm2, "attr:callback", ci->focus, len,
ci->mark, s, 110);
}
return Efallthrough;
}
DEF_CMD_CLOSED(ws_close)
{
struct ws_info *ws = ci->home->data;
mark_free(ws->mymark);
ws->mymark = NULL;
return 1;
}
static struct map *ws_map safe;
DEF_LOOKUP_CMD(whitespace_handle, ws_map);
static struct pane *ws_attach(struct pane *f safe)
{
struct ws_info *ws;
struct pane *p;
char *w;
p = pane_register(f, 0, &whitespace_handle.c);
if (!p)
return p;
ws = p->data;
w = pane_attr_get(f, "whitespace-width");
if (w) {
ws->warn_width = atoi(w);
if (ws->warn_width < 8)
ws->warn_width = INT_MAX;
} else
ws->warn_width = 80;
w = pane_attr_get(f, "whitespace-indent-space");
if (w && strcasecmp(w, "no") != 0)
ws->indent_space = True;
w = pane_attr_get(f, "whitespace-max-spaces");
if (w) {
ws->max_spaces = atoi(w);
if (ws->max_spaces < 1)
ws->max_spaces = 7;
} else
ws->max_spaces = INT_MAX;
w = pane_attr_get(f, "whitespace-single-blank-lines");
if (w && strcasecmp(w, "no") != 0)
ws->single_blanks = True;
return p;
}
DEF_CMD(ws_clone)
{
struct pane *p;
p = ws_attach(ci->focus);
if (p)
pane_clone_children(ci->home, p);
return 1;
}
DEF_CMD(whitespace_attach)
{
struct pane *p;
p = ws_attach(ci->focus);
if (!p)
return Efail;
return comm_call(ci->comm2, "callback:attach", p);
}
DEF_CMD(whitespace_activate)
{
struct pane *p;
p = call_ret(pane, "attach-whitespace", ci->focus);
if (!p)
return Efail;
call("doc:append:view-default", p, 0, NULL, ",whitespace");
return 1;
}
void edlib_init(struct pane *ed safe)
{
ws_map = key_alloc();
key_add(ws_map, "map-attr", &ws_attrs);
key_add(ws_map, "Close", &ws_close);
key_add(ws_map, "Clone", &ws_clone);
call_comm("global-set-command", ed, &whitespace_attach,
0, NULL, "attach-whitespace");
call_comm("global-set-command", ed, &whitespace_activate,
0, NULL, "interactive-cmd-whitespace-mode");
}