Merge pull request #110 from tesselode/fixes
[wdl/wdl-ol.git] / WDL / win32_curses / eel_edit.cpp
blob425afeb1af587695571944de76da6fee814409a0
1 #ifdef _WIN32
2 #include <windows.h>
3 #else
4 #include "../swell/swell.h"
5 #endif
6 #include <stdlib.h>
7 #include <string.h>
8 #ifndef CURSES_INSTANCE
9 #define CURSES_INSTANCE ((win32CursesCtx *)m_cursesCtx)
10 #endif
11 #include "curses.h"
12 #include "eel_edit.h"
13 #include "../wdlutf8.h"
14 #include "../win32_utf8.h"
15 #include "../wdlcstring.h"
16 #include "../eel2/ns-eel-int.h"
19 EEL_Editor::EEL_Editor(void *cursesCtx) : WDL_CursesEditor(cursesCtx)
21 m_added_funclist=NULL;
22 m_suggestion_x=m_suggestion_y=-1;
23 m_case_sensitive=false;
24 m_comment_str="//"; // todo IsWithinComment() or something?
25 m_function_prefix = "function ";
28 EEL_Editor::~EEL_Editor()
32 #define sh_func_ontoken(x,y)
34 int EEL_Editor::namedTokenHighlight(const char *tokStart, int len, int state)
36 if (len == 4 && !strnicmp(tokStart,"this",4)) return SYNTAX_KEYWORD;
37 if (len == 7 && !strnicmp(tokStart,"_global",7)) return SYNTAX_KEYWORD;
38 if (len == 5 && !strnicmp(tokStart,"local",5)) return SYNTAX_KEYWORD;
39 if (len == 8 && !strnicmp(tokStart,"function",8)) return SYNTAX_KEYWORD;
40 if (len == 6 && !strnicmp(tokStart,"static",6)) return SYNTAX_KEYWORD;
41 if (len == 8 && !strnicmp(tokStart,"instance",8)) return SYNTAX_KEYWORD;
42 if (len == 6 && !strnicmp(tokStart,"global",6)) return SYNTAX_KEYWORD;
43 if (len == 7 && !strnicmp(tokStart,"globals",7)) return SYNTAX_KEYWORD;
45 if (len == 5 && !strnicmp(tokStart,"while",5)) return SYNTAX_KEYWORD;
46 if (len == 4 && !strnicmp(tokStart,"loop",4)) return SYNTAX_KEYWORD;
48 if (len == 17 && !strnicmp(tokStart,"__denormal_likely",17)) return SYNTAX_FUNC;
49 if (len == 19 && !strnicmp(tokStart,"__denormal_unlikely",19)) return SYNTAX_FUNC;
51 if (m_added_funclist)
53 char buf[512];
54 lstrcpyn_safe(buf,tokStart,wdl_min(sizeof(buf),len+1));
55 char **r=m_added_funclist->GetPtr(buf);
56 if (r) return *r ? SYNTAX_FUNC : SYNTAX_REGVAR;
59 NSEEL_VMCTX vm = peek_want_VM_funcs() ? peek_get_VM() : NULL;
60 int x;
61 for(x=0;;x++)
63 functionType *f = nseel_getFunctionFromTableEx((compileContext*)vm,x);
64 if (!f) break;
65 if (f && !strnicmp(tokStart,f->name,len) && (int)strlen(f->name) == len)
67 return SYNTAX_FUNC;
70 return A_NORMAL;
73 int EEL_Editor::parse_format_specifier(const char *fmt_in, int *var_offs, int *var_len)
75 const char *fmt = fmt_in+1;
76 *var_offs = 0;
77 *var_len = 0;
78 if (fmt_in[0] != '%') return 0; // passed a non-specifier
80 while (*fmt)
82 const char c = *fmt++;
84 if (c>0 && isalpha(c))
86 return (int) (fmt - fmt_in);
89 if (c == '.' || c == '+' || c == '-' || c == ' ' || (c>='0' && c<='9'))
92 else if (c == '{')
94 if (*var_offs!=0) return 0; // already specified
95 *var_offs = (int)(fmt-fmt_in);
96 if (*fmt == '.' || (*fmt >= '0' && *fmt <= '9')) return 0; // symbol name can't start with 0-9 or .
98 while (*fmt != '}')
100 if ((*fmt >= 'a' && *fmt <= 'z') ||
101 (*fmt >= 'A' && *fmt <= 'Z') ||
102 (*fmt >= '0' && *fmt <= '9') ||
103 *fmt == '_' || *fmt == '.' || *fmt == '#')
105 fmt++;
107 else
109 return 0; // bad character in variable name
112 *var_len = (int)((fmt-fmt_in) - *var_offs);
113 fmt++;
115 else
117 break;
120 return 0;
124 void EEL_Editor::draw_string(int *skipcnt, const char *str, int amt, int *attr, int newAttr, int comment_string_state)
126 if (amt > 0 && comment_string_state=='"')
128 while (amt > 0 && *str)
130 const char *str_scan = str;
131 int varpos,varlen,l=0;
133 while (!l && *str_scan)
135 while (*str_scan && *str_scan != '%' && str_scan < str+amt) str_scan++;
136 if (str_scan >= str+amt) break;
138 l = parse_format_specifier(str_scan,&varpos,&varlen);
139 if (!l && *str_scan) if (*++str_scan == '%') str_scan++;
141 if (!*str_scan || str_scan >= str+amt) break; // allow default processing to happen if we reached the end of the string
143 if (l > amt) l=amt;
145 if (str_scan > str)
147 const int sz=wdl_min((int)(str_scan-str),amt);
148 draw_string_urlchk(skipcnt,str,sz,attr,newAttr);
149 str += sz;
150 amt -= sz;
154 const int sz=(varlen>0) ? wdl_min(varpos,amt) : wdl_min(l,amt);
155 if (sz>0)
157 draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT2);
158 str += sz;
159 amt -= sz;
163 if (varlen>0)
165 int sz = wdl_min(varlen,amt);
166 if (sz>0)
168 draw_string_internal(skipcnt,str,sz,attr,*str == '#' ? SYNTAX_STRINGVAR : SYNTAX_HIGHLIGHT1);
169 amt -= sz;
170 str += sz;
173 sz = wdl_min(l - varpos - varlen, amt);
174 if (sz>0)
176 draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT2);
177 amt-=sz;
178 str+=sz;
183 draw_string_urlchk(skipcnt,str,amt,attr,newAttr);
186 void EEL_Editor::draw_string_urlchk(int *skipcnt, const char *str, int amt, int *attr, int newAttr)
188 if (amt > 0 && (newAttr == SYNTAX_COMMENT || newAttr == SYNTAX_STRING))
190 const char *sstr=str;
191 while (amt > 0 && *str)
193 const char *str_scan = str;
194 int l=0;
196 while (l < 10 && *str_scan)
198 str_scan += l;
199 l=0;
200 while (*str_scan &&
201 (strncmp(str_scan,"http://",7) || (sstr != str_scan && str_scan[-1] > 0 && isalnum(str_scan[-1]))) &&
202 str_scan < str+amt) str_scan++;
203 if (!*str_scan || str_scan >= str+amt) break;
204 while (str_scan[l] && str_scan[l] != ')' && str_scan[l] != '\"' && str_scan[l] != ')' && str_scan[l] != ' ' && str_scan[l] != '\t') l++;
206 if (!*str_scan || str_scan >= str+amt) break; // allow default processing to happen if we reached the end of the string
208 if (l > amt) l=amt;
210 if (str_scan > str)
212 const int sz=wdl_min((int)(str_scan-str),amt);
213 draw_string_internal(skipcnt,str,sz,attr,newAttr);
214 str += sz;
215 amt -= sz;
218 const int sz=wdl_min(l,amt);
219 if (sz>0)
221 draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT1);
222 str += sz;
223 amt -= sz;
227 draw_string_internal(skipcnt,str,amt,attr,newAttr);
230 void EEL_Editor::draw_string_internal(int *skipcnt, const char *str, int amt, int *attr, int newAttr)
232 if (amt>0)
234 const int sk = *skipcnt;
235 if (amt < sk)
237 *skipcnt=sk - amt;
239 else
241 if (sk>0)
243 str += sk;
244 amt -= sk;
245 *skipcnt=0;
247 if (amt>0)
249 if (*attr != newAttr)
251 attrset(newAttr);
252 *attr = newAttr;
254 addnstr(str,amt);
261 WDL_TypedBuf<char> EEL_Editor::s_draw_parentokenstack;
262 bool EEL_Editor::sh_draw_parenttokenstack_pop(char c)
264 int sz = s_draw_parentokenstack.GetSize();
265 while (--sz >= 0)
267 char tc = s_draw_parentokenstack.Get()[sz];
268 if (tc == c)
270 s_draw_parentokenstack.Resize(sz,false);
271 return false;
274 switch (c)
276 case '?':
277 // any open paren or semicolon is enough to cause error for ?:
279 return true;
280 case '(':
281 if (tc == '[') return true;
282 break;
283 case '[':
284 if (tc == '(') return true;
285 break;
289 return true;
291 bool EEL_Editor::sh_draw_parentokenstack_update(const char *tok, int toklen)
293 if (toklen == 1)
295 switch (*tok)
297 case '(':
298 case '[':
299 case ';':
300 case '?':
301 s_draw_parentokenstack.Add(*tok);
302 break;
303 case ':': return sh_draw_parenttokenstack_pop('?');
304 case ')': return sh_draw_parenttokenstack_pop('(');
305 case ']': return sh_draw_parenttokenstack_pop('[');
308 return false;
312 void EEL_Editor::draw_line_highlight(int y, const char *p, int *c_comment_state)
314 int last_attr = A_NORMAL;
315 attrset(last_attr);
316 move(y, 0);
317 int rv = do_draw_line(p, c_comment_state, last_attr);
318 attrset(rv< 0 ? SYNTAX_ERROR : A_NORMAL);
319 clrtoeol();
320 if (rv < 0) attrset(A_NORMAL);
323 int EEL_Editor::do_draw_line(const char *p, int *c_comment_state, int last_attr)
325 //skipcnt = m_offs_x
326 if (is_code_start_line(p))
328 *c_comment_state=0;
329 s_draw_parentokenstack.Resize(0,false);
332 int skipcnt = m_offs_x;
333 int ignoreSyntaxState = overrideSyntaxDrawingForLine(&skipcnt, &p, c_comment_state, &last_attr);
335 if (ignoreSyntaxState>0)
337 int len = (int)strlen(p);
338 draw_string(&skipcnt,p,len,&last_attr,ignoreSyntaxState==100 ? SYNTAX_ERROR :
339 ignoreSyntaxState==2 ? SYNTAX_COMMENT : A_NORMAL);
340 return len-m_offs_x < COLS;
344 // syntax highlighting
345 const char *endptr = p+strlen(p);
346 const char *tok;
347 const char *lp = p;
348 int toklen=0;
349 int last_comment_state=*c_comment_state;
350 while (NULL != (tok = sh_tokenize(&p,endptr,&toklen,c_comment_state)) || lp < endptr)
352 if (tok && *tok < 0 && toklen == 1)
354 while (tok[toklen] < 0) {p++; toklen++; } // utf-8 skip
356 if (last_comment_state>0) // if in a multi-line string or comment
358 // draw empty space between lp and p as a string. in this case, tok/toklen includes our string, so we quickly finish after
359 draw_string(&skipcnt,lp,(int)(p-lp),&last_attr, last_comment_state==1 ? SYNTAX_COMMENT:SYNTAX_STRING, last_comment_state);
360 last_comment_state=0;
361 lp = p;
362 continue;
364 sh_func_ontoken(tok,toklen);
366 // draw empty space between lp and tok/endptr as normal
367 const char *adv_to = tok ? tok : endptr;
368 if (adv_to > lp) draw_string(&skipcnt,lp,(int)(adv_to-lp),&last_attr, A_NORMAL);
370 if (adv_to >= endptr) break;
372 last_comment_state=0;
373 lp = p;
374 if (adv_to == p) continue;
376 // draw token
377 int attr = A_NORMAL;
378 int err_left=0;
379 int err_right=0;
380 int start_of_tok = 0;
382 if (tok[0] == '/' && toklen > 1 && (tok[1] == '*' || tok[1] == '/'))
384 attr = SYNTAX_COMMENT;
386 else if (tok[0] > 0 && (isalpha(tok[0]) || tok[0] == '_' || tok[0] == '#'))
388 int def_attr = A_NORMAL;
389 bool isf=true;
390 if (tok[0] == '#')
392 def_attr = SYNTAX_STRINGVAR;
393 draw_string(&skipcnt,tok,1,&last_attr,def_attr);
394 tok++;
395 toklen--;
397 while (toklen > 0)
399 // divide up by .s, if any
400 int this_len=0;
401 while (this_len < toklen && tok[this_len] != '.') this_len++;
402 if (this_len > 0)
404 int attr=isf?namedTokenHighlight(tok,this_len,*c_comment_state):def_attr;
405 if (isf && attr == A_NORMAL)
407 int ntok_len=0, cc = *c_comment_state;
408 const char *pp=lp,*ntok = sh_tokenize(&pp,endptr,&ntok_len,&cc);
409 if (ntok && ntok_len>0 && *ntok == '(') def_attr = attr = SYNTAX_FUNC2;
412 draw_string(&skipcnt,tok,this_len,&last_attr,attr==A_NORMAL?def_attr:attr);
413 tok += this_len;
414 toklen -= this_len;
416 if (toklen > 0)
418 draw_string(&skipcnt,tok,1,&last_attr,SYNTAX_HIGHLIGHT1);
419 tok++;
420 toklen--;
422 isf=false;
424 continue;
426 else if (tok[0] == '.' ||
427 (tok[0] >= '0' && tok[0] <= '9') ||
428 (toklen > 1 && tok[0] == '$' && (tok[1] == 'x' || tok[1]=='X')))
430 attr = SYNTAX_HIGHLIGHT2;
432 int x=1,mode=0;
433 if (tok[0] == '.') mode=1;
434 else if (toklen > 1 && (tok[0] == '$' || tok[0] == '0') && (tok[1] == 'x' || tok[1] == 'X')) { mode=2; x++; }
435 for(;x<toklen;x++)
437 if (tok[x] == '.' && !mode) mode=1;
438 else if (tok[x] < '0' || tok[x] > '9')
440 if (mode != 2 || ((tok[x] < 'a' || tok[x] > 'f') && (tok[x] < 'A' || tok[x] > 'F')))
441 break;
444 if (x<toklen) err_right=toklen-x;
446 else if (tok[0] == '\'' || tok[0] == '\"')
448 start_of_tok = tok[0];
449 attr = SYNTAX_STRING;
451 else if (tok[0] == '$')
453 attr = SYNTAX_HIGHLIGHT2;
455 if (toklen >= 3 && !strnicmp(tok,"$pi",3)) err_right = toklen - 3;
456 else if (toklen >= 2 && !strnicmp(tok,"$e",2)) err_right = toklen - 2;
457 else if (toklen >= 4 && !strnicmp(tok,"$phi",4)) err_right = toklen - 4;
458 else if (toklen == 4 && tok[1] == '\'' && tok[3] == '\'') { }
459 else if (toklen > 1 && tok[1] == '~')
461 int x;
462 for(x=2;x<toklen;x++) if (tok[x] < '0' || tok[x] > '9') break;
463 if (x<toklen) err_right=toklen-x;
465 else err_right = toklen;
467 else if (ignoreSyntaxState==-1 && (tok[0] == '{' || tok[0] == '}'))
469 attr = SYNTAX_HIGHLIGHT1;
471 else
473 const char *h="()+*-=/,|&%;!<>?:^!~[]";
474 while (*h && *h != tok[0]) h++;
475 if (*h)
477 if (*c_comment_state != STATE_BEFORE_CODE && sh_draw_parentokenstack_update(tok,toklen))
478 attr = SYNTAX_ERROR;
479 else
480 attr = SYNTAX_HIGHLIGHT1;
482 else
484 err_left=1;
485 if (tok[0] < 0) while (err_left < toklen && tok[err_left]<0) err_left++; // utf-8 skip
489 if (ignoreSyntaxState) err_left = err_right = 0;
491 if (err_left > 0)
493 if (err_left > toklen) err_left=toklen;
494 draw_string(&skipcnt,tok,err_left,&last_attr,SYNTAX_ERROR);
495 tok+=err_left;
496 toklen -= err_left;
498 if (err_right > toklen) err_right=toklen;
500 draw_string(&skipcnt, tok, toklen - err_right, &last_attr, attr, start_of_tok);
502 if (err_right > 0)
503 draw_string(&skipcnt,tok+toklen-err_right,err_right,&last_attr,SYNTAX_ERROR);
505 if (ignoreSyntaxState == -1 && tok[0] == '>')
507 draw_string(&skipcnt,p,strlen(p),&last_attr,ignoreSyntaxState==2 ? SYNTAX_COMMENT : A_NORMAL);
508 break;
511 return 1;
514 int EEL_Editor::GetCommentStateForLineStart(int line)
516 if (m_write_leading_tabs<=0) m_indent_size=2;
517 const bool uses_code_start_lines = !!is_code_start_line(NULL);
519 int state=0;
520 int x=0;
522 if (uses_code_start_lines)
524 state=STATE_BEFORE_CODE;
525 for (;;x++)
527 WDL_FastString *t = m_text.Get(x);
528 if (!t || is_code_start_line(t->Get())) break;
530 const char *p=t->Get();
532 if (!strnicmp(p,"tabsize:",8))
534 int a = atoi(p+8);
535 if (a>0 && a < 32) m_indent_size = a;
540 // scan backwards to find line starting with @
541 for (x=line;x>=0;x--)
543 WDL_FastString *t = m_text.Get(x);
544 if (!t) break;
545 if (is_code_start_line(t->Get()))
547 state=0;
548 break;
551 x++;
554 s_draw_parentokenstack.Resize(0,false);
556 for (;x<line;x++)
558 WDL_FastString *t = m_text.Get(x);
559 const char *p = t?t->Get():"";
560 if (is_code_start_line(p))
562 s_draw_parentokenstack.Resize(0,false);
563 state=0;
565 else if (state != STATE_BEFORE_CODE)
567 const int ll=t?t->GetLength():0;
568 const char *endp = p+ll;
569 int toklen;
570 const char *tok;
571 while (NULL != (tok=sh_tokenize(&p,endp,&toklen,&state))) // eat all tokens, updating state
573 sh_func_ontoken(tok,toklen);
574 sh_draw_parentokenstack_update(tok,toklen);
578 return state;
581 const char *EEL_Editor::sh_tokenize(const char **ptr, const char *endptr, int *lenOut, int *state)
583 return nseel_simple_tokenizer(ptr, endptr, lenOut, state);
587 bool EEL_Editor::LineCanAffectOtherLines(const char *txt, int spos, int slen) // if multiline comment etc
589 const char *special_start = txt + spos;
590 const char *special_end = txt + spos + slen;
591 while (*txt)
593 if (txt >= special_start-1 && txt < special_end)
595 const char c = txt[0];
596 if (c == '*' && txt[1] == '/') return true;
597 if (c == '/' && (txt[1] == '/' || txt[1] == '*')) return true;
598 if (c == '\\' && (txt[1] == '\"' || txt[1] == '\'')) return true;
600 if (txt >= special_start)
602 if (c == '\"' || c == '\'') return true;
603 if (c == '(' || c == '[' || c == ')' || c == ']' || c == ':' || c == ';' || c == '?') return true;
606 txt++;
608 return false;
612 struct eel_sh_token
614 int line, col, end_col;
615 unsigned int data; // packed char for token type, plus 24 bits for linecnt (0=single line, 1=extra line, etc)
617 eel_sh_token(int _line, int _col, int toklen, unsigned char c)
619 line = _line;
620 col = _col;
621 end_col = col + toklen;
622 data = c;
624 ~eel_sh_token() { }
626 void add_linecnt(int endcol) { data += 256; end_col = endcol; }
627 int get_linecnt() const { return (data >> 8); }
628 char get_c() const { return (char) (data & 255); }
630 bool is_comment() const {
631 return get_c() == '/' && (get_linecnt() || end_col>col+1);
635 static int eel_sh_get_token_for_pos(const WDL_TypedBuf<eel_sh_token> *toklist, int line, int col, bool *is_after)
637 const int sz = toklist->GetSize();
638 int x;
639 for (x=0; x < sz; x ++)
641 const eel_sh_token *tok = toklist->Get()+x;
642 const int first_line = tok->line;
643 const int last_line = first_line+tok->get_linecnt(); // last affected line (usually same as first)
645 if (last_line >= line) // if this token doesn't end before the line we care about
647 // check to see if the token starts after our position
648 if (first_line > line || (first_line == line && tok->col > col)) break;
650 // token started before line/col, see if it ends after line/col
651 if (last_line > line || tok->end_col > col)
653 // direct hit
654 *is_after = false;
655 return x;
659 *is_after = true;
660 return x-1;
663 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)
665 toklist->Resize(0,false);
666 int state=0;
667 int l;
668 int end_line = lines->GetSize();
669 if (editor->is_code_start_line(NULL))
671 for (l = start_line; l < end_line; l ++)
673 WDL_FastString *s = lines->Get(l);
674 if (s && editor->is_code_start_line(s->Get()))
676 end_line = l;
677 break;
680 for (; start_line >= 0; start_line--)
682 WDL_FastString *s = lines->Get(start_line);
683 if (s && editor->is_code_start_line(s->Get())) break;
685 if (start_line < 0) return; // before any code
687 start_line++;
689 else
691 start_line = 0;
695 for (l=start_line;l<end_line;l++)
697 WDL_FastString *t = lines->Get(l);
698 const int ll = t?t->GetLength():0;
699 const char *start_p = t?t->Get():"";
700 const char *p = start_p;
701 const char *endp = start_p+ll;
703 const char *tok;
704 int last_state=state;
705 int toklen;
706 while (NULL != (tok=editor->sh_tokenize(&p,endp,&toklen,&state))||last_state)
708 if (last_state == '\'' || last_state == '"' || last_state==1)
710 const int sz=toklist->GetSize();
711 // update last token to include this data
712 if (sz) toklist->Get()[sz-1].add_linecnt((int) ((tok ? p:endp) - start_p));
714 else
716 if (tok) switch (tok[0])
718 case '{':
719 case '}':
720 case '?':
721 case ':':
722 case ';':
723 case '(':
724 case '[':
725 case ')':
726 case ']':
727 case '\'':
728 case '"':
729 case '/': // comment
731 eel_sh_token t(l,(int)(tok-start_p),toklen,tok[0]);
732 toklist->Add(t);
734 break;
737 last_state=0;
742 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)
744 static WDL_TypedBuf<eel_sh_token> toklist;
745 eel_sh_generate_token_list(text,&toklist, cury,editor);
746 bool is_after;
747 const int hit_tokidx = eel_sh_get_token_for_pos(&toklist, cury, curx, &is_after);
748 const eel_sh_token *hit_tok = hit_tokidx >= 0 ? toklist.Get() + hit_tokidx : NULL;
750 if (!is_after && hit_tok && (hit_tok->get_c() == '"' || hit_tok->get_c() == '\'' || hit_tok->is_comment()))
752 // if within a string or comment, move to start, unless already at start, move to end
753 if (cury == hit_tok->line && curx == hit_tok->col)
755 *newx=hit_tok->end_col-1;
756 *newy=hit_tok->line + hit_tok->get_linecnt();
758 else
760 *newx=hit_tok->col;
761 *newy=hit_tok->line;
763 return true;
766 if (!hit_tok) return false;
768 const int toksz=toklist.GetSize();
769 int tokpos = hit_tokidx;
770 int pc1=0,pc2=0; // (, [ count
771 int pc3=0; // : or ? count depending on mode
772 int dir=-1, mode=0; // default to scan to previous [(
773 if (!is_after)
775 switch (hit_tok->get_c())
777 case '(': mode=1; dir=1; break;
778 case '[': mode=2; dir=1; break;
779 case ')': mode=3; dir=-1; break;
780 case ']': mode=4; dir=-1; break;
781 case '?': mode=5; dir=1; break;
782 case ':': mode=6; break;
783 case ';': mode=7; break;
785 // if hit a token, exclude this token from scanning
786 tokpos += dir;
789 while (tokpos>=0 && tokpos<toksz)
791 const eel_sh_token *tok = toklist.Get() + tokpos;
792 const char this_c = tok->get_c();
793 if (!pc1 && !pc2)
795 bool match=false, want_abort=false;
796 switch (mode)
798 case 0: match = this_c == '(' || this_c == '['; break;
799 case 1: match = this_c == ')'; break;
800 case 2: match = this_c == ']'; break;
801 case 3: match = this_c == '('; break;
802 case 4: match = this_c == '['; break;
803 case 5:
804 // scan forward to nearest : or ;
805 if (this_c == '?') pc3++;
806 else if (this_c == ':')
808 if (pc3>0) pc3--;
809 else match=true;
811 else if (this_c == ';') match=true;
812 else if (this_c == ')' || this_c == ']')
814 want_abort=true; // if you have "(x<y?z)", don't match for the ?
816 break;
817 case 6: // scanning back from : to ?, if any
818 case 7: // semicolon searches same as colon, effectively
819 if (this_c == ':') pc3++;
820 else if (this_c == '?')
822 if (pc3>0) pc3--;
823 else match = true;
825 else if (this_c == ';' || this_c == '(' || this_c == '[')
827 want_abort=true;
829 break;
832 if (want_abort) break;
833 if (match)
835 *newx=tok->col;
836 *newy=tok->line;
837 return true;
840 switch (this_c)
842 case '[': pc2++; break;
843 case ']': pc2--; break;
844 case '(': pc1++; break;
845 case ')': pc1--; break;
847 tokpos+=dir;
850 if (errmsg)
852 if (!mode) *errmsg = "Could not find previous [ or (";
853 else if (mode == 1) *errmsg = "Could not find matching )";
854 else if (mode == 2) *errmsg = "Could not find matching ]";
855 else if (mode == 3) *errmsg = "Could not find matching (";
856 else if (mode == 4) *errmsg = "Could not find matching [";
857 else if (mode == 5) *errmsg = "Could not find matching : or ; for ?";
858 else if (mode == 6) *errmsg = "Could not find matching ? for :";
859 else if (mode == 7) *errmsg = "Could not find matching ? for ;";
861 return false;
865 void EEL_Editor::doParenMatching()
867 WDL_FastString *curstr;
868 const char *errmsg = "";
869 if (NULL != (curstr=m_text.Get(m_curs_y)))
871 int bytex = WDL_utf8_charpos_to_bytepos(curstr->Get(),m_curs_x);
872 if (bytex >= curstr->GetLength()) bytex=curstr->GetLength()-1;
873 if (bytex<0) bytex = 0;
875 int new_x,new_y;
876 if (eel_sh_get_matching_pos_for_pos(&m_text, bytex,m_curs_y,&new_x,&new_y,&errmsg,this))
878 curstr = m_text.Get(new_y);
879 if (curstr) new_x = WDL_utf8_bytepos_to_charpos(curstr->Get(),new_x);
881 m_curs_x=new_x;
882 m_curs_y=new_y;
883 m_want_x=-1;
884 draw();
885 setCursor(1);
887 else if (errmsg[0])
889 draw_message(errmsg);
890 setCursor(0);
895 int EEL_Editor::peek_get_function_info(const char *name, char *sstr, size_t sstr_sz, int chkmask, int ignoreline)
897 if ((chkmask&4) && m_function_prefix && *m_function_prefix)
899 const size_t nlen = strlen(name);
900 const char *prefix = m_function_prefix;
901 const int prefix_len = (int) strlen(m_function_prefix);
902 for (int i=0; i < m_text.GetSize(); ++i)
904 WDL_FastString* s=m_text.Get(i);
905 if (s && i != ignoreline)
907 const char* p= s->Get();
908 while (*p)
910 if (m_case_sensitive ? !strncmp(p,prefix,prefix_len) : !strnicmp(p,prefix,prefix_len))
912 p+=prefix_len;
913 while (*p == ' ') p++;
914 if (m_case_sensitive ? !strncmp(p,name,nlen) : !strnicmp(p,name,nlen))
916 const char *np = p+nlen;
917 while (*np == ' ') np++;
919 if (*np == '(')
921 lstrcpyn_safe(sstr,p,sstr_sz);
922 return 4;
926 p++;
932 if ((chkmask&2) && m_added_funclist)
934 char **p=m_added_funclist->GetPtr(name);
935 if (p && *p)
937 lstrcpyn_safe(sstr,*p,sstr_sz);
938 return 2;
942 if (chkmask & 1)
944 peek_lock();
945 NSEEL_VMCTX vm = peek_want_VM_funcs() ? peek_get_VM() : NULL;
946 for (int x=0;;x++)
948 functionType *f = nseel_getFunctionFromTableEx((compileContext*)vm,x);
949 if (!f) break;
950 if (f && !stricmp(name,f->name))
952 snprintf(sstr,sstr_sz,"'%s' is a function that requires %d parameters", f->name,f->nParams&0xff);
953 peek_unlock();
954 return 1;
957 peek_unlock();
960 return 0;
962 bool EEL_Editor::peek_get_variable_info(const char *name, char *sstr, size_t sstr_sz)
964 peek_lock();
965 NSEEL_VMCTX vm = peek_get_VM();
966 EEL_F *vptr=NSEEL_VM_getvar(vm,name);
967 double v=0.0;
968 if (vptr) v=*vptr;
969 peek_unlock();
971 if (!vptr) return false;
973 int good_len=-1;
974 snprintf(sstr,sstr_sz,"%s=%.14f",name,v);
976 if (vm && v > -1.0 && v < NSEEL_RAM_ITEMSPERBLOCK*NSEEL_RAM_BLOCKS)
978 const unsigned int w = (unsigned int) (v+NSEEL_CLOSEFACTOR);
979 EEL_F *dv = NSEEL_VM_getramptr_noalloc(vm,w,NULL);
980 if (dv)
982 snprintf_append(sstr,sstr_sz," [0x%06x]=%.14f",w,*dv);
983 good_len=-2;
985 else
987 good_len = strlen(sstr);
988 snprintf_append(sstr,sstr_sz," [0x%06x]=<uninit>",w);
992 char buf[512];
993 buf[0]=0;
994 if (peek_get_numbered_string_value(v,buf,sizeof(buf)))
996 if (good_len==-2)
997 snprintf_append(sstr,sstr_sz," %.0f(str)=%s",v,buf);
998 else
1000 if (good_len>=0) sstr[good_len]=0; // remove [addr]=<uninit> if a string and no ram
1001 snprintf_append(sstr,sstr_sz," (str)=%s",buf);
1004 return true;
1007 void EEL_Editor::doWatchInfo(int c)
1009 // determine the word we are on, check its value in the effect
1010 char sstr[512], buf[512];
1011 lstrcpyn_safe(sstr,"Use this on a valid symbol name", sizeof(sstr));
1012 WDL_FastString *t=m_text.Get(m_curs_y);
1013 char curChar=0;
1014 if (t)
1016 const char *p=t->Get();
1017 const int bytex = WDL_utf8_charpos_to_bytepos(p,m_curs_x);
1018 if (bytex >= 0 && bytex < t->GetLength()) curChar = p[bytex];
1019 if (c != KEY_F1 && (m_selecting ||
1020 curChar == '(' ||
1021 curChar == '[' ||
1022 curChar == ')' ||
1023 curChar == ']'
1026 WDL_FastString code;
1027 int miny,maxy,minx,maxx;
1028 bool ok = false;
1029 if (!m_selecting)
1031 if (eel_sh_get_matching_pos_for_pos(&m_text,minx=m_curs_x,miny=m_curs_y,&maxx, &maxy,NULL,this))
1033 if (maxy==miny)
1035 if (maxx < minx)
1037 int tmp = minx;
1038 minx=maxx;
1039 maxx=tmp;
1042 else if (maxy < miny)
1044 int tmp=maxy;
1045 maxy=miny;
1046 miny=tmp;
1047 tmp = minx;
1048 minx=maxx;
1049 maxx=tmp;
1051 ok = true;
1052 minx++; // skip leading (
1055 else
1057 ok=true;
1058 getselectregion(minx,miny,maxx,maxy);
1059 WDL_FastString *s;
1060 s = m_text.Get(miny);
1061 if (s) minx = WDL_utf8_charpos_to_bytepos(s->Get(),minx);
1062 s = m_text.Get(maxy);
1063 if (s) maxx = WDL_utf8_charpos_to_bytepos(s->Get(),maxx);
1066 if (ok)
1068 int x;
1069 for (x = miny; x <= maxy; x ++)
1071 WDL_FastString *s=m_text.Get(x);
1072 if (s)
1074 const char *str=s->Get();
1075 int sx,ex;
1076 if (x == miny) sx=wdl_max(minx,0);
1077 else sx=0;
1078 int tmp=s->GetLength();
1079 if (sx > tmp) sx=tmp;
1081 if (x == maxy) ex=wdl_min(maxx,tmp);
1082 else ex=tmp;
1084 if (code.GetLength()) code.Append("\r\n");
1085 code.Append(ex-sx?str+sx:"",ex-sx);
1089 if (code.Get()[0])
1091 if (m_selecting && (GetAsyncKeyState(VK_SHIFT)&0x8000))
1093 peek_lock();
1094 NSEEL_CODEHANDLE ch;
1095 NSEEL_VMCTX vm = peek_get_VM();
1097 if (vm && (ch = NSEEL_code_compile_ex(vm,code.Get(),1,0)))
1099 codeHandleType *p = (codeHandleType*)ch;
1100 code.Ellipsize(3,20);
1101 const char *errstr = "failed writing to";
1102 if (p->code)
1104 buf[0]=0;
1105 GetTempPath(sizeof(buf)-64,buf);
1106 lstrcatn(buf,"jsfx-out",sizeof(buf));
1107 FILE *fp = fopen(buf,"wb");
1108 if (fp)
1110 errstr="wrote to";
1111 fwrite(p->code,1,p->code_size,fp);
1112 fclose(fp);
1115 snprintf(sstr,sizeof(sstr),"Expression '%s' compiled to %d bytes, %s temp/jsfx-out",code.Get(),p->code_size, errstr);
1116 NSEEL_code_free(ch);
1118 else
1120 code.Ellipsize(3,20);
1121 snprintf(sstr,sizeof(sstr),"Expression '%s' could not compile",code.Get());
1123 peek_unlock();
1125 else
1127 WDL_FastString code2;
1128 code2.Set("__debug_watch_value = (((((");
1129 code2.Append(code.Get());
1130 code2.Append(")))));");
1132 peek_lock();
1134 NSEEL_VMCTX vm = peek_get_VM();
1136 EEL_F *vptr=NULL;
1137 double v=0.0;
1138 const char *err="Invalid context";
1139 if (vm)
1141 NSEEL_CODEHANDLE ch = NSEEL_code_compile_ex(vm,code2.Get(),1,0);
1142 if (!ch) err = "Error parsing";
1143 else
1145 NSEEL_code_execute(ch);
1146 NSEEL_code_free(ch);
1147 vptr = NSEEL_VM_getvar(vm,"__debug_watch_value");
1148 if (vptr) v = *vptr;
1152 peek_unlock();
1155 // remove whitespace from code for display
1156 int x;
1157 bool lb=true;
1158 for (x=0;x<code.GetLength();x++)
1160 if (isspace(code.Get()[x]))
1162 if (lb) code.DeleteSub(x--,1);
1163 lb=true;
1165 else
1167 lb=false;
1170 if (lb && code.GetLength()>0) code.SetLen(code.GetLength()-1);
1173 code.Ellipsize(3,20);
1174 if (vptr)
1176 snprintf(sstr,sizeof(sstr),"Expression '%s' evaluates to %.14f",code.Get(),v);
1178 else
1180 snprintf(sstr,sizeof(sstr),"Error evaluating '%s': %s",code.Get(),err?err:"Unknown error");
1184 // compile+execute code within () as debug_watch_value = ( code )
1185 // show value (or err msg)
1187 else if (curChar>0 && (isalnum(curChar) || curChar == '_' || curChar == '.' || curChar == '#'))
1189 const int bytex = WDL_utf8_charpos_to_bytepos(p,m_curs_x);
1190 const char *lp=p+bytex;
1191 const char *rp=lp + WDL_utf8_charpos_to_bytepos(lp,1);
1192 while (lp >= p && *lp > 0 && (isalnum(*lp) || *lp == '_' || (*lp == '.' && (lp==p || lp[-1]!='.')))) lp--;
1193 if (lp < p || *lp != '#') lp++;
1194 while (*rp && *rp > 0 && (isalnum(*rp) || *rp == '_' || (*rp == '.' && rp[1] != '.'))) rp++;
1196 if (*lp == '#' && rp > lp+1)
1198 WDL_FastString n;
1199 lp++;
1200 n.Set(lp,(int)(rp-lp));
1201 int idx;
1202 if ((idx=peek_get_named_string_value(n.Get(),buf,sizeof(buf)))>=0) snprintf(sstr,sizeof(sstr),"#%s(%d)=%s",n.Get(),idx,buf);
1203 else snprintf(sstr,sizeof(sstr),"#%s not found",n.Get());
1205 else if (*lp > 0 && (isalpha(*lp) || *lp == '_') && rp > lp)
1207 WDL_FastString n;
1208 n.Set(lp,(int)(rp-lp));
1210 if (c==KEY_F1)
1212 on_help(n.Get(),0);
1213 return;
1216 int f = peek_get_function_info(n.Get(),sstr,sizeof(sstr),~0,-1);
1218 if (!f) f = peek_get_variable_info(n.Get(),sstr,sizeof(sstr))?1:0;
1219 if (!f) snprintf(sstr,sizeof(sstr),"'%s' NOT FOUND",n.Get());
1223 if (c==KEY_F1)
1225 on_help(NULL,(int)curChar);
1226 return;
1229 setCursor();
1230 draw_message(sstr);
1234 void EEL_Editor::draw_bottom_line()
1236 #define BOLD(x) { attrset(COLOR_BOTTOMLINE|A_BOLD); addstr(x); attrset(COLOR_BOTTOMLINE&~A_BOLD); }
1237 addstr("ma"); BOLD("T"); addstr("ch");
1238 BOLD(" S"); addstr("ave");
1239 if (peek_get_VM())
1241 addstr(" pee"); BOLD("K");
1243 if (GetTabCount()>1)
1245 addstr(" | tab: ");
1246 BOLD("[], F?"); addstr("=switch ");
1247 BOLD("W"); addstr("=close");
1249 #undef BOLD
1252 #define CTRL_KEY_DOWN (GetAsyncKeyState(VK_CONTROL)&0x8000)
1253 #define SHIFT_KEY_DOWN (GetAsyncKeyState(VK_SHIFT)&0x8000)
1254 #define ALT_KEY_DOWN (GetAsyncKeyState(VK_MENU)&0x8000)
1256 int EEL_Editor::onChar(int c)
1258 if ((m_ui_state == UI_STATE_NORMAL || m_ui_state == UI_STATE_MESSAGE) &&
1259 (c == 'K'-'A'+1 || c == 'S'-'A'+1 || !SHIFT_KEY_DOWN) && !ALT_KEY_DOWN) switch (c)
1261 case KEY_F1:
1262 if (CTRL_KEY_DOWN) break;
1263 case 'K'-'A'+1:
1264 doWatchInfo(c);
1265 return 0;
1266 case 'S'-'A'+1:
1267 if(updateFile())
1269 draw_message("Error writing file, changes not saved!");
1271 setCursor();
1272 return 0;
1274 case 'R'-'A'+1:
1275 if (!m_selecting)
1277 WDL_FastString *txtstr=m_text.Get(m_curs_y);
1278 const char *txt=txtstr?txtstr->Get():NULL;
1279 char fnp[2048];
1280 if (txt && line_has_openable_file(txt,WDL_utf8_charpos_to_bytepos(txt,m_curs_x),fnp,sizeof(fnp)))
1282 WDL_CursesEditor::OpenFileInTab(fnp);
1285 return 0;
1286 case KEY_F4:
1287 case 'T'-'A'+1:
1288 doParenMatching();
1289 return 0;
1292 return WDL_CursesEditor::onChar(c);
1295 void EEL_Editor::draw_top_line()
1297 if (m_curs_x >= m_suggestion_x && m_curs_y == m_suggestion_y && m_suggestion.GetLength())
1299 const char *p=m_suggestion.Get();
1300 char str[512];
1301 if (WDL_utf8_get_charlen(m_suggestion.Get()) > COLS)
1303 int l = WDL_utf8_charpos_to_bytepos(m_suggestion.Get(),COLS-4);
1304 if (l > sizeof(str)-6) l = sizeof(str)-6;
1305 lstrcpyn(str, m_suggestion.Get(), l+1);
1306 strcat(str, "...");
1307 p=str;
1310 attrset(COLOR_TOPLINE|A_BOLD);
1311 bkgdset(COLOR_TOPLINE);
1312 move(0, 0);
1313 addstr(p);
1314 clrtoeol();
1315 attrset(0);
1316 bkgdset(0);
1318 else
1320 m_suggestion_x=m_suggestion_y=-1;
1321 if (m_suggestion.GetLength()) m_suggestion.Set("");
1322 WDL_CursesEditor::draw_top_line();
1327 void EEL_Editor::onRightClick(HWND hwnd)
1329 WDL_LogicalSortStringKeyedArray<int> flist(m_case_sensitive);
1330 int i;
1331 if (!(GetAsyncKeyState(VK_CONTROL)&0x8000) && m_function_prefix && *m_function_prefix)
1333 const char *prefix = m_function_prefix;
1334 const int prefix_len = (int) strlen(m_function_prefix);
1335 const int comment_len=(int)strlen(m_comment_str);
1336 for (i=0; i < m_text.GetSize(); ++i)
1338 WDL_FastString* s=m_text.Get(i);
1339 const char* p=s ? s->Get() : NULL;
1340 if (p) while (*p)
1342 if (!strncmp(p, m_comment_str, comment_len)) break;
1344 if (m_case_sensitive ? !strncmp(p,prefix,prefix_len) : !strnicmp(p,prefix,prefix_len))
1346 p+=prefix_len;
1347 while (*p == ' ') p++;
1348 if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_')
1350 const char *q = p+1;
1351 while ((*q >= '0' && *q <= '9') ||
1352 (*q >= 'a' && *q <= 'z') ||
1353 (*q >= 'A' && *q <= 'Z') ||
1354 *q == '_' || *q == '.') q++;
1356 while (*q == ' ') q++;
1357 if (*q == '(')
1359 while (*q && *q != ')') q++;
1360 if (*q) q++;
1362 char buf[128];
1363 lstrcpyn(buf, p, wdl_min(q-p+1, sizeof(buf)));
1364 if (strlen(buf) > sizeof(buf)-2) lstrcpyn(buf+sizeof(buf)-5, "...", 4);
1365 flist.AddUnsorted(buf, i);
1369 p++;
1373 if (flist.GetSize())
1375 flist.Resort();
1376 HMENU hm=CreatePopupMenu();
1377 int pos=0;
1378 for (i=0; i < flist.GetSize(); ++i)
1380 const char* fname=NULL;
1381 int line=flist.Enumerate(i, &fname);
1382 InsertMenu(hm, pos++, MF_STRING|MF_BYPOSITION, line+1, fname);
1384 POINT p;
1385 GetCursorPos(&p);
1386 int ret=TrackPopupMenu(hm, TPM_NONOTIFY|TPM_RETURNCMD, p.x, p.y, 0, hwnd, NULL);
1387 DestroyMenu(hm);
1388 if (ret-- > 0)
1390 m_curs_y=ret;
1391 m_select_x1=0;
1392 m_select_x2=strlen(m_text.Get(ret)->Get());
1393 m_select_y1=m_select_y2=ret;
1394 m_selecting=1;
1395 setCursor(0,0.25);
1398 else
1400 doWatchInfo(0);
1405 #ifdef WDL_IS_FAKE_CURSES
1407 LRESULT EEL_Editor::onMouseMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1409 switch (uMsg)
1411 case WM_LBUTTONDBLCLK:
1412 if (CURSES_INSTANCE && CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h)
1414 const int y = ((short)HIWORD(lParam)) / CURSES_INSTANCE->m_font_h - m_top_margin;
1415 //const int x = ((short)LOWORD(lParam)) / CURSES_INSTANCE->m_font_w + m_offs_x;
1416 WDL_FastString *fs=m_text.Get(y + m_paneoffs_y[m_curpane]);
1417 if (fs && y >= 0)
1419 if (!strncmp(fs->Get(),"import",6) && isspace(fs->Get()[6]))
1421 onChar('R'-'A'+1); // open imported file
1422 return 1;
1427 break;
1430 return WDL_CursesEditor::onMouseMessage(hwnd,uMsg,wParam,lParam);
1432 #endif