eel_edit: do not go to edit cursor when saving [t=285086] -- from 47553d34
[wdl.git] / WDL / win32_curses / eel_edit.cpp
blob14379907a2fc8f2c9817fb1c395129c9f33ab76d
1 #ifdef _WIN32
2 #include <windows.h>
3 #include <windowsx.h>
4 #else
5 #include "../swell/swell.h"
6 #endif
7 #include <stdlib.h>
8 #include <string.h>
9 #ifndef CURSES_INSTANCE
10 #define CURSES_INSTANCE ((win32CursesCtx *)m_cursesCtx)
11 #endif
12 #include "curses.h"
13 #include "eel_edit.h"
14 #include "../wdlutf8.h"
15 #include "../win32_utf8.h"
16 #include "../wdlcstring.h"
17 #include "../eel2/ns-eel-int.h"
19 int g_eel_editor_max_vis_suggestions = 50;
20 int g_eel_editor_flags;
22 EEL_Editor::EEL_Editor(void *cursesCtx) : WDL_CursesEditor(cursesCtx)
24 m_suggestion_curline_comment_state=0;
25 m_suggestion_x=m_suggestion_y=-1;
26 m_case_sensitive=false;
27 m_comment_str="//"; // todo IsWithinComment() or something?
28 m_function_prefix = "function ";
30 m_suggestion_hwnd=NULL;
31 m_suggestion_hwnd_scroll=0;
32 m_suggestion_hwnd_sel=-1;
33 m_code_func_cache_lines=0;
34 m_code_func_cache_time=0;
37 EEL_Editor::~EEL_Editor()
39 if (m_suggestion_hwnd) DestroyWindow(m_suggestion_hwnd);
40 m_code_func_cache.Empty(true,free);
43 #define sh_func_ontoken(x,y)
45 int EEL_Editor::namedTokenHighlight(const char *tokStart, int len, int state)
47 if (len == 4 && !strnicmp(tokStart,"this",4)) return SYNTAX_KEYWORD;
48 if (len == 7 && !strnicmp(tokStart,"_global",7)) return SYNTAX_KEYWORD;
49 if (len == 5 && !strnicmp(tokStart,"local",5)) return SYNTAX_KEYWORD;
50 if (len == 8 && !strnicmp(tokStart,"function",8)) return SYNTAX_KEYWORD;
51 if (len == 6 && !strnicmp(tokStart,"static",6)) return SYNTAX_KEYWORD;
52 if (len == 8 && !strnicmp(tokStart,"instance",8)) return SYNTAX_KEYWORD;
53 if (len == 6 && !strnicmp(tokStart,"global",6)) return SYNTAX_KEYWORD;
54 if (len == 7 && !strnicmp(tokStart,"globals",7)) return SYNTAX_KEYWORD;
56 if (len == 5 && !strnicmp(tokStart,"while",5)) return SYNTAX_KEYWORD;
57 if (len == 4 && !strnicmp(tokStart,"loop",4)) return SYNTAX_KEYWORD;
59 if (len == 17 && !strnicmp(tokStart,"__denormal_likely",17)) return SYNTAX_FUNC;
60 if (len == 19 && !strnicmp(tokStart,"__denormal_unlikely",19)) return SYNTAX_FUNC;
62 char buf[512];
63 lstrcpyn_safe(buf,tokStart,wdl_min(sizeof(buf),len+1));
64 NSEEL_VMCTX vm = peek_want_VM_funcs() ? peek_get_VM() : NULL;
65 if (nseel_getFunctionByName((compileContext*)vm,buf,NULL)) return SYNTAX_FUNC;
67 return A_NORMAL;
70 int EEL_Editor::parse_format_specifier(const char *fmt_in, int *var_offs, int *var_len)
72 const char *fmt = fmt_in+1;
73 *var_offs = 0;
74 *var_len = 0;
75 if (fmt_in[0] != '%') return 0; // passed a non-specifier
77 while (*fmt)
79 const char c = *fmt++;
81 if (c>0 && isalpha(c))
83 return (int) (fmt - fmt_in);
86 if (c == '.' || c == '+' || c == '-' || c == ' ' || (c>='0' && c<='9'))
89 else if (c == '{')
91 if (*var_offs!=0) return 0; // already specified
92 *var_offs = (int)(fmt-fmt_in);
93 if (*fmt == '.' || (*fmt >= '0' && *fmt <= '9')) return 0; // symbol name can't start with 0-9 or .
95 while (*fmt != '}')
97 if ((*fmt >= 'a' && *fmt <= 'z') ||
98 (*fmt >= 'A' && *fmt <= 'Z') ||
99 (*fmt >= '0' && *fmt <= '9') ||
100 *fmt == '_' || *fmt == '.' || *fmt == '#')
102 fmt++;
104 else
106 return 0; // bad character in variable name
109 *var_len = (int)((fmt-fmt_in) - *var_offs);
110 fmt++;
112 else
114 break;
117 return 0;
121 void EEL_Editor::draw_string(int *skipcnt, const char *str, int amt, int *attr, int newAttr, int comment_string_state)
123 if (amt > 0 && comment_string_state=='"')
125 while (amt > 0 && *str)
127 const char *str_scan = str;
128 int varpos,varlen,l=0;
130 while (!l && *str_scan)
132 while (*str_scan && *str_scan != '%' && str_scan < str+amt) str_scan++;
133 if (str_scan >= str+amt) break;
135 l = parse_format_specifier(str_scan,&varpos,&varlen);
136 if (!l && *str_scan) if (*++str_scan == '%') str_scan++;
138 if (!*str_scan || str_scan >= str+amt) break; // allow default processing to happen if we reached the end of the string
140 if (l > amt) l=amt;
142 if (str_scan > str)
144 const int sz=wdl_min((int)(str_scan-str),amt);
145 draw_string_urlchk(skipcnt,str,sz,attr,newAttr);
146 str += sz;
147 amt -= sz;
151 const int sz=(varlen>0) ? wdl_min(varpos,amt) : wdl_min(l,amt);
152 if (sz>0)
154 draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT2);
155 str += sz;
156 amt -= sz;
160 if (varlen>0)
162 int sz = wdl_min(varlen,amt);
163 if (sz>0)
165 draw_string_internal(skipcnt,str,sz,attr,*str == '#' ? SYNTAX_STRINGVAR : SYNTAX_HIGHLIGHT1);
166 amt -= sz;
167 str += sz;
170 sz = wdl_min(l - varpos - varlen, amt);
171 if (sz>0)
173 draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT2);
174 amt-=sz;
175 str+=sz;
180 draw_string_urlchk(skipcnt,str,amt,attr,newAttr);
183 void EEL_Editor::draw_string_urlchk(int *skipcnt, const char *str, int amt, int *attr, int newAttr)
185 if (amt > 0 && (newAttr == SYNTAX_COMMENT || newAttr == SYNTAX_STRING))
187 const char *sstr=str;
188 while (amt > 0 && *str)
190 const char *str_scan = str;
191 int l=0;
193 while (l < 10 && *str_scan)
195 str_scan += l;
196 l=0;
197 while (*str_scan &&
198 (strncmp(str_scan,"http://",7) || (sstr != str_scan && str_scan[-1] > 0 && isalnum(str_scan[-1]))) &&
199 str_scan < str+amt) str_scan++;
200 if (!*str_scan || str_scan >= str+amt) break;
201 while (str_scan[l] && str_scan[l] != ')' && str_scan[l] != '\"' && str_scan[l] != ')' && str_scan[l] != ' ' && str_scan[l] != '\t') l++;
203 if (!*str_scan || str_scan >= str+amt) break; // allow default processing to happen if we reached the end of the string
205 if (l > amt) l=amt;
207 if (str_scan > str)
209 const int sz=wdl_min((int)(str_scan-str),amt);
210 draw_string_internal(skipcnt,str,sz,attr,newAttr);
211 str += sz;
212 amt -= sz;
215 const int sz=wdl_min(l,amt);
216 if (sz>0)
218 draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT1);
219 str += sz;
220 amt -= sz;
224 draw_string_internal(skipcnt,str,amt,attr,newAttr);
227 void EEL_Editor::draw_string_internal(int *skipcnt, const char *str, int amt, int *attr, int newAttr)
229 // *skipcnt is in characters, amt is in bytes
230 while (*skipcnt > 0 && amt > 0)
232 const int clen = wdl_utf8_parsechar(str,NULL);
233 str += clen;
234 amt -= clen;
235 *skipcnt -= 1;
238 if (amt>0)
240 if (*attr != newAttr)
242 attrset(newAttr);
243 *attr = newAttr;
245 addnstr(str,amt);
250 WDL_TypedBuf<char> EEL_Editor::s_draw_parentokenstack;
251 bool EEL_Editor::sh_draw_parenttokenstack_pop(char c)
253 int sz = s_draw_parentokenstack.GetSize();
254 while (--sz >= 0)
256 char tc = s_draw_parentokenstack.Get()[sz];
257 if (tc == c)
259 s_draw_parentokenstack.Resize(sz,false);
260 return false;
263 switch (c)
265 case '?':
266 // any open paren or semicolon is enough to cause error for ?:
268 return true;
269 case '(':
270 if (tc == 'P') return true;
271 if (tc == '[') return true;
272 break;
273 case '[':
274 if (tc == 'P') return true;
275 if (tc == '(') return true;
276 break;
277 case 'P':
278 if (tc != '?' && tc != ';') return true;
279 break;
283 return true;
286 static int is_preproc_token(const char *tok)
288 if (tok[0] == '<') return tok[1] == '?' ? 1 : 0;
289 if (tok[0] == '?') return tok[1] == '>' ? -1 : 0;
290 return 0;
293 bool EEL_Editor::sh_draw_parentokenstack_update(const char *tok, int toklen)
295 if (toklen == 1)
297 switch (*tok)
299 case '(':
300 case '[':
301 case ';':
302 case '?':
303 s_draw_parentokenstack.Add(*tok);
304 break;
305 case ':': return sh_draw_parenttokenstack_pop('?');
306 case ')': return sh_draw_parenttokenstack_pop('(');
307 case ']': return sh_draw_parenttokenstack_pop('[');
310 else if (toklen == 2)
312 switch (is_preproc_token(tok))
314 case 1: s_draw_parentokenstack.Add('P'); break;
315 case -1: return sh_draw_parenttokenstack_pop('P');
318 return false;
322 void EEL_Editor::draw_line_highlight(int y, const char *p, int *c_comment_state, int line_n)
324 if (line_n == m_curs_y)
325 m_suggestion_curline_comment_state = *c_comment_state;
326 int last_attr = A_NORMAL;
327 attrset(last_attr);
328 move(y, 0);
329 int rv = do_draw_line(p, c_comment_state, last_attr);
330 attrset(rv< 0 ? SYNTAX_ERROR : A_NORMAL);
331 clrtoeol();
332 if (rv < 0) attrset(A_NORMAL);
335 #define STATE_PREPROC (0x20000000)
336 #define STATE_PREPROC_SINGLELINEMODE (0x10000000)
337 #define STATE_PREPROC_MASK (0x3ff00000)
338 #define STATE_PREPROC_ENCODE_OLD(x) (((x)<<20)&0x0ff00000)
339 #define STATE_PREPROC_DECODE_OLD(x) (((x)&0x0ff00000)>>20)
342 int EEL_Editor::do_draw_line(const char *p, int *c_comment_state, int last_attr)
344 if (!(*c_comment_state & STATE_PREPROC) && is_code_start_line(p))
346 *c_comment_state=0;
347 s_draw_parentokenstack.Resize(0,false);
350 int skipcnt = m_offs_x;
351 int ignoreSyntaxState = 0;
353 const int initial_state = *c_comment_state;
354 if (!(initial_state & STATE_PREPROC))
355 ignoreSyntaxState = overrideSyntaxDrawingForLine(&skipcnt, &p, c_comment_state, &last_attr);
357 if (ignoreSyntaxState>0)
359 int len = (int)strlen(p);
360 draw_string(&skipcnt,p,len,&last_attr,ignoreSyntaxState==100 ? SYNTAX_ERROR :
361 ignoreSyntaxState==2 ? SYNTAX_COMMENT : A_NORMAL);
362 return len-m_offs_x < COLS;
364 if (ignoreSyntaxState<0 && initial_state == STATE_BEFORE_CODE) *c_comment_state=0;
366 if ((initial_state & STATE_PREPROC) && (initial_state & STATE_PREPROC_SINGLELINEMODE))
367 ignoreSyntaxState = -1;
369 // syntax highlighting
370 const char *endptr = p+strlen(p);
371 const char *tok;
372 const char *lp = p;
373 int toklen=0;
374 int last_comment_state=*c_comment_state & ~STATE_PREPROC_MASK;
375 while (NULL != (tok = sh_tokenize(&p,endptr,&toklen,c_comment_state)) || lp < endptr)
377 if (initial_state == STATE_BEFORE_CODE && !(*c_comment_state & STATE_PREPROC) && !ignoreSyntaxState)
378 *c_comment_state = initial_state;
379 if (tok && *tok < 0 && toklen == 1)
381 while (tok[toklen] < 0) {p++; toklen++; } // utf-8 skip
383 bool is_pp = toklen == 2 && is_preproc_token(tok);
384 if (!is_pp && (last_comment_state>0 || *c_comment_state == STATE_BEFORE_CODE)) // if in a multi-line string or comment
386 // draw empty space between lp and p as a string. in this case, tok/toklen includes our string, so we quickly finish after
387 draw_string(&skipcnt,lp,(int)(p-lp),&last_attr,
388 last_comment_state == 1 ||
389 last_comment_state == STATE_BEFORE_CODE ||
390 *c_comment_state == STATE_BEFORE_CODE ? SYNTAX_COMMENT : SYNTAX_STRING,
391 last_comment_state);
392 last_comment_state=0;
393 lp = p;
394 continue;
396 sh_func_ontoken(tok,toklen);
398 // draw empty space between lp and tok/endptr as normal
399 const char *adv_to = tok ? tok : endptr;
400 if (adv_to > lp) draw_string(&skipcnt,lp,(int)(adv_to-lp),&last_attr, A_NORMAL);
402 if (adv_to >= endptr) break;
404 last_comment_state=0;
405 lp = p;
406 if (adv_to == p) continue;
408 // draw token
409 int attr = A_NORMAL;
410 int err_left=0;
411 int err_right=0;
412 int start_of_tok = 0;
414 if (tok[0] == '/' && toklen > 1 && (tok[1] == '*' || tok[1] == '/'))
416 attr = SYNTAX_COMMENT;
418 else if (tok[0] > 0 && (isalpha(tok[0]) || tok[0] == '_' || tok[0] == '#'))
420 int def_attr = A_NORMAL;
421 bool isf=true;
422 if (tok[0] == '#')
423 def_attr = SYNTAX_STRINGVAR;
424 while (toklen > 0)
426 // divide up by .s, if any, unless we're a string
427 int this_len=def_attr == SYNTAX_STRINGVAR ? toklen : 0;
428 while (this_len < toklen && tok[this_len] != '.') this_len++;
429 if (this_len > 0)
431 int attr=isf?namedTokenHighlight(tok,this_len,*c_comment_state):def_attr;
432 if (isf && attr == A_NORMAL)
434 int ntok_len=0, cc = *c_comment_state;
435 const char *pp=lp,*ntok = sh_tokenize(&pp,endptr,&ntok_len,&cc);
436 if (ntok && ntok_len>0 && *ntok == '(') def_attr = attr = SYNTAX_FUNC2;
439 draw_string(&skipcnt,tok,this_len,&last_attr,attr==A_NORMAL?def_attr:attr);
440 tok += this_len;
441 toklen -= this_len;
443 if (toklen > 0)
445 draw_string(&skipcnt,tok,1,&last_attr,SYNTAX_HIGHLIGHT1);
446 tok++;
447 toklen--;
449 isf=false;
451 continue;
453 else if (tok[0] == '.' ||
454 (tok[0] >= '0' && tok[0] <= '9') ||
455 (toklen > 1 && tok[0] == '$' && (tok[1] == 'x' || tok[1]=='X')))
457 attr = SYNTAX_HIGHLIGHT2;
459 int x=1,mode=0;
460 if (tok[0] == '.') mode=1;
461 else if (toklen > 1 && (tok[0] == '$' || tok[0] == '0') && (tok[1] == 'x' || tok[1] == 'X')) { mode=2; x++; }
462 for(;x<toklen;x++)
464 if (tok[x] == '.' && !mode) mode=1;
465 else if (tok[x] < '0' || tok[x] > '9')
467 if (mode != 2 || ((tok[x] < 'a' || tok[x] > 'f') && (tok[x] < 'A' || tok[x] > 'F')))
468 break;
471 if (x<toklen) err_right=toklen-x;
473 else if (tok[0] == '\'' || tok[0] == '\"')
475 start_of_tok = tok[0];
476 attr = SYNTAX_STRING;
478 else if (tok[0] == '$')
480 attr = SYNTAX_HIGHLIGHT2;
482 if (toklen >= 3 && !strnicmp(tok,"$pi",3)) err_right = toklen - 3;
483 else if (toklen >= 2 && !strnicmp(tok,"$e",2)) err_right = toklen - 2;
484 else if (toklen >= 4 && !strnicmp(tok,"$phi",4)) err_right = toklen - 4;
485 else if (toklen == 4 && tok[1] == '\'' && tok[3] == '\'') { }
486 else if (toklen > 1 && tok[1] == '~')
488 int x;
489 for(x=2;x<toklen;x++) if (tok[x] < '0' || tok[x] > '9') break;
490 if (x<toklen) err_right=toklen-x;
492 else err_right = toklen;
494 else if (ignoreSyntaxState==-1 && (tok[0] == '{' || tok[0] == '}'))
496 attr = SYNTAX_HIGHLIGHT1;
498 else if (is_pp)
500 if (tok[0] == '?')
501 last_comment_state = *c_comment_state;
502 if (sh_draw_parentokenstack_update(tok,toklen)) attr = SYNTAX_ERROR;
503 else attr = SYNTAX_KEYWORD;
505 else
507 const char *h="()+*-=/,|&%;!<>?:^!~[]";
508 while (*h && *h != tok[0]) h++;
509 if (*h)
511 if (*c_comment_state != STATE_BEFORE_CODE && sh_draw_parentokenstack_update(tok,toklen))
512 attr = SYNTAX_ERROR;
513 else
514 attr = SYNTAX_HIGHLIGHT1;
516 else
518 err_left=1;
519 if (tok[0] < 0) while (err_left < toklen && tok[err_left]<0) err_left++; // utf-8 skip
523 if (ignoreSyntaxState) err_left = err_right = 0;
525 if (err_left > 0)
527 if (err_left > toklen) err_left=toklen;
528 draw_string(&skipcnt,tok,err_left,&last_attr,SYNTAX_ERROR);
529 tok+=err_left;
530 toklen -= err_left;
532 if (err_right > toklen) err_right=toklen;
534 draw_string(&skipcnt, tok, toklen - err_right, &last_attr, attr, start_of_tok);
536 if (err_right > 0)
537 draw_string(&skipcnt,tok+toklen-err_right,err_right,&last_attr,SYNTAX_ERROR);
539 if (ignoreSyntaxState == -1 && tok[0] == '>')
541 draw_string(&skipcnt,p,strlen(p),&last_attr,ignoreSyntaxState==2 ? SYNTAX_COMMENT : A_NORMAL);
542 break;
545 if (ignoreSyntaxState<0)
547 if (initial_state == STATE_BEFORE_CODE ||
548 ((initial_state & STATE_PREPROC) &&
549 (initial_state & STATE_PREPROC_SINGLELINEMODE))
552 if (*c_comment_state & STATE_PREPROC)
553 *c_comment_state |= STATE_PREPROC_SINGLELINEMODE;
554 else
555 *c_comment_state=STATE_BEFORE_CODE;
558 return 1;
561 int EEL_Editor::GetCommentStateForLineStart(int line)
563 if (m_write_leading_tabs<=0) m_indent_size=2;
565 const bool uses_code_start_lines = !!is_code_start_line(NULL);
566 int state=uses_code_start_lines ? STATE_BEFORE_CODE : 0;
568 s_draw_parentokenstack.Resize(0,false);
570 int state_ret = state;
571 bool need_code = uses_code_start_lines; // we're also searching for tabsize:
572 for (int x=0;x<line || need_code;x++)
574 WDL_FastString *t = m_text.Get(x);
575 if (!t) break;
577 const char *p = t->Get();
578 if (state == STATE_BEFORE_CODE && !strnicmp(p,"tabsize:",8))
580 int a = atoi(p+8);
581 if (a>0 && a < 32) m_indent_size = a;
583 if (!(state & STATE_PREPROC) && uses_code_start_lines && is_code_start_line(p))
585 need_code = false;
586 if (x>=line) break;
587 s_draw_parentokenstack.Resize(0,false);
588 state=0;
590 else
592 const int ll=t?t->GetLength():0;
593 const char *endp = p+ll;
594 int toklen;
595 const char *tok;
596 const int initial_state = state;
597 // to be maximally correct here with the preprocessor here, we need to call overrideSyntaxDrawingForLine()
598 // without drawing to detect special lines, but meh corner cases
599 while (NULL != (tok=sh_tokenize(&p,endp,&toklen,&state))) // eat all tokens, updating state
601 if (initial_state == STATE_BEFORE_CODE && !(state & STATE_PREPROC))
602 state = STATE_BEFORE_CODE;
603 if (x<line && (state != STATE_BEFORE_CODE || (toklen == 2 && is_preproc_token(tok))))
605 sh_func_ontoken(tok,toklen);
606 sh_draw_parentokenstack_update(tok,toklen);
610 if (x<line) state_ret = state;
612 return state_ret;
615 const char *EEL_Editor::sh_tokenize(const char **ptr, const char *endptr, int *lenOut, int *state)
617 int in_preproc = 0, prev_state = state ? *state : 0;
618 if (prev_state & STATE_PREPROC)
620 in_preproc = prev_state & STATE_PREPROC_MASK;
621 *state &= ~STATE_PREPROC_MASK;
623 const char *p = nseel_simple_tokenizer(ptr, endptr, lenOut, state);
624 if (in_preproc) *state |= in_preproc;
626 if (!p || !state) return p;
628 if (in_preproc)
630 if (*lenOut >= 1 && p+1 < endptr && is_preproc_token(p) < 0)
632 *ptr += 2 - *lenOut;
633 *lenOut = 2;
634 *state = STATE_PREPROC_DECODE_OLD(prev_state); // caller will have to be aware and highlight this token correctly
636 else
638 for (int x = 0; x < *lenOut - 1; x ++)
639 if (is_preproc_token(p+x) < 0)
641 int adj = x - *lenOut;
642 *ptr += adj;
643 *lenOut += adj;
644 break;
648 else
650 if (*state == STATE_BEFORE_CODE && *lenOut == 4 && p + 7 < endptr && !strnicmp(p,"http://",7))
652 int nl = 7;
653 while (p+nl < endptr && p[nl] && p[nl] != ' ' && p[nl] != '\t') nl++;
654 *ptr += nl-*lenOut;
655 *lenOut = nl;
658 if (*lenOut >= 1 && p+1 < endptr && is_preproc_token(p) > 0)
660 *ptr += 2 - *lenOut;
661 *lenOut = 2;
662 *state = STATE_PREPROC_ENCODE_OLD(prev_state) | STATE_PREPROC;
664 else
666 for (int x = 0; x < *lenOut - 1; x ++)
667 if (is_preproc_token(p+x) > 0)
669 int adj = x - *lenOut;
670 *ptr += adj;
671 *lenOut += adj;
672 if (p[0] == '"' || p[0] == '\'') *state = p[0];
673 break;
677 return p;
681 bool EEL_Editor::LineCanAffectOtherLines(const char *txt, int spos, int slen) // if multiline comment etc
683 const char *special_start = txt + spos;
684 const char *special_end = txt + spos + slen;
685 while (*txt)
687 if (txt >= special_start-1 && txt < special_end)
689 const char c = txt[0];
690 if (c == '*' && txt[1] == '/') return true;
691 if (c == '/' && (txt[1] == '/' || txt[1] == '*')) return true;
692 if (c == '\\' && (txt[1] == '\"' || txt[1] == '\'')) return true;
693 if (is_preproc_token(txt)) return true;
695 if (txt >= special_start)
697 if (c == '\"' || c == '\'') return true;
698 if (c == '(' || c == '[' || c == ')' || c == ']' || c == ':' || c == ';' || c == '?') return true;
701 txt++;
703 return false;
707 struct eel_sh_token
709 int line, col, end_col;
710 unsigned int data; // packed char for token type, plus 24 bits for linecnt (0=single line, 1=extra line, etc)
712 eel_sh_token(int _line, int _col, int toklen, unsigned char c)
714 line = _line;
715 col = _col;
716 end_col = col + toklen;
717 data = c;
719 ~eel_sh_token() { }
721 void add_linecnt(int endcol) { data += 256; end_col = endcol; }
722 int get_linecnt() const { return (data >> 8); }
723 char get_c() const { return (char) (data & 255); }
725 bool is_comment() const {
726 return get_c() == '/' && (get_linecnt() || end_col>col+1);
730 static int eel_sh_get_token_for_pos(const WDL_TypedBuf<eel_sh_token> *toklist, int line, int col, bool *is_after)
732 const int sz = toklist->GetSize();
733 int x;
734 for (x=0; x < sz; x ++)
736 const eel_sh_token *tok = toklist->Get()+x;
737 const int first_line = tok->line;
738 const int last_line = first_line+tok->get_linecnt(); // last affected line (usually same as first)
740 if (last_line >= line) // if this token doesn't end before the line we care about
742 // check to see if the token starts after our position
743 if (first_line > line || (first_line == line && tok->col > col)) break;
745 // token started before line/col, see if it ends after line/col
746 if (last_line > line || tok->end_col > col)
748 // direct hit
749 *is_after = false;
750 return x;
754 *is_after = true;
755 return x-1;
758 static void eel_sh_generate_token_list(const WDL_PtrList<WDL_FastString> *lines, WDL_TypedBuf<eel_sh_token> *toklist, int start_line, EEL_Editor *editor)
760 toklist->Resize(0,false);
761 int state=0;
762 int l;
763 int end_line = lines->GetSize();
764 if (editor->is_code_start_line(NULL))
766 for (l = start_line; l < end_line; l ++)
768 WDL_FastString *s = lines->Get(l);
769 if (s && editor->is_code_start_line(s->Get()))
771 end_line = l;
772 break;
775 for (; start_line >= 0; start_line--)
777 WDL_FastString *s = lines->Get(start_line);
778 if (s && editor->is_code_start_line(s->Get())) break;
780 if (start_line < 0) return; // before any code
782 start_line++;
784 else
786 start_line = 0;
790 int last_state_save = 0;
791 for (l=start_line;l<end_line;l++)
793 WDL_FastString *t = lines->Get(l);
794 const int ll = t?t->GetLength():0;
795 const char *start_p = t?t->Get():"";
796 const char *p = start_p;
797 const char *endp = start_p+ll;
799 const char *tok;
800 int last_state=state & ~STATE_PREPROC;
801 int toklen;
802 while (NULL != (tok=editor->sh_tokenize(&p,endp,&toklen,&state))||last_state)
804 if (tok && toklen == 2 && is_preproc_token(tok) > 0)
806 bool ok = true;
807 for (int x = toklist->GetSize()-1; x>=0; x--)
809 char c = toklist->Get()[x].get_c();
810 if (c == 'p') break; // ?>
811 if (c == 'P') { ok = false; break; } // <?
813 if (WDL_NORMALLY(ok))
815 eel_sh_token t(l,(int)(tok-start_p),2,'P');
816 toklist->Add(t);
817 last_state_save = STATE_PREPROC_DECODE_OLD(state);
818 last_state = 0;
819 continue;
822 if (tok && toklen == 2 && is_preproc_token(tok) < 0)
824 bool ok = false;
825 for (int x = toklist->GetSize()-1; x>=0; x--)
827 char c = toklist->Get()[x].get_c();
828 if (c == 'p') break; // ?>
829 if (c == 'P') { ok = true; break; } // <?
831 if (WDL_NORMALLY(ok))
833 eel_sh_token t(l,(int)(tok-start_p),2,'p');
834 toklist->Add(t);
835 last_state = last_state_save;
836 last_state_save = 0;
837 continue;
840 if (last_state == '\'' || last_state == '"' || last_state==1)
842 const int sz=toklist->GetSize();
843 // update last token to include this data
844 if (sz)
846 char c = last_state == 1 ? '/' : last_state;
847 if (toklist->Get()[sz-1].get_c() == c)
848 toklist->Get()[sz-1].add_linecnt((int) ((tok ? p:endp) - start_p));
849 else if (tok)
851 // usually indicates continuation after a preprocessor block
852 eel_sh_token t(l,(int)(tok-start_p),toklen,c);
853 toklist->Add(t);
857 else
859 if (tok) switch (tok[0])
861 case '{':
862 case '}':
863 case '?':
864 case ':':
865 case ';':
866 case '(':
867 case '[':
868 case ')':
869 case ']':
870 case '\'':
871 case '"':
872 case '/': // comment
874 eel_sh_token t(l,(int)(tok-start_p),toklen,tok[0]);
875 toklist->Add(t);
877 break;
880 last_state=0;
885 static bool eel_sh_get_matching_pos_for_pos(WDL_PtrList<WDL_FastString> *text, int curx, int cury, int *newx, int *newy, const char **errmsg, EEL_Editor *editor)
887 static WDL_TypedBuf<eel_sh_token> toklist;
888 eel_sh_generate_token_list(text,&toklist, cury,editor);
889 bool is_after;
890 const int hit_tokidx = eel_sh_get_token_for_pos(&toklist, cury, curx, &is_after);
891 const eel_sh_token *hit_tok = hit_tokidx >= 0 ? toklist.Get() + hit_tokidx : NULL;
893 if (!is_after && hit_tok && (hit_tok->get_c() == '"' || hit_tok->get_c() == '\'' || hit_tok->is_comment()))
895 eel_sh_token tok = *hit_tok; // save a copy, toklist might get destroyed recursively here
896 hit_tok = &tok;
898 //if (tok.get_c() == '"')
900 // the user could be editing code in code, tokenize it and see if we can make sense of it
901 WDL_FastString start, end;
902 WDL_PtrList<WDL_FastString> tmplist;
903 WDL_FastString *s = text->Get(tok.line);
904 if (s && s->GetLength() > tok.col+1)
906 int maxl = tok.get_linecnt()>0 ? 0 : tok.end_col - tok.col - 2;
907 start.Set(s->Get() + tok.col+1, maxl);
909 tmplist.Add(&start);
910 const int linecnt = tok.get_linecnt();
911 if (linecnt>0)
913 for (int a=1; a < linecnt; a ++)
915 s = text->Get(tok.line + a);
916 if (s) tmplist.Add(s);
918 s = text->Get(tok.line + linecnt);
919 if (s)
921 if (tok.end_col>1) end.Set(s->Get(), tok.end_col-1);
922 tmplist.Add(&end);
926 int lx = curx, ly = cury - tok.line;
927 if (cury == tok.line) lx -= (tok.col+1);
929 // this will destroy the token
930 if (eel_sh_get_matching_pos_for_pos(&tmplist, lx, ly, newx, newy, errmsg, editor))
932 *newy += tok.line;
933 if (cury == tok.line) *newx += tok.col + 1;
934 return true;
938 // if within a string or comment, move to start, unless already at start, move to end
939 if (cury == hit_tok->line && curx == hit_tok->col)
941 *newx=hit_tok->end_col-1;
942 *newy=hit_tok->line + hit_tok->get_linecnt();
944 else
946 *newx=hit_tok->col;
947 *newy=hit_tok->line;
949 return true;
952 if (!hit_tok) return false;
954 const int toksz=toklist.GetSize();
955 int tokpos = hit_tokidx;
956 int pc1=0,pc2=0; // (, [ count
957 int pc3=0; // : or ? count depending on mode
958 int dir=-1, mode=0; // default to scan to previous [( or <?
959 if (!is_after)
961 switch (hit_tok->get_c())
963 case '(': mode=1; dir=1; break;
964 case '[': mode=2; dir=1; break;
965 case ')': mode=3; dir=-1; break;
966 case ']': mode=4; dir=-1; break;
967 case '?': mode=5; dir=1; break;
968 case ':': mode=6; break;
969 case ';': mode=7; break;
970 case 'P': mode=8; dir=1; break;
971 case 'p': mode=9; dir=-1; break;
973 // if hit a token, exclude this token from scanning
974 tokpos += dir;
977 while (tokpos>=0 && tokpos<toksz)
979 const eel_sh_token *tok = toklist.Get() + tokpos;
980 const char this_c = tok->get_c();
981 if (mode != 8 && mode != 9)
983 if (dir < 0)
985 if (this_c == 'P') // <?
987 if (mode == 0)
989 *newx=tok->col;
990 *newy=tok->line;
991 return true;
993 break;
995 if (this_c == 'p')
997 while (--tokpos >= 0 && toklist.Get()[tokpos].get_c() != 'P');
998 tokpos--;
999 continue;
1002 else
1004 if (this_c == 'p') break; // ?>
1005 if (this_c == 'P')
1007 while (++tokpos < toksz && toklist.Get()[tokpos].get_c() != 'p');
1008 tokpos++;
1009 continue;
1013 if ((!pc1 && !pc2) || mode == 8 || mode == 9)
1015 bool match=false, want_abort=false;
1016 switch (mode)
1018 case 0: match = this_c == '(' || this_c == '['; break;
1019 case 1: match = this_c == ')'; break;
1020 case 2: match = this_c == ']'; break;
1021 case 3: match = this_c == '('; break;
1022 case 4: match = this_c == '['; break;
1023 case 5:
1024 // scan forward to nearest : or ;
1025 if (this_c == '?') pc3++;
1026 else if (this_c == ':')
1028 if (pc3>0) pc3--;
1029 else match=true;
1031 else if (this_c == ';') match=true;
1032 else if (this_c == ')' || this_c == ']')
1034 want_abort=true; // if you have "(x<y?z)", don't match for the ?
1036 break;
1037 case 6: // scanning back from : to ?, if any
1038 case 7: // semicolon searches same as colon, effectively
1039 if (this_c == ':') pc3++;
1040 else if (this_c == '?')
1042 if (pc3>0) pc3--;
1043 else match = true;
1045 else if (this_c == ';' || this_c == '(' || this_c == '[')
1047 want_abort=true;
1049 break;
1050 case 8: match = this_c == 'p'; break;
1051 case 9: match = this_c == 'P'; break;
1054 if (want_abort) break;
1055 if (match)
1057 *newx=tok->col;
1058 *newy=tok->line;
1059 return true;
1062 switch (this_c)
1064 case '[': pc2++; break;
1065 case ']': pc2--; break;
1066 case '(': pc1++; break;
1067 case ')': pc1--; break;
1069 tokpos+=dir;
1072 if (errmsg)
1074 if (!mode) *errmsg = "Could not find previous [ or (";
1075 else if (mode == 1) *errmsg = "Could not find matching )";
1076 else if (mode == 2) *errmsg = "Could not find matching ]";
1077 else if (mode == 3) *errmsg = "Could not find matching (";
1078 else if (mode == 4) *errmsg = "Could not find matching [";
1079 else if (mode == 5) *errmsg = "Could not find matching : or ; for ?";
1080 else if (mode == 6) *errmsg = "Could not find matching ? for :";
1081 else if (mode == 7) *errmsg = "Could not find matching ? for ;";
1083 return false;
1087 void EEL_Editor::doParenMatching()
1089 WDL_FastString *curstr;
1090 const char *errmsg = "";
1091 if (NULL != (curstr=m_text.Get(m_curs_y)))
1093 int bytex = WDL_utf8_charpos_to_bytepos(curstr->Get(),m_curs_x);
1094 if (bytex >= curstr->GetLength()) bytex=curstr->GetLength()-1;
1095 if (bytex<0) bytex = 0;
1097 int new_x,new_y;
1098 if (eel_sh_get_matching_pos_for_pos(&m_text, bytex,m_curs_y,&new_x,&new_y,&errmsg,this))
1100 curstr = m_text.Get(new_y);
1101 if (curstr) new_x = WDL_utf8_bytepos_to_charpos(curstr->Get(),new_x);
1103 m_curs_x=new_x;
1104 m_curs_y=new_y;
1105 m_want_x=-1;
1106 draw();
1107 setCursor(1);
1109 else if (errmsg[0])
1111 draw_message(errmsg);
1112 setCursor(0);
1117 static int word_len(const char *p)
1119 int l = 0;
1120 if (*p >= 'a' && *p <='z')
1122 l++;
1123 // lowercase word
1124 while (p[l] && p[l] != '_' && p[l] != '.' && !(p[l] >= 'A' && p[l] <='Z')) l++;
1126 else if (*p >= 'A' && *p <= 'Z')
1128 if (!strcmp(p,"MIDI")) return 4;
1129 l++;
1130 if (p[l] >= 'A' && p[l] <='Z') // UPPERCASE word
1131 while (p[l] && p[l] != '_' && p[l] != '.' && !(p[l] >= 'a' && p[l] <='z')) l++;
1132 else // Titlecase word
1133 while (p[l] && p[l] != '_' && p[l] != '.' && !(p[l] >= 'A' && p[l] <='Z')) l++;
1135 return l;
1138 static int search_str_partial(const char *str, int reflen, const char *word, int wordlen)
1140 // find the longest leading segment of word in str
1141 int best_len = 0;
1142 for (int y = 0; y < reflen; y ++)
1144 while (y < reflen && !strnicmp(str+y,word,best_len+1))
1146 if (++best_len >= wordlen) return best_len;
1147 reflen--;
1150 return best_len;
1153 static int fuzzy_match2(const char *codestr, int codelen, const char *refstr, int reflen)
1155 // codestr is user-typed, refstr is the reference function name
1156 // our APIs are gfx_blah_blah or TrackBlahBlah so breaking the API up by words
1157 // and searching the user-entered code should be effective
1158 int lendiff = reflen - codelen;
1159 if (lendiff < 0) lendiff = -lendiff;
1161 const char *word = refstr;
1162 int score = 0;
1163 for (;;)
1165 while (*word == '_' || *word == '.') word++;
1166 const int wordlen = word_len(word);
1167 if (!wordlen) break;
1168 char word_buf[128];
1169 lstrcpyn_safe(word_buf,word,wordlen+1);
1171 if (WDL_stristr(refstr,word_buf)==word)
1173 int max_match_len = search_str_partial(codestr,codelen,word,wordlen);
1174 if (max_match_len < 2 && wordlen == 5 && !strnicmp(word,"Count",5))
1176 max_match_len = search_str_partial(codestr,codelen,"Num",3);
1178 if (max_match_len > (wordlen <= 2 ? 1 : 2))
1179 score += max_match_len;
1181 word += wordlen;
1184 if (!score) return 0;
1186 return score * 1000 + 1000 - wdl_clamp(lendiff*2,0,200);
1189 int EEL_Editor::fuzzy_match(const char *codestr, const char *refstr)
1191 const int codestr_len = (int)strlen(codestr), refstr_len = (int)strlen(refstr);
1192 if (refstr_len >= codestr_len && !strnicmp(codestr,refstr,codestr_len)) return 1000000000;
1193 int score1 = fuzzy_match2(refstr,refstr_len,codestr,codestr_len);
1194 int score2 = fuzzy_match2(codestr,codestr_len,refstr,refstr_len);
1195 if (score2 > score1) return score2 | 1;
1196 return score1&~1;
1199 static int eeledit_varenumfunc(const char *name, EEL_F *val, void *ctx)
1201 void **parms = (void **)ctx;
1202 int score = ((EEL_Editor*)parms[2])->fuzzy_match((const char *)parms[1], name);
1203 if (score > 0) ((suggested_matchlist*)parms[0])->add(name,score,suggested_matchlist::MODE_VAR);
1204 return 1;
1207 void EEL_Editor::get_suggested_token_names(const char *fname, int chkmask, suggested_matchlist *list)
1209 int x;
1210 if (chkmask & (KEYWORD_MASK_BUILTIN_FUNC|KEYWORD_MASK_USER_VAR))
1212 peek_lock();
1213 NSEEL_VMCTX vm = peek_get_VM();
1214 compileContext *fvm = vm && peek_want_VM_funcs() ? (compileContext*)vm : NULL;
1215 if (chkmask&KEYWORD_MASK_BUILTIN_FUNC) for (x=0;;x++)
1217 functionType *p = nseel_enumFunctions(fvm,x);
1218 if (!p) break;
1219 int score = fuzzy_match(fname,p->name);
1220 if (score>0) list->add(p->name,score);
1222 if (vm && (chkmask&KEYWORD_MASK_USER_VAR))
1224 const void *parms[3] = { list, fname, this };
1225 NSEEL_VM_enumallvars(vm, eeledit_varenumfunc, parms);
1227 peek_unlock();
1230 if (chkmask & KEYWORD_MASK_USER_FUNC)
1232 ensure_code_func_cache_valid();
1233 for (int x=0;x< m_code_func_cache.GetSize();x++)
1235 const char *k = m_code_func_cache.Get(x) + 4;
1236 if (WDL_NORMALLY(k))
1238 int score = fuzzy_match(fname,k);
1239 if (score > 0) list->add(k,score,suggested_matchlist::MODE_USERFUNC);
1245 int EEL_Editor::peek_get_token_info(const char *name, char *sstr, size_t sstr_sz, int chkmask, int ignoreline)
1247 if (chkmask&KEYWORD_MASK_USER_FUNC)
1249 ensure_code_func_cache_valid();
1250 for (int i = 0; i < m_code_func_cache.GetSize(); i ++)
1252 const char *cacheptr = m_code_func_cache.Get(i);
1253 const char *nameptr = cacheptr + sizeof(int);
1254 if (!(m_case_sensitive ? strcmp(nameptr, name):stricmp(nameptr,name)) &&
1255 *(int *)cacheptr != ignoreline)
1257 const char *parms = nameptr+strlen(nameptr)+1;
1258 const char *trail = parms+strlen(parms)+1;
1259 snprintf(sstr,sstr_sz,"%s%s%s%s",nameptr,parms,*trail?" " :"",trail);
1260 return 4;
1265 if ((chkmask & KEYWORD_MASK_USER_VAR) && name[0] == '#')
1267 char tmp[256];
1268 int v = peek_get_named_string_value(name+1,tmp,sizeof(tmp));
1269 if (v>=0)
1271 snprintf(sstr,sstr_sz,"%s(%d)=\"%s\"",name,v,tmp);
1272 return KEYWORD_MASK_USER_VAR;
1276 if (chkmask & (KEYWORD_MASK_BUILTIN_FUNC|KEYWORD_MASK_USER_VAR))
1278 int rv = 0;
1279 peek_lock();
1280 NSEEL_VMCTX vm = peek_want_VM_funcs() ? peek_get_VM() : NULL;
1281 functionType *f = (chkmask&KEYWORD_MASK_BUILTIN_FUNC) ? nseel_getFunctionByName((compileContext*)vm,name,NULL) : NULL;
1282 double v;
1283 if (f)
1285 snprintf(sstr,sstr_sz,"'%s' is a function that requires %d parameters", f->name,f->nParams&0xff);
1286 rv = KEYWORD_MASK_BUILTIN_FUNC;
1288 else if (chkmask & KEYWORD_MASK_USER_VAR)
1290 if (!vm) vm = peek_get_VM();
1291 EEL_F *vptr=NSEEL_VM_getvar(vm,name);
1292 if (vptr)
1294 v = *vptr;
1295 rv = KEYWORD_MASK_USER_VAR;
1298 peek_unlock();
1300 if (rv == KEYWORD_MASK_USER_VAR)
1302 int good_len=-1;
1303 snprintf(sstr,sstr_sz,"%s=%.14f",name,v);
1304 WDL_remove_trailing_decimal_zeros(sstr,2);
1306 if (v > -1.0 && v < NSEEL_RAM_ITEMSPERBLOCK*NSEEL_RAM_BLOCKS)
1308 const unsigned int w = (unsigned int) (v+NSEEL_CLOSEFACTOR);
1309 EEL_F *dv = NSEEL_VM_getramptr_noalloc(vm,w,NULL);
1310 if (dv)
1312 snprintf_append(sstr,sstr_sz," [%d]=%.14f",w,*dv);
1313 WDL_remove_trailing_decimal_zeros(sstr,2);
1315 else
1317 good_len = strlen(sstr);
1318 snprintf_append(sstr,sstr_sz," [%d]=<0>",w);
1322 char buf[512];
1323 buf[0]=0;
1324 if (peek_get_numbered_string_value(v,buf,sizeof(buf)))
1326 if (good_len>=0) sstr[good_len]=0; // remove [addr]=<uninit> if a string and no ram
1327 snprintf_append(sstr,sstr_sz," #=\"%s\"",buf);
1331 if (rv) return rv;
1334 return 0;
1338 void EEL_Editor::doWatchInfo(int c)
1340 // determine the word we are on, check its value in the effect
1341 char sstr[512], buf[512];
1342 lstrcpyn_safe(sstr,"Use this on a valid symbol name", sizeof(sstr));
1343 WDL_FastString *t=m_text.Get(m_curs_y);
1344 char curChar=0;
1345 if (t)
1347 const char *p=t->Get();
1348 const int bytex = WDL_utf8_charpos_to_bytepos(p,m_curs_x);
1349 if (bytex >= 0 && bytex < t->GetLength()) curChar = p[bytex];
1350 if (c != KEY_F1 && (m_selecting ||
1351 curChar == '(' ||
1352 curChar == '[' ||
1353 curChar == ')' ||
1354 curChar == ']'
1357 WDL_FastString code;
1358 int miny,maxy,minx,maxx;
1359 bool ok = false;
1360 if (!m_selecting)
1362 if (eel_sh_get_matching_pos_for_pos(&m_text,minx=m_curs_x,miny=m_curs_y,&maxx, &maxy,NULL,this))
1364 if (maxy==miny)
1366 if (maxx < minx)
1368 int tmp = minx;
1369 minx=maxx;
1370 maxx=tmp;
1373 else if (maxy < miny)
1375 int tmp=maxy;
1376 maxy=miny;
1377 miny=tmp;
1378 tmp = minx;
1379 minx=maxx;
1380 maxx=tmp;
1382 ok = true;
1383 minx++; // skip leading (
1386 else
1388 ok=true;
1389 getselectregion(minx,miny,maxx,maxy);
1390 WDL_FastString *s;
1391 s = m_text.Get(miny);
1392 if (s) minx = WDL_utf8_charpos_to_bytepos(s->Get(),minx);
1393 s = m_text.Get(maxy);
1394 if (s) maxx = WDL_utf8_charpos_to_bytepos(s->Get(),maxx);
1397 if (ok)
1399 int x;
1400 for (x = miny; x <= maxy; x ++)
1402 WDL_FastString *s=m_text.Get(x);
1403 if (s)
1405 const char *str=s->Get();
1406 int sx,ex;
1407 if (x == miny) sx=wdl_max(minx,0);
1408 else sx=0;
1409 int tmp=s->GetLength();
1410 if (sx > tmp) sx=tmp;
1412 if (x == maxy) ex=wdl_min(maxx,tmp);
1413 else ex=tmp;
1415 if (code.GetLength()) code.Append("\r\n");
1416 code.Append(ex-sx?str+sx:"",ex-sx);
1420 if (code.Get()[0])
1422 if (m_selecting && (GetAsyncKeyState(VK_SHIFT)&0x8000))
1424 peek_lock();
1425 NSEEL_CODEHANDLE ch;
1426 NSEEL_VMCTX vm = peek_get_VM();
1428 if (vm && (ch = NSEEL_code_compile_ex(vm,code.Get(),1,0)))
1430 codeHandleType *p = (codeHandleType*)ch;
1431 code.Ellipsize(3,20);
1432 const char *errstr = "failed writing to";
1433 if (p->code)
1435 buf[0]=0;
1436 GetTempPath(sizeof(buf)-64,buf);
1437 lstrcatn(buf,"jsfx-out",sizeof(buf));
1438 FILE *fp = fopen(buf,"wb");
1439 if (fp)
1441 errstr="wrote to";
1442 fwrite(p->code,1,p->code_size,fp);
1443 fclose(fp);
1446 snprintf(sstr,sizeof(sstr),"Expression '%s' compiled to %d bytes, %s temp/jsfx-out",code.Get(),p->code_size, errstr);
1447 NSEEL_code_free(ch);
1449 else
1451 code.Ellipsize(3,20);
1452 snprintf(sstr,sizeof(sstr),"Expression '%s' could not compile",code.Get());
1454 peek_unlock();
1456 else
1458 WDL_FastString code2;
1459 code2.Set("__debug_watch_value = (((((");
1460 code2.Append(code.Get());
1461 code2.Append(")))));");
1463 peek_lock();
1465 NSEEL_VMCTX vm = peek_get_VM();
1467 EEL_F *vptr=NULL;
1468 double v=0.0;
1469 const char *err="Invalid context";
1470 if (vm)
1472 NSEEL_CODEHANDLE ch = NSEEL_code_compile_ex(vm,code2.Get(),1,0);
1473 if (!ch) err = "Error parsing";
1474 else
1476 NSEEL_code_execute(ch);
1477 NSEEL_code_free(ch);
1478 vptr = NSEEL_VM_getvar(vm,"__debug_watch_value");
1479 if (vptr) v = *vptr;
1483 peek_unlock();
1486 // remove whitespace from code for display
1487 int x;
1488 bool lb=true;
1489 for (x=0;x<code.GetLength();x++)
1491 if (code.Get()[x]>0 && isspace(code.Get()[x]))
1493 if (lb) code.DeleteSub(x--,1);
1494 lb=true;
1496 else
1498 lb=false;
1501 if (lb && code.GetLength()>0) code.SetLen(code.GetLength()-1);
1504 code.Ellipsize(3,20);
1505 if (vptr)
1507 snprintf(sstr,sizeof(sstr),"Expression '%s' evaluates to %.14f",code.Get(),v);
1509 else
1511 snprintf(sstr,sizeof(sstr),"Error evaluating '%s': %s",code.Get(),err?err:"Unknown error");
1515 // compile+execute code within () as debug_watch_value = ( code )
1516 // show value (or err msg)
1518 else if (curChar>0 && (isalnum(curChar) || curChar == '_' || curChar == '.' || curChar == '#'))
1520 const int bytex = WDL_utf8_charpos_to_bytepos(p,m_curs_x);
1521 const char *lp=p+bytex;
1522 const char *rp=lp + WDL_utf8_charpos_to_bytepos(lp,1);
1523 while (lp >= p && *lp > 0 && (isalnum(*lp) || *lp == '_' || (*lp == '.' && (lp==p || lp[-1]!='.')))) lp--;
1524 if (lp < p || *lp != '#') lp++;
1525 while (*rp && *rp > 0 && (isalnum(*rp) || *rp == '_' || (*rp == '.' && rp[1] != '.'))) rp++;
1527 if (*lp == '#' && rp > lp+1)
1529 WDL_FastString n;
1530 lp++;
1531 n.Set(lp,(int)(rp-lp));
1532 int idx=peek_get_named_string_value(n.Get(),buf,sizeof(buf));
1533 if (idx>=0)
1535 snprintf(sstr,sizeof(sstr),"#%s(%d)=%s",n.Get(),idx,buf);
1536 lp="";
1539 if (*lp > 0 && (isalpha(*lp) || *lp == '_') && rp > lp)
1541 WDL_FastString n;
1542 n.Set(lp,(int)(rp-lp));
1544 if (c==KEY_F1)
1546 if (m_suggestion.GetLength())
1547 goto help_from_sug;
1548 on_help(n.Get(),0);
1549 return;
1552 int f = peek_get_token_info(n.Get(),sstr,sizeof(sstr),~0,-1);
1553 if (!f) snprintf(sstr,sizeof(sstr),"'%s' NOT FOUND",n.Get());
1557 if (c==KEY_F1)
1559 help_from_sug:
1560 WDL_FastString t;
1561 if (m_suggestion.GetLength())
1563 const char *p = m_suggestion.Get();
1564 int l;
1565 for (l = 0; isalnum(p[l]) || p[l] == '_' || p[l] == '.'; l ++);
1566 if (l>0) t.Set(m_suggestion.Get(),l);
1568 on_help(t.GetLength() > 2 ? t.Get() : NULL,(int)curChar);
1569 return;
1572 setCursor();
1573 draw_message(sstr);
1577 void EEL_Editor::draw_bottom_line()
1579 #define BOLD(x) { attrset(COLOR_BOTTOMLINE|A_BOLD); addstr(x); attrset(COLOR_BOTTOMLINE&~A_BOLD); }
1580 addstr("ma"); BOLD("T"); addstr("ch");
1581 BOLD(" S"); addstr("ave");
1582 if (peek_get_VM())
1584 addstr(" pee"); BOLD("K");
1586 if (GetTabCount()>1)
1588 addstr(" | tab: ");
1589 BOLD("[], F?"); addstr("=switch ");
1590 BOLD("W"); addstr("=close");
1592 #undef BOLD
1595 #define CTRL_KEY_DOWN (GetAsyncKeyState(VK_CONTROL)&0x8000)
1596 #define SHIFT_KEY_DOWN (GetAsyncKeyState(VK_SHIFT)&0x8000)
1597 #define ALT_KEY_DOWN (GetAsyncKeyState(VK_MENU)&0x8000)
1599 #ifndef WM_MOUSEWHEEL
1600 #define WM_MOUSEWHEEL 0x20A
1601 #endif
1603 static const char *suggestion_help_text = "(up/down to select, tab to insert)";
1604 static const char *suggestion_help_text2 = "(tab or return to insert)";
1605 static LRESULT WINAPI suggestionProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1607 #ifdef _WIN32
1609 static int Scroll_Message;
1610 if (!Scroll_Message)
1612 Scroll_Message = (int)RegisterWindowMessage("MSWHEEL_ROLLMSG");
1613 if (!Scroll_Message) Scroll_Message=-1;
1615 if (Scroll_Message > 0 && uMsg == (UINT)Scroll_Message)
1617 uMsg=WM_MOUSEWHEEL;
1618 wParam<<=16;
1620 #endif
1622 EEL_Editor *editor;
1623 switch (uMsg)
1625 case WM_DESTROY:
1626 editor = (EEL_Editor *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
1627 if (editor) editor->m_suggestion_hwnd = NULL;
1628 break;
1629 case WM_MOUSEWHEEL:
1631 editor = (EEL_Editor *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
1632 if (editor && editor->m_cursesCtx)
1634 win32CursesCtx *ctx = (win32CursesCtx *)editor->m_cursesCtx;
1635 int a = (int)(short)HIWORD(wParam);
1636 if (a < 0) { a /= 120; if (a==0) a--; }
1637 else if (a>0) { a /= 120; if (a==0) a++; }
1638 else return 1;
1640 RECT r;
1641 GetClientRect(hwnd,&r);
1642 const int fonth = wdl_max(ctx->m_font_h,1);
1643 const int maxv = wdl_max(r.bottom / fonth - 1,1);
1644 const int ni = editor->m_suggestion_list.get_size();
1645 if (ni > maxv)
1647 const int nv = wdl_clamp(editor->m_suggestion_hwnd_scroll - a, 0, ni - maxv);
1648 if (nv != editor->m_suggestion_hwnd_scroll)
1650 editor->m_suggestion_hwnd_scroll = nv;
1651 editor->m_suggestion_hwnd_sel = wdl_clamp(editor->m_suggestion_hwnd_sel,nv,nv+maxv-1);
1652 InvalidateRect(hwnd,NULL,FALSE);
1657 return 1;
1658 case WM_LBUTTONDOWN:
1659 case WM_MOUSEMOVE:
1660 editor = (EEL_Editor *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
1661 if (editor)
1663 win32CursesCtx *ctx = (win32CursesCtx *)editor->m_cursesCtx;
1664 if (ctx && ctx->m_font_h)
1666 HWND par = GetParent(hwnd);
1667 if (uMsg == WM_MOUSEMOVE)
1669 HWND foc = GetFocus();
1670 if (!foc || (foc != hwnd && foc != par)) return 0;
1673 RECT r;
1674 GetClientRect(hwnd,&r);
1675 SetForegroundWindow(par);
1676 SetFocus(par);
1678 const int max_vis = r.bottom / ctx->m_font_h - 1, sel = editor->m_suggestion_hwnd_sel;
1679 int hit = GET_Y_LPARAM(lParam) / ctx->m_font_h;
1680 if (hit >= max_vis) return 0;
1681 hit += editor->m_suggestion_hwnd_scroll;
1683 if (uMsg == WM_LBUTTONDOWN && !SHIFT_KEY_DOWN && !ALT_KEY_DOWN && !CTRL_KEY_DOWN)
1685 editor->m_suggestion_hwnd_sel = hit;
1686 editor->onChar('\t');
1688 else if (sel != hit)
1690 POINT pt;
1691 GetCursorPos(&pt);
1692 if (wdl_abs(pt.x - editor->m_suggestion_hwnd_initmousepos.x) +
1693 wdl_abs(pt.y - editor->m_suggestion_hwnd_initmousepos.y) < ctx->m_font_h*3/4)
1695 return 0;
1697 editor->m_suggestion_hwnd_initmousepos.x = pt.x + 0x10000000;
1699 editor->m_suggestion_hwnd_sel = hit;
1700 InvalidateRect(hwnd,NULL,FALSE);
1702 char sug[512];
1703 sug[0]=0;
1704 const char *p = editor->m_suggestion_list.get(hit);
1705 if (p && editor->peek_get_token_info(p,sug,sizeof(sug),~0,-1))
1707 editor->m_suggestion.Set(sug);
1708 editor->draw_top_line();
1709 editor->setCursor();
1714 return 0;
1715 case WM_PAINT:
1716 editor = (EEL_Editor *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
1717 if (editor)
1719 PAINTSTRUCT ps;
1720 if (BeginPaint(hwnd,&ps))
1722 RECT r;
1723 GetClientRect(hwnd,&r);
1725 win32CursesCtx *ctx = (win32CursesCtx *)editor->m_cursesCtx;
1726 HBRUSH br = CreateSolidBrush(ctx->colortab[WDL_CursesEditor::COLOR_TOPLINE][1]);
1727 FillRect(ps.hdc,&r,br);
1728 DeleteObject(br);
1730 suggested_matchlist *ml = &editor->m_suggestion_list;
1732 HGDIOBJ oldObj = SelectObject(ps.hdc,ctx->mOurFont);
1733 SetBkMode(ps.hdc,TRANSPARENT);
1735 const int fonth = wdl_max(ctx->m_font_h,1);
1736 const int maxv = wdl_max(r.bottom / fonth - 1,1);
1737 const int selpos = wdl_max(editor->m_suggestion_hwnd_sel,0);
1738 int ypos = 0;
1740 editor->m_suggestion_hwnd_scroll = wdl_clamp(editor->m_suggestion_hwnd_scroll, selpos-maxv + 1,selpos);
1741 if (editor->m_suggestion_hwnd_scroll < 0)
1742 editor->m_suggestion_hwnd_scroll = 0;
1744 const bool show_scores = (GetAsyncKeyState(VK_SHIFT)&0x8000) && (GetAsyncKeyState(VK_CONTROL)&0x8000) && (GetAsyncKeyState(VK_MENU)&0x8000);
1746 for (int x = editor->m_suggestion_hwnd_scroll; x < ml->get_size() && ypos <= r.bottom-fonth*2; x ++)
1748 int mode, score;
1749 const char *p = ml->get(x,&mode,&score);
1750 if (WDL_NORMALLY(p))
1752 const bool sel = x == selpos;
1753 SetTextColor(ps.hdc,ctx->colortab[
1754 (mode == suggested_matchlist::MODE_VAR ? WDL_CursesEditor::COLOR_TOPLINE :
1755 mode == suggested_matchlist::MODE_USERFUNC ? WDL_CursesEditor::SYNTAX_FUNC2 :
1756 mode == suggested_matchlist::MODE_REGVAR ? WDL_CursesEditor::SYNTAX_REGVAR :
1757 WDL_CursesEditor::SYNTAX_KEYWORD)
1758 | (sel ? A_BOLD:0)][0]);
1759 RECT tr = {4, ypos, r.right-4, ypos+fonth };
1760 DrawTextUTF8(ps.hdc,p,-1,&tr,DT_SINGLELINE|DT_NOPREFIX|DT_TOP|DT_LEFT);
1761 if (show_scores)
1763 char tmp[128];
1764 snprintf(tmp,sizeof(tmp),"(%d)",score);
1765 DrawTextUTF8(ps.hdc,tmp,-1,&tr,DT_SINGLELINE|DT_NOPREFIX|DT_TOP|DT_RIGHT);
1768 ypos += fonth;
1771 const COLORREF mix = ((ctx->colortab[WDL_CursesEditor::COLOR_TOPLINE][1]&0xfefefe)>>1) +
1772 ((ctx->colortab[WDL_CursesEditor::COLOR_TOPLINE][0]&0xfefefe)>>1);
1773 SetTextColor(ps.hdc,mix);
1774 RECT tr = {4, r.bottom-fonth, r.right-4, r.bottom };
1775 DrawTextUTF8(ps.hdc,
1776 editor->m_suggestion_hwnd_sel >= 0 ? suggestion_help_text2 : suggestion_help_text,
1777 -1,&tr,DT_SINGLELINE|DT_NOPREFIX|DT_TOP|DT_CENTER);
1779 SelectObject(ps.hdc,oldObj);
1781 EndPaint(hwnd,&ps);
1784 return 0;
1786 return DefWindowProc(hwnd,uMsg,wParam,lParam);
1789 void EEL_Editor::open_import_line()
1791 WDL_FastString *txtstr=m_text.Get(m_curs_y);
1792 const char *txt=txtstr?txtstr->Get():NULL;
1793 char fnp[2048];
1794 if (txt && line_has_openable_file(txt,WDL_utf8_charpos_to_bytepos(txt,m_curs_x),fnp,sizeof(fnp)))
1796 WDL_CursesEditor::OpenFileInTab(fnp);
1800 int EEL_Editor::onChar(int c)
1802 if ((m_ui_state == UI_STATE_NORMAL || m_ui_state == UI_STATE_MESSAGE) &&
1803 (c == 'K'-'A'+1 || c == 'S'-'A'+1 || c == 'R'-'A'+1 || !SHIFT_KEY_DOWN) && !ALT_KEY_DOWN) switch (c)
1805 case KEY_F1:
1806 if (CTRL_KEY_DOWN) break;
1807 case 'K'-'A'+1:
1808 doWatchInfo(c);
1809 return 0;
1810 case 'S'-'A'+1:
1812 WDL_DestroyCheck chk(&destroy_check);
1813 if(updateFile())
1815 if (chk.isOK())
1816 draw_message("Error writing file, changes not saved!");
1818 if (chk.isOK())
1819 setCursorIfVisible();
1821 return 0;
1823 // case 'I': note stupidly we map Ctrl+I to VK_TAB, bleh
1824 case 'R'-'A'+1:
1825 if (!SHIFT_KEY_DOWN) break;
1826 if (!m_selecting)
1828 open_import_line();
1830 return 0;
1831 case KEY_F4:
1832 case 'T'-'A'+1:
1833 doParenMatching();
1834 return 0;
1838 int rv = 0;
1839 int do_sug = (g_eel_editor_max_vis_suggestions > 0 &&
1840 m_ui_state == UI_STATE_NORMAL &&
1841 !m_selecting && m_top_margin > 0 &&
1842 (c == 'L'-'A'+1 || (!CTRL_KEY_DOWN && !ALT_KEY_DOWN))) ? 1 : 0;
1843 bool did_fuzzy = false;
1844 char sug[1024];
1845 sug[0]=0;
1847 if (do_sug)
1849 if (m_suggestion_hwnd)
1851 // insert if tab or shift+enter or (enter if arrow-navigated)
1852 if ((c == '\t' && !SHIFT_KEY_DOWN) ||
1853 (c == '\r' && (m_suggestion_hwnd_sel>=0 || SHIFT_KEY_DOWN)))
1855 char buf[512];
1856 int sug_mode;
1857 const char *ptr = m_suggestion_list.get(wdl_max(m_suggestion_hwnd_sel,0), &sug_mode);
1858 lstrcpyn_safe(buf,ptr?ptr:"",sizeof(buf));
1860 DestroyWindow(m_suggestion_hwnd);
1862 WDL_FastString *l=m_text.Get(m_curs_y);
1863 if (buf[0] && l &&
1864 WDL_NORMALLY(m_suggestion_tokpos>=0 && m_suggestion_tokpos < l->GetLength()) &&
1865 WDL_NORMALLY(m_suggestion_toklen>0) &&
1866 WDL_NORMALLY(m_suggestion_tokpos+m_suggestion_toklen <= l->GetLength()))
1868 preSaveUndoState();
1869 if (sug_mode == suggested_matchlist::MODE_USERFUNC)
1871 const char *tok = l->Get() + m_suggestion_tokpos;
1872 for (int j = m_suggestion_toklen - 1; j >= 0; j --)
1874 if (tok[j] == '.')
1876 const char *be = buf;
1877 while (*be) be++;
1878 while (be > buf && *be != '.') be--;
1879 if (be > buf)
1881 // buf is foo.bar, lead_sz=3, if j>=3 check for common prefix
1882 const int lead_sz = (int) (be-buf);
1883 if (j == lead_sz || (j > lead_sz && tok[j-lead_sz-1] == '.'))
1885 // prefixes match, ignore
1886 if (!strnicmp(buf, tok + j - lead_sz, lead_sz+1)) continue;
1890 m_suggestion_tokpos += j+1;
1891 m_suggestion_toklen -= j+1;
1892 break;
1896 l->DeleteSub(m_suggestion_tokpos,m_suggestion_toklen);
1897 l->Insert(buf,m_suggestion_tokpos);
1898 int pos = m_suggestion_tokpos + strlen(buf);
1899 if (sug_mode == suggested_matchlist::MODE_FUNC || sug_mode == suggested_matchlist::MODE_USERFUNC)
1901 if (pos >= l->GetLength() || l->Get()[pos] != '(')
1902 l->Insert("(",pos++);
1904 m_curs_x = WDL_utf8_bytepos_to_charpos(l->Get(),pos);
1905 draw();
1906 saveUndoState();
1907 setCursor();
1908 goto run_suggest;
1910 return 0;
1912 if ((c == KEY_UP || c==KEY_DOWN || c == KEY_NPAGE || c == KEY_PPAGE) && !SHIFT_KEY_DOWN)
1914 m_suggestion_hwnd_sel = wdl_max(m_suggestion_hwnd_sel,0) +
1915 (c==KEY_UP ? -1 : c==KEY_DOWN ? 1 : c==KEY_NPAGE ? 4 : -4);
1917 if (m_suggestion_hwnd_sel >= m_suggestion_list.get_size())
1918 m_suggestion_hwnd_sel = m_suggestion_list.get_size()-1;
1919 if (m_suggestion_hwnd_sel < 0)
1920 m_suggestion_hwnd_sel=0;
1922 InvalidateRect(m_suggestion_hwnd,NULL,FALSE);
1924 const char *p = m_suggestion_list.get(m_suggestion_hwnd_sel);
1925 if (p) peek_get_token_info(p,sug,sizeof(sug),~0,m_curs_y);
1926 goto finish_sug;
1930 if (c==27 ||
1931 c=='L'-'A'+1 ||
1932 c=='\r' ||
1933 c=='\t' ||
1934 (c>=KEY_DOWN && c<= KEY_F12 && c!=KEY_DC)) do_sug = 2; // no fuzzy window
1935 else if (c=='\b' && !m_suggestion_hwnd) do_sug=2; // backspace will update but won't show suggestions
1938 rv = WDL_CursesEditor::onChar(c);
1940 if (!CURSES_INSTANCE)
1941 return rv;
1943 run_suggest:
1944 if (do_sug)
1946 WDL_FastString *l=m_text.Get(m_curs_y);
1947 if (l)
1949 const int MAX_TOK=512;
1950 struct {
1951 const char *tok;
1952 int toklen;
1953 } token_list[MAX_TOK];
1955 const char *cursor = l->Get() + WDL_utf8_charpos_to_bytepos(l->Get(),m_curs_x);
1956 int ntok = 0;
1958 const char *p = l->Get(), *endp = p + l->GetLength();
1959 int state = m_suggestion_curline_comment_state, toklen = 0, bcnt = 0, pcnt = 0;
1960 const char *tok;
1961 // if no parens/brackets are open, use a peekable token that starts at cursor
1962 while ((tok=sh_tokenize(&p,endp,&toklen,&state)) && cursor > tok-(!pcnt && !bcnt && (tok[0] < 0 || tok[0] == '_' || isalpha(tok[0]) || tok[0] == '#')))
1964 if (!state)
1966 if (WDL_NOT_NORMALLY(ntok >= MAX_TOK))
1968 memmove(token_list,token_list+1,sizeof(token_list) - sizeof(token_list[0]));
1969 ntok--;
1971 switch (*tok)
1973 case '[': bcnt++; break;
1974 case ']': bcnt--; break;
1975 case '(': pcnt++; break;
1976 case ')': pcnt--; break;
1978 token_list[ntok].tok = tok;
1979 token_list[ntok].toklen = toklen;
1980 ntok++;
1985 int t = ntok;
1986 int comma_cnt = 0;
1987 while (--t >= 0)
1989 const char *tok = token_list[t].tok;
1990 int toklen = token_list[t].toklen;
1991 const int lc = tok[0], ac = lc==')' ? '(' : lc==']' ? '[' : 0;
1992 if (ac)
1994 int cnt=1;
1995 while (--t>=0)
1997 const int c = token_list[t].tok[0];
1998 if (c == lc) cnt++;
1999 else if (c == ac && !--cnt) break;
2001 if (t > 0)
2003 const int c = token_list[t-1].tok[0];
2004 if (c != ',' && c != ')' && c != ']') t--;
2005 continue;
2008 if (t<0) break;
2010 if (tok[0] == ',') comma_cnt++;
2011 else if ((tok[0] < 0 || tok[0] == '_' || isalpha(tok[0]) || tok[0] == '#') &&
2012 (cursor <= tok + toklen || (t < ntok-1 && token_list[t+1].tok[0] == '(')))
2014 // if cursor is within or at end of token, or is a function (followed by open-paren)
2015 char buf[512];
2016 lstrcpyn_safe(buf,tok,wdl_min(toklen+1, sizeof(buf)));
2017 if (peek_get_token_info(buf,sug,sizeof(sug),~0,m_curs_y))
2019 if (comma_cnt > 0)
2021 // hide previous parameters from sug's parameter fields so we know which parameters
2022 // we are (hopefully on)
2023 char *pstart = sug;
2024 while (*pstart && *pstart != '(') pstart++;
2025 if (*pstart == '(') pstart++;
2026 int comma_pos = 0;
2027 char *p = pstart;
2028 while (comma_pos < comma_cnt)
2030 while (*p == ' ') p++;
2031 while (*p && *p != ',' && *p != ')') p++;
2032 if (*p == ')') break;
2033 if (*p) p++;
2034 comma_pos++;
2037 if (*p && *p != ')')
2039 *pstart=0;
2040 lstrcpyn_safe(buf,p,sizeof(buf));
2041 snprintf_append(sug,sizeof(sug), "... %s",buf);
2044 break;
2047 // only use token up to cursor for suggestions
2048 if (cursor >= tok && cursor <= tok+toklen)
2050 toklen = (int) (cursor-tok);
2051 lstrcpyn_safe(buf,tok,wdl_min(toklen+1, sizeof(buf)));
2054 if (c == '\b' && cursor == tok)
2057 else if (do_sug != 2 && t == ntok-1 && toklen >= 3 && cursor <= tok + toklen)
2059 m_suggestion_list.clear();
2060 get_suggested_token_names(buf,~0,&m_suggestion_list);
2062 win32CursesCtx *ctx = (win32CursesCtx *)m_cursesCtx;
2063 if (m_suggestion_list.get_size()>0 &&
2064 WDL_NORMALLY(ctx->m_hwnd) && WDL_NORMALLY(ctx->m_font_w) && WDL_NORMALLY(ctx->m_font_h))
2066 m_suggestion_toklen = toklen;
2067 m_suggestion_tokpos = (int)(tok-l->Get());
2068 m_suggestion_hwnd_sel = -1;
2069 if (!m_suggestion_hwnd)
2071 #ifdef _WIN32
2072 static HINSTANCE last_inst;
2073 const char *classname = "eel_edit_predicto";
2074 HINSTANCE inst = (HINSTANCE)GetWindowLongPtr(ctx->m_hwnd,GWLP_HINSTANCE);
2075 if (inst != last_inst)
2077 last_inst = inst;
2078 WNDCLASS wc={CS_DBLCLKS,suggestionProc,};
2079 wc.lpszClassName=classname;
2080 wc.hInstance=inst;
2081 wc.hCursor=LoadCursor(NULL,IDC_ARROW);
2082 RegisterClass(&wc);
2084 m_suggestion_hwnd = CreateWindowEx(0,classname,"", WS_CHILD, 0,0,0,0, ctx->m_hwnd, NULL, inst, NULL);
2085 #else
2086 m_suggestion_hwnd = CreateDialogParam(NULL,NULL,ctx->m_hwnd, suggestionProc, 0);
2087 #endif
2088 if (m_suggestion_hwnd) SetWindowLongPtr(m_suggestion_hwnd,GWLP_USERDATA,(LPARAM)this);
2090 if (m_suggestion_hwnd)
2092 const int fontw = ctx->m_font_w, fonth = ctx->m_font_h;
2093 int xpos = (WDL_utf8_bytepos_to_charpos(l->Get(),m_suggestion_tokpos) - m_offs_x) * fontw;
2094 RECT par_cr;
2095 GetClientRect(ctx->m_hwnd,&par_cr);
2097 int use_w = (int)strlen(suggestion_help_text);
2098 int use_h = (wdl_min(g_eel_editor_max_vis_suggestions,m_suggestion_list.get_size()) + 1)*fonth;
2099 for (int x = 0; x < m_suggestion_list.get_size(); x ++)
2101 const char *p = m_suggestion_list.get(x);
2102 if (WDL_NORMALLY(p!=NULL))
2104 const int l = (int) strlen(p);
2105 if (l > use_w) use_w=l;
2109 use_w = 8 + use_w * fontw;
2110 if (use_w > par_cr.right - xpos)
2112 xpos = wdl_max(par_cr.right - use_w,0);
2113 use_w = par_cr.right - xpos;
2116 const int cursor_line_y = ctx->m_cursor_y * fonth;
2117 int ypos = cursor_line_y + fonth;
2118 if (ypos + use_h > par_cr.bottom)
2120 if (cursor_line_y-fonth > par_cr.bottom - ypos)
2122 // more room at the top, but enough?
2123 ypos = cursor_line_y - use_h;
2124 if (ypos<fonth)
2126 ypos = fonth;
2127 use_h = cursor_line_y-fonth;
2130 else
2131 use_h = par_cr.bottom - ypos;
2134 SetWindowPos(m_suggestion_hwnd,NULL,xpos,ypos,use_w,use_h, SWP_NOZORDER|SWP_NOACTIVATE);
2135 InvalidateRect(m_suggestion_hwnd,NULL,FALSE);
2136 ShowWindow(m_suggestion_hwnd,SW_SHOWNA);
2138 GetCursorPos(&m_suggestion_hwnd_initmousepos);
2140 did_fuzzy = true;
2141 const char *p = m_suggestion_list.get(wdl_max(m_suggestion_hwnd_sel,0));
2142 if (p && peek_get_token_info(p,sug,sizeof(sug),~0,m_curs_y)) break;
2149 if (!did_fuzzy && m_suggestion_hwnd) DestroyWindow(m_suggestion_hwnd);
2151 finish_sug:
2152 if (strcmp(sug,m_suggestion.Get()) && m_ui_state == UI_STATE_NORMAL)
2154 m_suggestion.Set(sug);
2155 if (sug[0])
2157 m_suggestion_x=m_curs_x;
2158 m_suggestion_y=m_curs_y;
2159 draw_top_line();
2160 setCursor();
2163 if (!sug[0] && m_suggestion_y>=0 && m_ui_state == UI_STATE_NORMAL)
2165 m_suggestion_x=m_suggestion_y=-1;
2166 m_suggestion.Set("");
2167 if (m_top_margin>0) draw_top_line();
2168 else draw();
2169 setCursor();
2172 return rv;
2175 void EEL_Editor::draw_top_line()
2177 if (m_curs_x >= m_suggestion_x && m_curs_y == m_suggestion_y && m_suggestion.GetLength() && m_ui_state == UI_STATE_NORMAL)
2179 const char *p=m_suggestion.Get();
2180 char str[512];
2181 if (WDL_utf8_get_charlen(m_suggestion.Get()) > COLS)
2183 int l = WDL_utf8_charpos_to_bytepos(m_suggestion.Get(),COLS-4);
2184 if (l > sizeof(str)-6) l = sizeof(str)-6;
2185 lstrcpyn(str, m_suggestion.Get(), l+1);
2186 strcat(str, "...");
2187 p=str;
2190 attrset(COLOR_TOPLINE|A_BOLD);
2191 bkgdset(COLOR_TOPLINE);
2192 move(0, 0);
2193 addstr(p);
2194 clrtoeol();
2195 attrset(0);
2196 bkgdset(0);
2198 else
2200 m_suggestion_x=m_suggestion_y=-1;
2201 if (m_suggestion.GetLength()) m_suggestion.Set("");
2202 WDL_CursesEditor::draw_top_line();
2207 void EEL_Editor::onRightClick(HWND hwnd)
2209 WDL_LogicalSortStringKeyedArray<int> flist(m_case_sensitive);
2210 int i;
2211 if (!(GetAsyncKeyState(VK_CONTROL)&0x8000))
2213 m_code_func_cache_lines = -1; // invalidate cache
2214 ensure_code_func_cache_valid();
2215 for (i = 0; i < m_code_func_cache.GetSize(); i ++)
2217 const char *p = m_code_func_cache.Get(i);
2218 const int line = *(int *)p;
2219 p += sizeof(int);
2221 const char *q = p+strlen(p)+1;
2222 char buf[512];
2223 snprintf(buf,sizeof(buf),"%s%s",p,q);
2224 flist.AddUnsorted(buf,line);
2225 p += 4;
2229 get_extra_filepos_names(&flist,0);
2231 if (flist.GetSize()>1)
2233 flist.Resort();
2234 if (m_case_sensitive) flist.Resort(WDL_LogicalSortStringKeyedArray<int>::cmpistr);
2237 get_extra_filepos_names(&flist,1);
2239 if (flist.GetSize())
2241 HMENU hm=CreatePopupMenu();
2242 int pos=0;
2243 for (i=0; i < flist.GetSize(); ++i)
2245 const char* fname=NULL;
2246 int line=flist.Enumerate(i, &fname);
2247 InsertMenu(hm, pos++, MF_STRING|MF_BYPOSITION, line+1, fname);
2249 POINT p;
2250 GetCursorPos(&p);
2251 int ret=TrackPopupMenu(hm, TPM_NONOTIFY|TPM_RETURNCMD, p.x, p.y, 0, hwnd, NULL);
2252 DestroyMenu(hm);
2253 if (ret > 0)
2255 GoToLine(ret-1,true);
2258 else
2260 doWatchInfo(0);
2264 void EEL_Editor::ensure_code_func_cache_valid()
2266 const char *prefix = m_function_prefix;
2267 if (!prefix || !*prefix) return;
2269 const DWORD now = GetTickCount();
2270 if (m_text.GetSize()==m_code_func_cache_lines && (now-m_code_func_cache_time)<5000) return;
2272 m_code_func_cache_lines = m_text.GetSize();
2273 m_code_func_cache_time = now;
2275 m_code_func_cache.Empty(true,free);
2277 const int prefix_len = (int) strlen(m_function_prefix);
2278 for (int i=0; i < m_text.GetSize(); ++i)
2280 WDL_FastString* s=m_text.Get(i);
2281 if (WDL_NORMALLY(s))
2283 const char *p = s->Get();
2284 while (*p)
2286 if (m_case_sensitive ? !strncmp(p,prefix,prefix_len) : !strnicmp(p,prefix,prefix_len))
2288 p+=prefix_len;
2289 while (*p == ' ') p++;
2290 if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_')
2292 const char *q = p+1;
2293 while ((*q >= '0' && *q <= '9') ||
2294 (*q >= 'a' && *q <= 'z') ||
2295 (*q >= 'A' && *q <= 'Z') ||
2296 *q == ':' || // lua
2297 *q == '_' || *q == '.') q++;
2299 const char *endp = q;
2300 while (*q == ' ') q++;
2301 if (*q == '(')
2303 const char *endq = q;
2304 while (*endq && *endq != ')') endq++;
2305 if (*endq) endq++;
2306 const char *r = endq;
2307 while (*r == ' ') r++;
2309 const int p_len = (int) (endp - p);
2310 const int q_len = (int) (endq - q);
2311 const int r_len = (int) strlen(r);
2313 // add function
2314 char *v = (char *)malloc(sizeof(int) + p_len + q_len + r_len + 3), *wr = v;
2315 if (WDL_NORMALLY(v))
2317 *(int *)wr = i; wr += sizeof(int);
2318 lstrcpyn_safe(wr,p,p_len+1); wr += p_len+1;
2319 lstrcpyn_safe(wr,q,q_len+1); wr += q_len+1;
2320 lstrcpyn_safe(wr,r,r_len+1); wr += r_len+1;
2322 m_code_func_cache.Add(v);
2324 p = r; // continue parsing after parentheses
2328 if (*p) p++;
2335 #ifdef WDL_IS_FAKE_CURSES
2337 LRESULT EEL_Editor::onMouseMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2339 switch (uMsg)
2341 case WM_LBUTTONDBLCLK:
2342 if (m_suggestion_hwnd) DestroyWindow(m_suggestion_hwnd);
2343 if (CURSES_INSTANCE && CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h)
2345 const int y = ((short)HIWORD(lParam)) / CURSES_INSTANCE->m_font_h - m_top_margin;
2346 //const int x = ((short)LOWORD(lParam)) / CURSES_INSTANCE->m_font_w + m_offs_x;
2347 WDL_FastString *fs=m_text.Get(y + m_paneoffs_y[m_curpane]);
2348 if (fs && y >= 0)
2350 if (!strncmp(fs->Get(),"import",6) && fs->Get()[6]>0 && isspace(fs->Get()[6]))
2352 open_import_line();
2353 return 1;
2357 // ctrl+doubleclicking a function goes to it
2358 if (!(g_eel_editor_flags&1) != !CTRL_KEY_DOWN)
2360 WDL_FastString *l=m_text.Get(m_curs_y);
2361 if (l)
2363 const char *p = l->Get(), *endp = p + l->GetLength(), *cursor = p + WDL_utf8_charpos_to_bytepos(p,m_curs_x);
2364 int state = 0, toklen = 0;
2365 const char *tok;
2366 while ((tok=sh_tokenize(&p,endp,&toklen,&state)) && cursor > tok+toklen);
2368 if (tok && cursor <= tok+toklen)
2370 ensure_code_func_cache_valid();
2372 while (toklen > 0)
2374 for (int i = 0; i < m_code_func_cache.GetSize(); i ++)
2376 const char *p = m_code_func_cache.Get(i);
2377 int line = *(int *)p;
2378 p+=sizeof(int);
2379 if (line != m_curs_y && strlen(p) == toklen && (m_case_sensitive ? !strncmp(p,tok,toklen) : !strnicmp(p,tok,toklen)))
2381 GoToLine(line,true);
2382 return 0;
2386 // try removing any foo. prefixes
2387 while (toklen > 0 && *tok != '.') { tok++; toklen--; }
2388 tok++;
2389 toklen--;
2395 break;
2396 case WM_LBUTTONDOWN:
2397 case WM_RBUTTONDOWN:
2398 if (m_suggestion_hwnd) DestroyWindow(m_suggestion_hwnd);
2399 break;
2402 return WDL_CursesEditor::onMouseMessage(hwnd,uMsg,wParam,lParam);
2404 #endif