#ifdef _WIN32 #include #include #else #include "../swell/swell.h" #endif #include #include #ifndef CURSES_INSTANCE #define CURSES_INSTANCE ((win32CursesCtx *)m_cursesCtx) #endif #include "curses.h" #include "eel_edit.h" #include "../wdlutf8.h" #include "../win32_utf8.h" #include "../wdlcstring.h" #include "../eel2/ns-eel-int.h" int g_eel_editor_max_vis_suggestions = 50; int g_eel_editor_flags; EEL_Editor::EEL_Editor(void *cursesCtx) : WDL_CursesEditor(cursesCtx) { m_suggestion_curline_comment_state=0; m_suggestion_x=m_suggestion_y=-1; m_case_sensitive=false; m_comment_str="//"; // todo IsWithinComment() or something? m_function_prefix = "function "; m_suggestion_hwnd=NULL; m_suggestion_hwnd_scroll=0; m_suggestion_hwnd_sel=-1; m_code_func_cache_lines=0; m_code_func_cache_time=0; } EEL_Editor::~EEL_Editor() { if (m_suggestion_hwnd) DestroyWindow(m_suggestion_hwnd); m_code_func_cache.Empty(true,free); } #define sh_func_ontoken(x,y) int EEL_Editor::namedTokenHighlight(const char *tokStart, int len, int state) { if (len == 4 && !strnicmp(tokStart,"this",4)) return SYNTAX_KEYWORD; if (len == 7 && !strnicmp(tokStart,"_global",7)) return SYNTAX_KEYWORD; if (len == 5 && !strnicmp(tokStart,"local",5)) return SYNTAX_KEYWORD; if (len == 8 && !strnicmp(tokStart,"function",8)) return SYNTAX_KEYWORD; if (len == 6 && !strnicmp(tokStart,"static",6)) return SYNTAX_KEYWORD; if (len == 8 && !strnicmp(tokStart,"instance",8)) return SYNTAX_KEYWORD; if (len == 6 && !strnicmp(tokStart,"global",6)) return SYNTAX_KEYWORD; if (len == 7 && !strnicmp(tokStart,"globals",7)) return SYNTAX_KEYWORD; if (len == 5 && !strnicmp(tokStart,"while",5)) return SYNTAX_KEYWORD; if (len == 4 && !strnicmp(tokStart,"loop",4)) return SYNTAX_KEYWORD; if (len == 17 && !strnicmp(tokStart,"__denormal_likely",17)) return SYNTAX_FUNC; if (len == 19 && !strnicmp(tokStart,"__denormal_unlikely",19)) return SYNTAX_FUNC; char buf[512]; lstrcpyn_safe(buf,tokStart,wdl_min(sizeof(buf),len+1)); NSEEL_VMCTX vm = peek_want_VM_funcs() ? peek_get_VM() : NULL; if (nseel_getFunctionByName((compileContext*)vm,buf,NULL)) return SYNTAX_FUNC; return A_NORMAL; } int EEL_Editor::parse_format_specifier(const char *fmt_in, int *var_offs, int *var_len) { const char *fmt = fmt_in+1; *var_offs = 0; *var_len = 0; if (fmt_in[0] != '%') return 0; // passed a non-specifier while (*fmt) { const char c = *fmt++; if (c>0 && isalpha(c)) { return (int) (fmt - fmt_in); } if (c == '.' || c == '+' || c == '-' || c == ' ' || (c>='0' && c<='9')) { } else if (c == '{') { if (*var_offs!=0) return 0; // already specified *var_offs = (int)(fmt-fmt_in); if (*fmt == '.' || (*fmt >= '0' && *fmt <= '9')) return 0; // symbol name can't start with 0-9 or . while (*fmt != '}') { if ((*fmt >= 'a' && *fmt <= 'z') || (*fmt >= 'A' && *fmt <= 'Z') || (*fmt >= '0' && *fmt <= '9') || *fmt == '_' || *fmt == '.' || *fmt == '#') { fmt++; } else { return 0; // bad character in variable name } } *var_len = (int)((fmt-fmt_in) - *var_offs); fmt++; } else { break; } } return 0; } void EEL_Editor::draw_string(int *skipcnt, const char *str, int amt, int *attr, int newAttr, int comment_string_state) { if (amt > 0 && comment_string_state=='"') { while (amt > 0 && *str) { const char *str_scan = str; int varpos,varlen,l=0; while (!l && *str_scan) { while (*str_scan && *str_scan != '%' && str_scan < str+amt) str_scan++; if (str_scan >= str+amt) break; l = parse_format_specifier(str_scan,&varpos,&varlen); if (!l && *str_scan) if (*++str_scan == '%') str_scan++; } if (!*str_scan || str_scan >= str+amt) break; // allow default processing to happen if we reached the end of the string if (l > amt) l=amt; if (str_scan > str) { const int sz=wdl_min((int)(str_scan-str),amt); draw_string_urlchk(skipcnt,str,sz,attr,newAttr); str += sz; amt -= sz; } { const int sz=(varlen>0) ? wdl_min(varpos,amt) : wdl_min(l,amt); if (sz>0) { draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT2); str += sz; amt -= sz; } } if (varlen>0) { int sz = wdl_min(varlen,amt); if (sz>0) { draw_string_internal(skipcnt,str,sz,attr,*str == '#' ? SYNTAX_STRINGVAR : SYNTAX_HIGHLIGHT1); amt -= sz; str += sz; } sz = wdl_min(l - varpos - varlen, amt); if (sz>0) { draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT2); amt-=sz; str+=sz; } } } } draw_string_urlchk(skipcnt,str,amt,attr,newAttr); } void EEL_Editor::draw_string_urlchk(int *skipcnt, const char *str, int amt, int *attr, int newAttr) { if (amt > 0 && (newAttr == SYNTAX_COMMENT || newAttr == SYNTAX_STRING)) { const char *sstr=str; while (amt > 0 && *str) { const char *str_scan = str; int l=0; while (l < 10 && *str_scan) { str_scan += l; l=0; while (*str_scan && (strncmp(str_scan,"http://",7) || (sstr != str_scan && str_scan[-1] > 0 && isalnum(str_scan[-1]))) && str_scan < str+amt) str_scan++; if (!*str_scan || str_scan >= str+amt) break; while (str_scan[l] && str_scan[l] != ')' && str_scan[l] != '\"' && str_scan[l] != ')' && str_scan[l] != ' ' && str_scan[l] != '\t') l++; } if (!*str_scan || str_scan >= str+amt) break; // allow default processing to happen if we reached the end of the string if (l > amt) l=amt; if (str_scan > str) { const int sz=wdl_min((int)(str_scan-str),amt); draw_string_internal(skipcnt,str,sz,attr,newAttr); str += sz; amt -= sz; } const int sz=wdl_min(l,amt); if (sz>0) { draw_string_internal(skipcnt,str,sz,attr,SYNTAX_HIGHLIGHT1); str += sz; amt -= sz; } } } draw_string_internal(skipcnt,str,amt,attr,newAttr); } void EEL_Editor::draw_string_internal(int *skipcnt, const char *str, int amt, int *attr, int newAttr) { // *skipcnt is in characters, amt is in bytes while (*skipcnt > 0 && amt > 0) { const int clen = wdl_utf8_parsechar(str,NULL); str += clen; amt -= clen; *skipcnt -= 1; } if (amt>0) { if (*attr != newAttr) { attrset(newAttr); *attr = newAttr; } addnstr(str,amt); } } WDL_TypedBuf EEL_Editor::s_draw_parentokenstack; bool EEL_Editor::sh_draw_parenttokenstack_pop(char c) { int sz = s_draw_parentokenstack.GetSize(); while (--sz >= 0) { char tc = s_draw_parentokenstack.Get()[sz]; if (tc == c) { s_draw_parentokenstack.Resize(sz,false); return false; } switch (c) { case '?': // any open paren or semicolon is enough to cause error for ?: return true; case '(': if (tc == 'P') return true; if (tc == '[') return true; break; case '[': if (tc == 'P') return true; if (tc == '(') return true; break; case 'P': if (tc != '?' && tc != ';') return true; break; } } return true; } static int is_preproc_token(const char *tok) { if (tok[0] == '<') return tok[1] == '?' ? 1 : 0; if (tok[0] == '?') return tok[1] == '>' ? -1 : 0; return 0; } bool EEL_Editor::sh_draw_parentokenstack_update(const char *tok, int toklen) { if (toklen == 1) { switch (*tok) { case '(': case '[': case ';': case '?': s_draw_parentokenstack.Add(*tok); break; case ':': return sh_draw_parenttokenstack_pop('?'); case ')': return sh_draw_parenttokenstack_pop('('); case ']': return sh_draw_parenttokenstack_pop('['); } } else if (toklen == 2) { switch (is_preproc_token(tok)) { case 1: s_draw_parentokenstack.Add('P'); break; case -1: return sh_draw_parenttokenstack_pop('P'); } } return false; } void EEL_Editor::draw_line_highlight(int y, const char *p, int *c_comment_state, int line_n) { if (line_n == m_curs_y) m_suggestion_curline_comment_state = *c_comment_state; int last_attr = A_NORMAL; attrset(last_attr); move(y, 0); int rv = do_draw_line(p, c_comment_state, last_attr); attrset(rv< 0 ? SYNTAX_ERROR : A_NORMAL); clrtoeol(); if (rv < 0) attrset(A_NORMAL); } #define STATE_PREPROC (0x20000000) #define STATE_PREPROC_SINGLELINEMODE (0x10000000) #define STATE_PREPROC_MASK (0x3ff00000) #define STATE_PREPROC_ENCODE_OLD(x) (((x)<<20)&0x0ff00000) #define STATE_PREPROC_DECODE_OLD(x) (((x)&0x0ff00000)>>20) int EEL_Editor::do_draw_line(const char *p, int *c_comment_state, int last_attr) { if (!(*c_comment_state & STATE_PREPROC) && is_code_start_line(p)) { *c_comment_state=0; s_draw_parentokenstack.Resize(0,false); } int skipcnt = m_offs_x; int ignoreSyntaxState = 0; const int initial_state = *c_comment_state; if (!(initial_state & STATE_PREPROC)) ignoreSyntaxState = overrideSyntaxDrawingForLine(&skipcnt, &p, c_comment_state, &last_attr); if (ignoreSyntaxState>0) { int len = (int)strlen(p); draw_string(&skipcnt,p,len,&last_attr,ignoreSyntaxState==100 ? SYNTAX_ERROR : ignoreSyntaxState==2 ? SYNTAX_COMMENT : A_NORMAL); return len-m_offs_x < COLS; } if (ignoreSyntaxState<0 && initial_state == STATE_BEFORE_CODE) *c_comment_state=0; if ((initial_state & STATE_PREPROC) && (initial_state & STATE_PREPROC_SINGLELINEMODE)) ignoreSyntaxState = -1; // syntax highlighting const char *endptr = p+strlen(p); const char *tok; const char *lp = p; int toklen=0; int last_comment_state=*c_comment_state & ~STATE_PREPROC_MASK; while (NULL != (tok = sh_tokenize(&p,endptr,&toklen,c_comment_state)) || lp < endptr) { if (initial_state == STATE_BEFORE_CODE && !(*c_comment_state & STATE_PREPROC) && !ignoreSyntaxState) *c_comment_state = initial_state; if (tok && *tok < 0 && toklen == 1) { while (tok[toklen] < 0) {p++; toklen++; } // utf-8 skip } bool is_pp = toklen == 2 && is_preproc_token(tok); if (!is_pp && (last_comment_state>0 || *c_comment_state == STATE_BEFORE_CODE)) // if in a multi-line string or comment { // draw empty space between lp and p as a string. in this case, tok/toklen includes our string, so we quickly finish after draw_string(&skipcnt,lp,(int)(p-lp),&last_attr, last_comment_state == 1 || last_comment_state == STATE_BEFORE_CODE || *c_comment_state == STATE_BEFORE_CODE ? SYNTAX_COMMENT : SYNTAX_STRING, last_comment_state); last_comment_state=0; lp = p; continue; } sh_func_ontoken(tok,toklen); // draw empty space between lp and tok/endptr as normal const char *adv_to = tok ? tok : endptr; if (adv_to > lp) draw_string(&skipcnt,lp,(int)(adv_to-lp),&last_attr, A_NORMAL); if (adv_to >= endptr) break; last_comment_state=0; lp = p; if (adv_to == p) continue; // draw token int attr = A_NORMAL; int err_left=0; int err_right=0; int start_of_tok = 0; if (tok[0] == '/' && toklen > 1 && (tok[1] == '*' || tok[1] == '/')) { attr = SYNTAX_COMMENT; } else if (tok[0] > 0 && (isalpha(tok[0]) || tok[0] == '_' || tok[0] == '#')) { int def_attr = A_NORMAL; bool isf=true; if (tok[0] == '#') def_attr = SYNTAX_STRINGVAR; while (toklen > 0) { // divide up by .s, if any, unless we're a string int this_len=def_attr == SYNTAX_STRINGVAR ? toklen : 0; while (this_len < toklen && tok[this_len] != '.') this_len++; if (this_len > 0) { int attr=isf?namedTokenHighlight(tok,this_len,*c_comment_state):def_attr; if (isf && attr == A_NORMAL) { int ntok_len=0, cc = *c_comment_state; const char *pp=lp,*ntok = sh_tokenize(&pp,endptr,&ntok_len,&cc); if (ntok && ntok_len>0 && *ntok == '(') def_attr = attr = SYNTAX_FUNC2; } draw_string(&skipcnt,tok,this_len,&last_attr,attr==A_NORMAL?def_attr:attr); tok += this_len; toklen -= this_len; } if (toklen > 0) { draw_string(&skipcnt,tok,1,&last_attr,SYNTAX_HIGHLIGHT1); tok++; toklen--; } isf=false; } continue; } else if (tok[0] == '.' || (tok[0] >= '0' && tok[0] <= '9') || (toklen > 1 && tok[0] == '$' && (tok[1] == 'x' || tok[1]=='X'))) { attr = SYNTAX_HIGHLIGHT2; int x=1,mode=0; if (tok[0] == '.') mode=1; else if (toklen > 1 && (tok[0] == '$' || tok[0] == '0') && (tok[1] == 'x' || tok[1] == 'X')) { mode=2; x++; } for(;x '9') { if (mode != 2 || ((tok[x] < 'a' || tok[x] > 'f') && (tok[x] < 'A' || tok[x] > 'F'))) break; } } if (x= 3 && !strnicmp(tok,"$pi",3)) err_right = toklen - 3; else if (toklen >= 2 && !strnicmp(tok,"$e",2)) err_right = toklen - 2; else if (toklen >= 4 && !strnicmp(tok,"$phi",4)) err_right = toklen - 4; else if (toklen == 4 && tok[1] == '\'' && tok[3] == '\'') { } else if (toklen > 1 && tok[1] == '~') { int x; for(x=2;x '9') break; if (x 0) { if (err_left > toklen) err_left=toklen; draw_string(&skipcnt,tok,err_left,&last_attr,SYNTAX_ERROR); tok+=err_left; toklen -= err_left; } if (err_right > toklen) err_right=toklen; draw_string(&skipcnt, tok, toklen - err_right, &last_attr, attr, start_of_tok); if (err_right > 0) draw_string(&skipcnt,tok+toklen-err_right,err_right,&last_attr,SYNTAX_ERROR); if (ignoreSyntaxState == -1 && tok[0] == '>') { draw_string(&skipcnt,p,strlen(p),&last_attr,ignoreSyntaxState==2 ? SYNTAX_COMMENT : A_NORMAL); break; } } if (ignoreSyntaxState<0) { if (initial_state == STATE_BEFORE_CODE || ((initial_state & STATE_PREPROC) && (initial_state & STATE_PREPROC_SINGLELINEMODE)) ) { if (*c_comment_state & STATE_PREPROC) *c_comment_state |= STATE_PREPROC_SINGLELINEMODE; else *c_comment_state=STATE_BEFORE_CODE; } } return 1; } int EEL_Editor::GetCommentStateForLineStart(int line) { if (m_write_leading_tabs<=0) m_indent_size=2; const bool uses_code_start_lines = !!is_code_start_line(NULL); int state=uses_code_start_lines ? STATE_BEFORE_CODE : 0; s_draw_parentokenstack.Resize(0,false); int state_ret = state; bool need_code = uses_code_start_lines; // we're also searching for tabsize: for (int x=0;xGet(); if (state == STATE_BEFORE_CODE && !strnicmp(p,"tabsize:",8)) { int a = atoi(p+8); if (a>0 && a < 32) m_indent_size = a; } if (!(state & STATE_PREPROC) && uses_code_start_lines && is_code_start_line(p)) { need_code = false; if (x>=line) break; s_draw_parentokenstack.Resize(0,false); state=0; } else { const int ll=t?t->GetLength():0; const char *endp = p+ll; int toklen; const char *tok; const int initial_state = state; // to be maximally correct here with the preprocessor here, we need to call overrideSyntaxDrawingForLine() // without drawing to detect special lines, but meh corner cases while (NULL != (tok=sh_tokenize(&p,endp,&toklen,&state))) // eat all tokens, updating state { if (initial_state == STATE_BEFORE_CODE && !(state & STATE_PREPROC)) state = STATE_BEFORE_CODE; if (x= 1 && p+1 < endptr && is_preproc_token(p) < 0) { *ptr += 2 - *lenOut; *lenOut = 2; *state = STATE_PREPROC_DECODE_OLD(prev_state); // caller will have to be aware and highlight this token correctly } else { for (int x = 0; x < *lenOut - 1; x ++) if (is_preproc_token(p+x) < 0) { int adj = x - *lenOut; *ptr += adj; *lenOut += adj; break; } } } else { if (*state == STATE_BEFORE_CODE && *lenOut == 4 && p + 7 < endptr && !strnicmp(p,"http://",7)) { int nl = 7; while (p+nl < endptr && p[nl] && p[nl] != ' ' && p[nl] != '\t') nl++; *ptr += nl-*lenOut; *lenOut = nl; } if (*lenOut >= 1 && p+1 < endptr && is_preproc_token(p) > 0) { *ptr += 2 - *lenOut; *lenOut = 2; *state = STATE_PREPROC_ENCODE_OLD(prev_state) | STATE_PREPROC; } else { for (int x = 0; x < *lenOut - 1; x ++) if (is_preproc_token(p+x) > 0) { int adj = x - *lenOut; *ptr += adj; *lenOut += adj; if (p[0] == '"' || p[0] == '\'') *state = p[0]; break; } } } return p; } bool EEL_Editor::LineCanAffectOtherLines(const char *txt, int spos, int slen) // if multiline comment etc { const char *special_start = txt + spos; const char *special_end = txt + spos + slen; while (*txt) { if (txt >= special_start-1 && txt < special_end) { const char c = txt[0]; if (c == '*' && txt[1] == '/') return true; if (c == '/' && (txt[1] == '/' || txt[1] == '*')) return true; if (c == '\\' && (txt[1] == '\"' || txt[1] == '\'')) return true; if (is_preproc_token(txt)) return true; if (txt >= special_start) { if (c == '\"' || c == '\'') return true; if (c == '(' || c == '[' || c == ')' || c == ']' || c == ':' || c == ';' || c == '?') return true; } } txt++; } return false; } struct eel_sh_token { int line, col, end_col; unsigned int data; // packed char for token type, plus 24 bits for linecnt (0=single line, 1=extra line, etc) eel_sh_token(int _line, int _col, int toklen, unsigned char c) { line = _line; col = _col; end_col = col + toklen; data = c; } ~eel_sh_token() { } void add_linecnt(int endcol) { data += 256; end_col = endcol; } int get_linecnt() const { return (data >> 8); } char get_c() const { return (char) (data & 255); } bool is_comment() const { return get_c() == '/' && (get_linecnt() || end_col>col+1); }; }; static int eel_sh_get_token_for_pos(const WDL_TypedBuf *toklist, int line, int col, bool *is_after) { const int sz = toklist->GetSize(); int x; for (x=0; x < sz; x ++) { const eel_sh_token *tok = toklist->Get()+x; const int first_line = tok->line; const int last_line = first_line+tok->get_linecnt(); // last affected line (usually same as first) if (last_line >= line) // if this token doesn't end before the line we care about { // check to see if the token starts after our position if (first_line > line || (first_line == line && tok->col > col)) break; // token started before line/col, see if it ends after line/col if (last_line > line || tok->end_col > col) { // direct hit *is_after = false; return x; } } } *is_after = true; return x-1; } static void eel_sh_generate_token_list(const WDL_PtrList *lines, WDL_TypedBuf *toklist, int start_line, EEL_Editor *editor) { toklist->Resize(0,false); int state=0; int l; int end_line = lines->GetSize(); if (editor->is_code_start_line(NULL)) { for (l = start_line; l < end_line; l ++) { WDL_FastString *s = lines->Get(l); if (s && editor->is_code_start_line(s->Get())) { end_line = l; break; } } for (; start_line >= 0; start_line--) { WDL_FastString *s = lines->Get(start_line); if (s && editor->is_code_start_line(s->Get())) break; } if (start_line < 0) return; // before any code start_line++; } else { start_line = 0; } int last_state_save = 0; for (l=start_line;lGet(l); const int ll = t?t->GetLength():0; const char *start_p = t?t->Get():""; const char *p = start_p; const char *endp = start_p+ll; const char *tok; int last_state=state & ~STATE_PREPROC; int toklen; while (NULL != (tok=editor->sh_tokenize(&p,endp,&toklen,&state))||last_state) { if (tok && toklen == 2 && is_preproc_token(tok) > 0) { bool ok = true; for (int x = toklist->GetSize()-1; x>=0; x--) { char c = toklist->Get()[x].get_c(); if (c == 'p') break; // ?> if (c == 'P') { ok = false; break; } // Add(t); last_state_save = STATE_PREPROC_DECODE_OLD(state); last_state = 0; continue; } } if (tok && toklen == 2 && is_preproc_token(tok) < 0) { bool ok = false; for (int x = toklist->GetSize()-1; x>=0; x--) { char c = toklist->Get()[x].get_c(); if (c == 'p') break; // ?> if (c == 'P') { ok = true; break; } // Add(t); last_state = last_state_save; last_state_save = 0; continue; } } if (last_state == '\'' || last_state == '"' || last_state==1) { const int sz=toklist->GetSize(); // update last token to include this data if (sz) { char c = last_state == 1 ? '/' : last_state; if (toklist->Get()[sz-1].get_c() == c) toklist->Get()[sz-1].add_linecnt((int) ((tok ? p:endp) - start_p)); else if (tok) { // usually indicates continuation after a preprocessor block eel_sh_token t(l,(int)(tok-start_p),toklen,c); toklist->Add(t); } } } else { if (tok) switch (tok[0]) { case '{': case '}': case '?': case ':': case ';': case '(': case '[': case ')': case ']': case '\'': case '"': case '/': // comment { eel_sh_token t(l,(int)(tok-start_p),toklen,tok[0]); toklist->Add(t); } break; } } last_state=0; } } } static bool eel_sh_get_matching_pos_for_pos(WDL_PtrList *text, int curx, int cury, int *newx, int *newy, const char **errmsg, EEL_Editor *editor) { static WDL_TypedBuf toklist; eel_sh_generate_token_list(text,&toklist, cury,editor); bool is_after; const int hit_tokidx = eel_sh_get_token_for_pos(&toklist, cury, curx, &is_after); const eel_sh_token *hit_tok = hit_tokidx >= 0 ? toklist.Get() + hit_tokidx : NULL; if (!is_after && hit_tok && (hit_tok->get_c() == '"' || hit_tok->get_c() == '\'' || hit_tok->is_comment())) { eel_sh_token tok = *hit_tok; // save a copy, toklist might get destroyed recursively here hit_tok = &tok; //if (tok.get_c() == '"') { // the user could be editing code in code, tokenize it and see if we can make sense of it WDL_FastString start, end; WDL_PtrList tmplist; WDL_FastString *s = text->Get(tok.line); if (s && s->GetLength() > tok.col+1) { int maxl = tok.get_linecnt()>0 ? 0 : tok.end_col - tok.col - 2; start.Set(s->Get() + tok.col+1, maxl); } tmplist.Add(&start); const int linecnt = tok.get_linecnt(); if (linecnt>0) { for (int a=1; a < linecnt; a ++) { s = text->Get(tok.line + a); if (s) tmplist.Add(s); } s = text->Get(tok.line + linecnt); if (s) { if (tok.end_col>1) end.Set(s->Get(), tok.end_col-1); tmplist.Add(&end); } } int lx = curx, ly = cury - tok.line; if (cury == tok.line) lx -= (tok.col+1); // this will destroy the token if (eel_sh_get_matching_pos_for_pos(&tmplist, lx, ly, newx, newy, errmsg, editor)) { *newy += tok.line; if (cury == tok.line) *newx += tok.col + 1; return true; } } // if within a string or comment, move to start, unless already at start, move to end if (cury == hit_tok->line && curx == hit_tok->col) { *newx=hit_tok->end_col-1; *newy=hit_tok->line + hit_tok->get_linecnt(); } else { *newx=hit_tok->col; *newy=hit_tok->line; } return true; } if (!hit_tok) return false; const int toksz=toklist.GetSize(); int tokpos = hit_tokidx; int pc1=0,pc2=0; // (, [ count int pc3=0; // : or ? count depending on mode int dir=-1, mode=0; // default to scan to previous [( or get_c()) { case '(': mode=1; dir=1; break; case '[': mode=2; dir=1; break; case ')': mode=3; dir=-1; break; case ']': mode=4; dir=-1; break; case '?': mode=5; dir=1; break; case ':': mode=6; break; case ';': mode=7; break; case 'P': mode=8; dir=1; break; case 'p': mode=9; dir=-1; break; } // if hit a token, exclude this token from scanning tokpos += dir; } while (tokpos>=0 && tokposget_c(); if (mode != 8 && mode != 9) { if (dir < 0) { if (this_c == 'P') // col; *newy=tok->line; return true; } break; } if (this_c == 'p') { while (--tokpos >= 0 && toklist.Get()[tokpos].get_c() != 'P'); tokpos--; continue; } } else { if (this_c == 'p') break; // ?> if (this_c == 'P') { while (++tokpos < toksz && toklist.Get()[tokpos].get_c() != 'p'); tokpos++; continue; } } } if ((!pc1 && !pc2) || mode == 8 || mode == 9) { bool match=false, want_abort=false; switch (mode) { case 0: match = this_c == '(' || this_c == '['; break; case 1: match = this_c == ')'; break; case 2: match = this_c == ']'; break; case 3: match = this_c == '('; break; case 4: match = this_c == '['; break; case 5: // scan forward to nearest : or ; if (this_c == '?') pc3++; else if (this_c == ':') { if (pc3>0) pc3--; else match=true; } else if (this_c == ';') match=true; else if (this_c == ')' || this_c == ']') { want_abort=true; // if you have "(x0) pc3--; else match = true; } else if (this_c == ';' || this_c == '(' || this_c == '[') { want_abort=true; } break; case 8: match = this_c == 'p'; break; case 9: match = this_c == 'P'; break; } if (want_abort) break; if (match) { *newx=tok->col; *newy=tok->line; return true; } } switch (this_c) { case '[': pc2++; break; case ']': pc2--; break; case '(': pc1++; break; case ')': pc1--; break; } tokpos+=dir; } if (errmsg) { if (!mode) *errmsg = "Could not find previous [ or ("; else if (mode == 1) *errmsg = "Could not find matching )"; else if (mode == 2) *errmsg = "Could not find matching ]"; else if (mode == 3) *errmsg = "Could not find matching ("; else if (mode == 4) *errmsg = "Could not find matching ["; else if (mode == 5) *errmsg = "Could not find matching : or ; for ?"; else if (mode == 6) *errmsg = "Could not find matching ? for :"; else if (mode == 7) *errmsg = "Could not find matching ? for ;"; } return false; } void EEL_Editor::doParenMatching() { WDL_FastString *curstr; const char *errmsg = ""; if (NULL != (curstr=m_text.Get(m_curs_y))) { int bytex = WDL_utf8_charpos_to_bytepos(curstr->Get(),m_curs_x); if (bytex >= curstr->GetLength()) bytex=curstr->GetLength()-1; if (bytex<0) bytex = 0; int new_x,new_y; if (eel_sh_get_matching_pos_for_pos(&m_text, bytex,m_curs_y,&new_x,&new_y,&errmsg,this)) { curstr = m_text.Get(new_y); if (curstr) new_x = WDL_utf8_bytepos_to_charpos(curstr->Get(),new_x); m_curs_x=new_x; m_curs_y=new_y; m_want_x=-1; draw(); setCursor(1); } else if (errmsg[0]) { draw_message(errmsg); setCursor(0); } } } static int word_len(const char *p) { int l = 0; if (*p >= 'a' && *p <='z') { l++; // lowercase word while (p[l] && p[l] != '_' && p[l] != '.' && !(p[l] >= 'A' && p[l] <='Z')) l++; } else if (*p >= 'A' && *p <= 'Z') { if (!strcmp(p,"MIDI")) return 4; l++; if (p[l] >= 'A' && p[l] <='Z') // UPPERCASE word while (p[l] && p[l] != '_' && p[l] != '.' && !(p[l] >= 'a' && p[l] <='z')) l++; else // Titlecase word while (p[l] && p[l] != '_' && p[l] != '.' && !(p[l] >= 'A' && p[l] <='Z')) l++; } return l; } static int search_str_partial(const char *str, int reflen, const char *word, int wordlen) { // find the longest leading segment of word in str int best_len = 0; for (int y = 0; y < reflen; y ++) { while (y < reflen && !strnicmp(str+y,word,best_len+1)) { if (++best_len >= wordlen) return best_len; reflen--; } } return best_len; } static int fuzzy_match2(const char *codestr, int codelen, const char *refstr, int reflen) { // codestr is user-typed, refstr is the reference function name // our APIs are gfx_blah_blah or TrackBlahBlah so breaking the API up by words // and searching the user-entered code should be effective int lendiff = reflen - codelen; if (lendiff < 0) lendiff = -lendiff; const char *word = refstr; int score = 0; for (;;) { while (*word == '_' || *word == '.') word++; const int wordlen = word_len(word); if (!wordlen) break; char word_buf[128]; lstrcpyn_safe(word_buf,word,wordlen+1); if (WDL_stristr(refstr,word_buf)==word) { int max_match_len = search_str_partial(codestr,codelen,word,wordlen); if (max_match_len < 2 && wordlen == 5 && !strnicmp(word,"Count",5)) { max_match_len = search_str_partial(codestr,codelen,"Num",3); } if (max_match_len > (wordlen <= 2 ? 1 : 2)) score += max_match_len; } word += wordlen; } if (!score) return 0; return score * 1000 + 1000 - wdl_clamp(lendiff*2,0,200); } int EEL_Editor::fuzzy_match(const char *codestr, const char *refstr) { const int codestr_len = (int)strlen(codestr), refstr_len = (int)strlen(refstr); if (refstr_len >= codestr_len && !strnicmp(codestr,refstr,codestr_len)) return 1000000000; int score1 = fuzzy_match2(refstr,refstr_len,codestr,codestr_len); int score2 = fuzzy_match2(codestr,codestr_len,refstr,refstr_len); if (score2 > score1) return score2 | 1; return score1&~1; } static int eeledit_varenumfunc(const char *name, EEL_F *val, void *ctx) { void **parms = (void **)ctx; int score = ((EEL_Editor*)parms[2])->fuzzy_match((const char *)parms[1], name); if (score > 0) ((suggested_matchlist*)parms[0])->add(name,score,suggested_matchlist::MODE_VAR); return 1; } void EEL_Editor::get_suggested_token_names(const char *fname, int chkmask, suggested_matchlist *list) { int x; if (chkmask & (KEYWORD_MASK_BUILTIN_FUNC|KEYWORD_MASK_USER_VAR)) { peek_lock(); NSEEL_VMCTX vm = peek_get_VM(); compileContext *fvm = vm && peek_want_VM_funcs() ? (compileContext*)vm : NULL; if (chkmask&KEYWORD_MASK_BUILTIN_FUNC) for (x=0;;x++) { functionType *p = nseel_enumFunctions(fvm,x); if (!p) break; int score = fuzzy_match(fname,p->name); if (score>0) list->add(p->name,score); } if (vm && (chkmask&KEYWORD_MASK_USER_VAR)) { const void *parms[3] = { list, fname, this }; NSEEL_VM_enumallvars(vm, eeledit_varenumfunc, parms); } peek_unlock(); } if (chkmask & KEYWORD_MASK_USER_FUNC) { ensure_code_func_cache_valid(); for (int x=0;x< m_code_func_cache.GetSize();x++) { const char *k = m_code_func_cache.Get(x) + 4; if (WDL_NORMALLY(k)) { int score = fuzzy_match(fname,k); if (score > 0) list->add(k,score,suggested_matchlist::MODE_USERFUNC); } } } } int EEL_Editor::peek_get_token_info(const char *name, char *sstr, size_t sstr_sz, int chkmask, int ignoreline) { if (chkmask&KEYWORD_MASK_USER_FUNC) { ensure_code_func_cache_valid(); for (int i = 0; i < m_code_func_cache.GetSize(); i ++) { const char *cacheptr = m_code_func_cache.Get(i); const char *nameptr = cacheptr + sizeof(int); if (!(m_case_sensitive ? strcmp(nameptr, name):stricmp(nameptr,name)) && *(int *)cacheptr != ignoreline) { const char *parms = nameptr+strlen(nameptr)+1; const char *trail = parms+strlen(parms)+1; snprintf(sstr,sstr_sz,"%s%s%s%s",nameptr,parms,*trail?" " :"",trail); return 4; } } } if ((chkmask & KEYWORD_MASK_USER_VAR) && name[0] == '#') { char tmp[256]; int v = peek_get_named_string_value(name+1,tmp,sizeof(tmp)); if (v>=0) { snprintf(sstr,sstr_sz,"%s(%d)=\"%s\"",name,v,tmp); return KEYWORD_MASK_USER_VAR; } } if (chkmask & (KEYWORD_MASK_BUILTIN_FUNC|KEYWORD_MASK_USER_VAR)) { int rv = 0; peek_lock(); NSEEL_VMCTX vm = peek_want_VM_funcs() ? peek_get_VM() : NULL; functionType *f = (chkmask&KEYWORD_MASK_BUILTIN_FUNC) ? nseel_getFunctionByName((compileContext*)vm,name,NULL) : NULL; double v; if (f) { snprintf(sstr,sstr_sz,"'%s' is a function that requires %d parameters", f->name,f->nParams&0xff); rv = KEYWORD_MASK_BUILTIN_FUNC; } else if (chkmask & KEYWORD_MASK_USER_VAR) { if (!vm) vm = peek_get_VM(); EEL_F *vptr=NSEEL_VM_getvar(vm,name); if (vptr) { v = *vptr; rv = KEYWORD_MASK_USER_VAR; } } peek_unlock(); if (rv == KEYWORD_MASK_USER_VAR) { int good_len=-1; snprintf(sstr,sstr_sz,"%s=%.14f",name,v); WDL_remove_trailing_decimal_zeros(sstr,2); if (v > -1.0 && v < NSEEL_RAM_ITEMSPERBLOCK*NSEEL_RAM_BLOCKS) { const unsigned int w = (unsigned int) (v+NSEEL_CLOSEFACTOR); EEL_F *dv = NSEEL_VM_getramptr_noalloc(vm,w,NULL); if (dv) { snprintf_append(sstr,sstr_sz," [%d]=%.14f",w,*dv); WDL_remove_trailing_decimal_zeros(sstr,2); } else { good_len = strlen(sstr); snprintf_append(sstr,sstr_sz," [%d]=<0>",w); } } char buf[512]; buf[0]=0; if (peek_get_numbered_string_value(v,buf,sizeof(buf))) { if (good_len>=0) sstr[good_len]=0; // remove [addr]= if a string and no ram snprintf_append(sstr,sstr_sz," #=\"%s\"",buf); } } if (rv) return rv; } return 0; } void EEL_Editor::doWatchInfo(int c) { // determine the word we are on, check its value in the effect char sstr[512], buf[512]; lstrcpyn_safe(sstr,"Use this on a valid symbol name", sizeof(sstr)); WDL_FastString *t=m_text.Get(m_curs_y); char curChar=0; if (t) { const char *p=t->Get(); const int bytex = WDL_utf8_charpos_to_bytepos(p,m_curs_x); if (bytex >= 0 && bytex < t->GetLength()) curChar = p[bytex]; if (c != KEY_F1 && (m_selecting || curChar == '(' || curChar == '[' || curChar == ')' || curChar == ']' )) { WDL_FastString code; int miny,maxy,minx,maxx; bool ok = false; if (!m_selecting) { if (eel_sh_get_matching_pos_for_pos(&m_text,minx=m_curs_x,miny=m_curs_y,&maxx, &maxy,NULL,this)) { if (maxy==miny) { if (maxx < minx) { int tmp = minx; minx=maxx; maxx=tmp; } } else if (maxy < miny) { int tmp=maxy; maxy=miny; miny=tmp; tmp = minx; minx=maxx; maxx=tmp; } ok = true; minx++; // skip leading ( } } else { ok=true; getselectregion(minx,miny,maxx,maxy); WDL_FastString *s; s = m_text.Get(miny); if (s) minx = WDL_utf8_charpos_to_bytepos(s->Get(),minx); s = m_text.Get(maxy); if (s) maxx = WDL_utf8_charpos_to_bytepos(s->Get(),maxx); } if (ok) { int x; for (x = miny; x <= maxy; x ++) { WDL_FastString *s=m_text.Get(x); if (s) { const char *str=s->Get(); int sx,ex; if (x == miny) sx=wdl_max(minx,0); else sx=0; int tmp=s->GetLength(); if (sx > tmp) sx=tmp; if (x == maxy) ex=wdl_min(maxx,tmp); else ex=tmp; if (code.GetLength()) code.Append("\r\n"); code.Append(ex-sx?str+sx:"",ex-sx); } } } if (code.Get()[0]) { if (m_selecting && (GetAsyncKeyState(VK_SHIFT)&0x8000)) { peek_lock(); NSEEL_CODEHANDLE ch; NSEEL_VMCTX vm = peek_get_VM(); if (vm && (ch = NSEEL_code_compile_ex(vm,code.Get(),1,0))) { codeHandleType *p = (codeHandleType*)ch; code.Ellipsize(3,20); const char *errstr = "failed writing to"; if (p->code) { buf[0]=0; GetTempPath(sizeof(buf)-64,buf); lstrcatn(buf,"jsfx-out",sizeof(buf)); FILE *fp = fopen(buf,"wb"); if (fp) { errstr="wrote to"; fwrite(p->code,1,p->code_size,fp); fclose(fp); } } snprintf(sstr,sizeof(sstr),"Expression '%s' compiled to %d bytes, %s temp/jsfx-out",code.Get(),p->code_size, errstr); NSEEL_code_free(ch); } else { code.Ellipsize(3,20); snprintf(sstr,sizeof(sstr),"Expression '%s' could not compile",code.Get()); } peek_unlock(); } else { WDL_FastString code2; code2.Set("__debug_watch_value = ((((("); code2.Append(code.Get()); code2.Append(")))));"); peek_lock(); NSEEL_VMCTX vm = peek_get_VM(); EEL_F *vptr=NULL; double v=0.0; const char *err="Invalid context"; if (vm) { NSEEL_CODEHANDLE ch = NSEEL_code_compile_ex(vm,code2.Get(),1,0); if (!ch) err = "Error parsing"; else { NSEEL_code_execute(ch); NSEEL_code_free(ch); vptr = NSEEL_VM_getvar(vm,"__debug_watch_value"); if (vptr) v = *vptr; } } peek_unlock(); { // remove whitespace from code for display int x; bool lb=true; for (x=0;x0 && isspace(code.Get()[x])) { if (lb) code.DeleteSub(x--,1); lb=true; } else { lb=false; } } if (lb && code.GetLength()>0) code.SetLen(code.GetLength()-1); } code.Ellipsize(3,20); if (vptr) { snprintf(sstr,sizeof(sstr),"Expression '%s' evaluates to %.14f",code.Get(),v); } else { snprintf(sstr,sizeof(sstr),"Error evaluating '%s': %s",code.Get(),err?err:"Unknown error"); } } } // compile+execute code within () as debug_watch_value = ( code ) // show value (or err msg) } else if (curChar>0 && (isalnum(curChar) || curChar == '_' || curChar == '.' || curChar == '#')) { const int bytex = WDL_utf8_charpos_to_bytepos(p,m_curs_x); const char *lp=p+bytex; const char *rp=lp + WDL_utf8_charpos_to_bytepos(lp,1); while (lp >= p && *lp > 0 && (isalnum(*lp) || *lp == '_' || (*lp == '.' && (lp==p || lp[-1]!='.')))) lp--; if (lp < p || *lp != '#') lp++; while (*rp && *rp > 0 && (isalnum(*rp) || *rp == '_' || (*rp == '.' && rp[1] != '.'))) rp++; if (*lp == '#' && rp > lp+1) { WDL_FastString n; lp++; n.Set(lp,(int)(rp-lp)); int idx=peek_get_named_string_value(n.Get(),buf,sizeof(buf)); if (idx>=0) { snprintf(sstr,sizeof(sstr),"#%s(%d)=%s",n.Get(),idx,buf); lp=""; } } if (*lp > 0 && (isalpha(*lp) || *lp == '_') && rp > lp) { WDL_FastString n; n.Set(lp,(int)(rp-lp)); if (c==KEY_F1) { if (m_suggestion.GetLength()) goto help_from_sug; on_help(n.Get(),0); return; } int f = peek_get_token_info(n.Get(),sstr,sizeof(sstr),~0,-1); if (!f) snprintf(sstr,sizeof(sstr),"'%s' NOT FOUND",n.Get()); } } } if (c==KEY_F1) { help_from_sug: WDL_FastString t; if (m_suggestion.GetLength()) { const char *p = m_suggestion.Get(); int l; for (l = 0; isalnum(p[l]) || p[l] == '_' || p[l] == '.'; l ++); if (l>0) t.Set(m_suggestion.Get(),l); } on_help(t.GetLength() > 2 ? t.Get() : NULL,(int)curChar); return; } setCursor(); draw_message(sstr); } void EEL_Editor::draw_bottom_line() { #define BOLD(x) { attrset(COLOR_BOTTOMLINE|A_BOLD); addstr(x); attrset(COLOR_BOTTOMLINE&~A_BOLD); } addstr("ma"); BOLD("T"); addstr("ch"); BOLD(" S"); addstr("ave"); if (peek_get_VM()) { addstr(" pee"); BOLD("K"); } if (GetTabCount()>1) { addstr(" | tab: "); BOLD("[], F?"); addstr("=switch "); BOLD("W"); addstr("=close"); } #undef BOLD } #define CTRL_KEY_DOWN (GetAsyncKeyState(VK_CONTROL)&0x8000) #define SHIFT_KEY_DOWN (GetAsyncKeyState(VK_SHIFT)&0x8000) #define ALT_KEY_DOWN (GetAsyncKeyState(VK_MENU)&0x8000) #ifndef WM_MOUSEWHEEL #define WM_MOUSEWHEEL 0x20A #endif static const char *suggestion_help_text = "(up/down to select, tab to insert)"; static const char *suggestion_help_text2 = "(tab or return to insert)"; static LRESULT WINAPI suggestionProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { #ifdef _WIN32 static int Scroll_Message; if (!Scroll_Message) { Scroll_Message = (int)RegisterWindowMessage("MSWHEEL_ROLLMSG"); if (!Scroll_Message) Scroll_Message=-1; } if (Scroll_Message > 0 && uMsg == (UINT)Scroll_Message) { uMsg=WM_MOUSEWHEEL; wParam<<=16; } #endif EEL_Editor *editor; switch (uMsg) { case WM_DESTROY: editor = (EEL_Editor *)GetWindowLongPtr(hwnd,GWLP_USERDATA); if (editor) editor->m_suggestion_hwnd = NULL; break; case WM_MOUSEWHEEL: { editor = (EEL_Editor *)GetWindowLongPtr(hwnd,GWLP_USERDATA); if (editor && editor->m_cursesCtx) { win32CursesCtx *ctx = (win32CursesCtx *)editor->m_cursesCtx; int a = (int)(short)HIWORD(wParam); if (a < 0) { a /= 120; if (a==0) a--; } else if (a>0) { a /= 120; if (a==0) a++; } else return 1; RECT r; GetClientRect(hwnd,&r); const int fonth = wdl_max(ctx->m_font_h,1); const int maxv = wdl_max(r.bottom / fonth - 1,1); const int ni = editor->m_suggestion_list.get_size(); if (ni > maxv) { const int nv = wdl_clamp(editor->m_suggestion_hwnd_scroll - a, 0, ni - maxv); if (nv != editor->m_suggestion_hwnd_scroll) { editor->m_suggestion_hwnd_scroll = nv; editor->m_suggestion_hwnd_sel = wdl_clamp(editor->m_suggestion_hwnd_sel,nv,nv+maxv-1); InvalidateRect(hwnd,NULL,FALSE); } } } } return 1; case WM_LBUTTONDOWN: case WM_MOUSEMOVE: editor = (EEL_Editor *)GetWindowLongPtr(hwnd,GWLP_USERDATA); if (editor) { win32CursesCtx *ctx = (win32CursesCtx *)editor->m_cursesCtx; if (ctx && ctx->m_font_h) { HWND par = GetParent(hwnd); if (uMsg == WM_MOUSEMOVE) { HWND foc = GetFocus(); if (!foc || (foc != hwnd && foc != par)) return 0; } RECT r; GetClientRect(hwnd,&r); SetForegroundWindow(par); SetFocus(par); const int max_vis = r.bottom / ctx->m_font_h - 1, sel = editor->m_suggestion_hwnd_sel; int hit = GET_Y_LPARAM(lParam) / ctx->m_font_h; if (hit >= max_vis) return 0; hit += editor->m_suggestion_hwnd_scroll; if (uMsg == WM_LBUTTONDOWN && !SHIFT_KEY_DOWN && !ALT_KEY_DOWN && !CTRL_KEY_DOWN) { editor->m_suggestion_hwnd_sel = hit; editor->onChar('\t'); } else if (sel != hit) { POINT pt; GetCursorPos(&pt); if (wdl_abs(pt.x - editor->m_suggestion_hwnd_initmousepos.x) + wdl_abs(pt.y - editor->m_suggestion_hwnd_initmousepos.y) < ctx->m_font_h*3/4) { return 0; } editor->m_suggestion_hwnd_initmousepos.x = pt.x + 0x10000000; editor->m_suggestion_hwnd_sel = hit; InvalidateRect(hwnd,NULL,FALSE); char sug[512]; sug[0]=0; const char *p = editor->m_suggestion_list.get(hit); if (p && editor->peek_get_token_info(p,sug,sizeof(sug),~0,-1)) { editor->m_suggestion.Set(sug); editor->draw_top_line(); editor->setCursor(); } } } } return 0; case WM_PAINT: editor = (EEL_Editor *)GetWindowLongPtr(hwnd,GWLP_USERDATA); if (editor) { PAINTSTRUCT ps; if (BeginPaint(hwnd,&ps)) { RECT r; GetClientRect(hwnd,&r); win32CursesCtx *ctx = (win32CursesCtx *)editor->m_cursesCtx; HBRUSH br = CreateSolidBrush(ctx->colortab[WDL_CursesEditor::COLOR_TOPLINE][1]); FillRect(ps.hdc,&r,br); DeleteObject(br); suggested_matchlist *ml = &editor->m_suggestion_list; HGDIOBJ oldObj = SelectObject(ps.hdc,ctx->mOurFont); SetBkMode(ps.hdc,TRANSPARENT); const int fonth = wdl_max(ctx->m_font_h,1); const int maxv = wdl_max(r.bottom / fonth - 1,1); const int selpos = wdl_max(editor->m_suggestion_hwnd_sel,0); int ypos = 0; editor->m_suggestion_hwnd_scroll = wdl_clamp(editor->m_suggestion_hwnd_scroll, selpos-maxv + 1,selpos); if (editor->m_suggestion_hwnd_scroll < 0) editor->m_suggestion_hwnd_scroll = 0; const bool show_scores = (GetAsyncKeyState(VK_SHIFT)&0x8000) && (GetAsyncKeyState(VK_CONTROL)&0x8000) && (GetAsyncKeyState(VK_MENU)&0x8000); for (int x = editor->m_suggestion_hwnd_scroll; x < ml->get_size() && ypos <= r.bottom-fonth*2; x ++) { int mode, score; const char *p = ml->get(x,&mode,&score); if (WDL_NORMALLY(p)) { const bool sel = x == selpos; SetTextColor(ps.hdc,ctx->colortab[ (mode == suggested_matchlist::MODE_VAR ? WDL_CursesEditor::COLOR_TOPLINE : mode == suggested_matchlist::MODE_USERFUNC ? WDL_CursesEditor::SYNTAX_FUNC2 : mode == suggested_matchlist::MODE_REGVAR ? WDL_CursesEditor::SYNTAX_REGVAR : WDL_CursesEditor::SYNTAX_KEYWORD) | (sel ? A_BOLD:0)][0]); RECT tr = {4, ypos, r.right-4, ypos+fonth }; DrawTextUTF8(ps.hdc,p,-1,&tr,DT_SINGLELINE|DT_NOPREFIX|DT_TOP|DT_LEFT); if (show_scores) { char tmp[128]; snprintf(tmp,sizeof(tmp),"(%d)",score); DrawTextUTF8(ps.hdc,tmp,-1,&tr,DT_SINGLELINE|DT_NOPREFIX|DT_TOP|DT_RIGHT); } } ypos += fonth; } { const COLORREF mix = ((ctx->colortab[WDL_CursesEditor::COLOR_TOPLINE][1]&0xfefefe)>>1) + ((ctx->colortab[WDL_CursesEditor::COLOR_TOPLINE][0]&0xfefefe)>>1); SetTextColor(ps.hdc,mix); RECT tr = {4, r.bottom-fonth, r.right-4, r.bottom }; DrawTextUTF8(ps.hdc, editor->m_suggestion_hwnd_sel >= 0 ? suggestion_help_text2 : suggestion_help_text, -1,&tr,DT_SINGLELINE|DT_NOPREFIX|DT_TOP|DT_CENTER); } SelectObject(ps.hdc,oldObj); EndPaint(hwnd,&ps); } } return 0; } return DefWindowProc(hwnd,uMsg,wParam,lParam); } void EEL_Editor::open_import_line() { WDL_FastString *txtstr=m_text.Get(m_curs_y); const char *txt=txtstr?txtstr->Get():NULL; char fnp[2048]; if (txt && line_has_openable_file(txt,WDL_utf8_charpos_to_bytepos(txt,m_curs_x),fnp,sizeof(fnp))) { WDL_CursesEditor::OpenFileInTab(fnp); } } int EEL_Editor::onChar(int c) { if ((m_ui_state == UI_STATE_NORMAL || m_ui_state == UI_STATE_MESSAGE) && (c == 'K'-'A'+1 || c == 'S'-'A'+1 || c == 'R'-'A'+1 || !SHIFT_KEY_DOWN) && !ALT_KEY_DOWN) switch (c) { case KEY_F1: if (CTRL_KEY_DOWN) break; WDL_FALLTHROUGH; case 'K'-'A'+1: doWatchInfo(c); return 0; case 'S'-'A'+1: { WDL_DestroyCheck chk(&destroy_check); if(updateFile()) { if (chk.isOK()) draw_message("Error writing file, changes not saved!"); } if (chk.isOK()) setCursorIfVisible(); } return 0; // case 'I': note stupidly we map Ctrl+I to VK_TAB, bleh case 'R'-'A'+1: if (!SHIFT_KEY_DOWN) break; if (!m_selecting) { open_import_line(); } return 0; case KEY_F4: case 'T'-'A'+1: doParenMatching(); return 0; } int rv = 0; int do_sug = (g_eel_editor_max_vis_suggestions > 0 && m_ui_state == UI_STATE_NORMAL && !m_selecting && m_top_margin > 0 && (c == 'L'-'A'+1 || (!CTRL_KEY_DOWN && !ALT_KEY_DOWN))) ? 1 : 0; bool did_fuzzy = false; char sug[1024]; sug[0]=0; if (do_sug) { if (m_suggestion_hwnd) { // insert if tab or shift+enter or (enter if arrow-navigated) if ((c == '\t' && !SHIFT_KEY_DOWN) || (c == '\r' && (m_suggestion_hwnd_sel>=0 || SHIFT_KEY_DOWN))) { char buf[512]; int sug_mode; const char *ptr = m_suggestion_list.get(wdl_max(m_suggestion_hwnd_sel,0), &sug_mode); lstrcpyn_safe(buf,ptr?ptr:"",sizeof(buf)); DestroyWindow(m_suggestion_hwnd); WDL_FastString *l=m_text.Get(m_curs_y); if (buf[0] && l && WDL_NORMALLY(m_suggestion_tokpos>=0 && m_suggestion_tokpos < l->GetLength()) && WDL_NORMALLY(m_suggestion_toklen>0) && WDL_NORMALLY(m_suggestion_tokpos+m_suggestion_toklen <= l->GetLength())) { preSaveUndoState(); if (sug_mode == suggested_matchlist::MODE_USERFUNC) { const char *tok = l->Get() + m_suggestion_tokpos; for (int j = m_suggestion_toklen - 1; j >= 0; j --) { if (tok[j] == '.') { const char *be = buf; while (*be) be++; while (be > buf && *be != '.') be--; if (be > buf) { // buf is foo.bar, lead_sz=3, if j>=3 check for common prefix const int lead_sz = (int) (be-buf); if (j == lead_sz || (j > lead_sz && tok[j-lead_sz-1] == '.')) { // prefixes match, ignore if (!strnicmp(buf, tok + j - lead_sz, lead_sz+1)) continue; } } m_suggestion_tokpos += j+1; m_suggestion_toklen -= j+1; break; } } } l->DeleteSub(m_suggestion_tokpos,m_suggestion_toklen); l->Insert(buf,m_suggestion_tokpos); int pos = m_suggestion_tokpos + strlen(buf); if (sug_mode == suggested_matchlist::MODE_FUNC || sug_mode == suggested_matchlist::MODE_USERFUNC) { if (pos >= l->GetLength() || l->Get()[pos] != '(') l->Insert("(",pos++); } m_curs_x = WDL_utf8_bytepos_to_charpos(l->Get(),pos); draw(); saveUndoState(); setCursor(); goto run_suggest; } return 0; } if ((c == KEY_UP || c==KEY_DOWN || c == KEY_NPAGE || c == KEY_PPAGE) && !SHIFT_KEY_DOWN) { m_suggestion_hwnd_sel = wdl_max(m_suggestion_hwnd_sel,0) + (c==KEY_UP ? -1 : c==KEY_DOWN ? 1 : c==KEY_NPAGE ? 4 : -4); if (m_suggestion_hwnd_sel >= m_suggestion_list.get_size()) m_suggestion_hwnd_sel = m_suggestion_list.get_size()-1; if (m_suggestion_hwnd_sel < 0) m_suggestion_hwnd_sel=0; InvalidateRect(m_suggestion_hwnd,NULL,FALSE); const char *p = m_suggestion_list.get(m_suggestion_hwnd_sel); if (p) peek_get_token_info(p,sug,sizeof(sug),~0,m_curs_y); goto finish_sug; } } if (c==27 || c=='L'-'A'+1 || c=='\r' || c=='\t' || (c>=KEY_DOWN && c<= KEY_F12 && c!=KEY_DC)) do_sug = 2; // no fuzzy window else if (c=='\b' && !m_suggestion_hwnd) do_sug=2; // backspace will update but won't show suggestions } rv = WDL_CursesEditor::onChar(c); if (!CURSES_INSTANCE) return rv; run_suggest: if (do_sug) { WDL_FastString *l=m_text.Get(m_curs_y); if (l) { const int MAX_TOK=512; struct { const char *tok; int toklen; } token_list[MAX_TOK]; const char *cursor = l->Get() + WDL_utf8_charpos_to_bytepos(l->Get(),m_curs_x); int ntok = 0; { const char *p = l->Get(), *endp = p + l->GetLength(); int state = m_suggestion_curline_comment_state, toklen = 0, bcnt = 0, pcnt = 0; const char *tok; // if no parens/brackets are open, use a peekable token that starts at cursor while ((tok=sh_tokenize(&p,endp,&toklen,&state)) && cursor > tok-(!pcnt && !bcnt && (tok[0] < 0 || tok[0] == '_' || isalpha(tok[0]) || tok[0] == '#'))) { if (!state) { if (WDL_NOT_NORMALLY(ntok >= MAX_TOK)) { memmove(token_list,token_list+1,sizeof(token_list) - sizeof(token_list[0])); ntok--; } switch (*tok) { case '[': bcnt++; break; case ']': bcnt--; break; case '(': pcnt++; break; case ')': pcnt--; break; } token_list[ntok].tok = tok; token_list[ntok].toklen = toklen; ntok++; } } } int t = ntok; int comma_cnt = 0; while (--t >= 0) { const char *tok = token_list[t].tok; int toklen = token_list[t].toklen; const int lc = tok[0], ac = lc==')' ? '(' : lc==']' ? '[' : 0; if (ac) { int cnt=1; while (--t>=0) { const int c = token_list[t].tok[0]; if (c == lc) cnt++; else if (c == ac && !--cnt) break; } if (t > 0) { const int c = token_list[t-1].tok[0]; if (c != ',' && c != ')' && c != ']') t--; continue; } } if (t<0) break; if (tok[0] == ',') comma_cnt++; else if ((tok[0] < 0 || tok[0] == '_' || isalpha(tok[0]) || tok[0] == '#') && (cursor <= tok + toklen || (t < ntok-1 && token_list[t+1].tok[0] == '('))) { // if cursor is within or at end of token, or is a function (followed by open-paren) char buf[512]; lstrcpyn_safe(buf,tok,wdl_min(toklen+1, sizeof(buf))); if (peek_get_token_info(buf,sug,sizeof(sug),~0,m_curs_y)) { if (comma_cnt > 0) { // hide previous parameters from sug's parameter fields so we know which parameters // we are (hopefully on) char *pstart = sug; while (*pstart && *pstart != '(') pstart++; if (*pstart == '(') pstart++; int comma_pos = 0; char *p = pstart; while (comma_pos < comma_cnt) { while (*p == ' ') p++; while (*p && *p != ',' && *p != ')') p++; if (*p == ')') break; if (*p) p++; comma_pos++; } if (*p && *p != ')') { *pstart=0; lstrcpyn_safe(buf,p,sizeof(buf)); snprintf_append(sug,sizeof(sug), "... %s",buf); } } break; } // only use token up to cursor for suggestions if (cursor >= tok && cursor <= tok+toklen) { toklen = (int) (cursor-tok); lstrcpyn_safe(buf,tok,wdl_min(toklen+1, sizeof(buf))); } if (c == '\b' && cursor == tok) { } else if (do_sug != 2 && t == ntok-1 && toklen >= 3 && cursor <= tok + toklen) { m_suggestion_list.clear(); get_suggested_token_names(buf,~0,&m_suggestion_list); win32CursesCtx *ctx = (win32CursesCtx *)m_cursesCtx; if (m_suggestion_list.get_size()>0 && WDL_NORMALLY(ctx->m_hwnd) && WDL_NORMALLY(ctx->m_font_w) && WDL_NORMALLY(ctx->m_font_h)) { m_suggestion_toklen = toklen; m_suggestion_tokpos = (int)(tok-l->Get()); m_suggestion_hwnd_sel = -1; if (!m_suggestion_hwnd) { #ifdef _WIN32 static HINSTANCE last_inst; const char *classname = "eel_edit_predicto"; HINSTANCE inst = (HINSTANCE)GetWindowLongPtr(ctx->m_hwnd,GWLP_HINSTANCE); if (inst != last_inst) { last_inst = inst; WNDCLASS wc={CS_DBLCLKS,suggestionProc,}; wc.lpszClassName=classname; wc.hInstance=inst; wc.hCursor=LoadCursor(NULL,IDC_ARROW); RegisterClass(&wc); } m_suggestion_hwnd = CreateWindowEx(0,classname,"", WS_CHILD, 0,0,0,0, ctx->m_hwnd, NULL, inst, NULL); #else m_suggestion_hwnd = CreateDialogParam(NULL,NULL,ctx->m_hwnd, suggestionProc, 0); #endif if (m_suggestion_hwnd) SetWindowLongPtr(m_suggestion_hwnd,GWLP_USERDATA,(LPARAM)this); } if (m_suggestion_hwnd) { const int fontw = ctx->m_font_w, fonth = ctx->m_font_h; int xpos = (WDL_utf8_bytepos_to_charpos(l->Get(),m_suggestion_tokpos) - m_offs_x) * fontw; RECT par_cr; GetClientRect(ctx->m_hwnd,&par_cr); int use_w = (int)strlen(suggestion_help_text); int use_h = (wdl_min(g_eel_editor_max_vis_suggestions,m_suggestion_list.get_size()) + 1)*fonth; for (int x = 0; x < m_suggestion_list.get_size(); x ++) { const char *p = m_suggestion_list.get(x); if (WDL_NORMALLY(p!=NULL)) { const int l = (int) strlen(p); if (l > use_w) use_w=l; } } use_w = 8 + use_w * fontw; if (use_w > par_cr.right - xpos) { xpos = wdl_max(par_cr.right - use_w,0); use_w = par_cr.right - xpos; } const int cursor_line_y = ctx->m_cursor_y * fonth; int ypos = cursor_line_y + fonth; if (ypos + use_h > par_cr.bottom) { if (cursor_line_y-fonth > par_cr.bottom - ypos) { // more room at the top, but enough? ypos = cursor_line_y - use_h; if (ypos=0 && m_ui_state == UI_STATE_NORMAL) { m_suggestion_x=m_suggestion_y=-1; m_suggestion.Set(""); if (m_top_margin>0) draw_top_line(); else draw(); setCursor(); } return rv; } void EEL_Editor::draw_top_line() { if (m_curs_x >= m_suggestion_x && m_curs_y == m_suggestion_y && m_suggestion.GetLength() && m_ui_state == UI_STATE_NORMAL) { const char *p=m_suggestion.Get(); char str[512]; if (WDL_utf8_get_charlen(m_suggestion.Get()) > COLS) { int l = WDL_utf8_charpos_to_bytepos(m_suggestion.Get(),COLS-4); if (l > sizeof(str)-6) l = sizeof(str)-6; lstrcpyn(str, m_suggestion.Get(), l+1); strcat(str, "..."); p=str; } attrset(COLOR_TOPLINE|A_BOLD); bkgdset(COLOR_TOPLINE); move(0, 0); addstr(p); clrtoeol(); attrset(0); bkgdset(0); } else { m_suggestion_x=m_suggestion_y=-1; if (m_suggestion.GetLength()) m_suggestion.Set(""); WDL_CursesEditor::draw_top_line(); } } void EEL_Editor::onRightClick(HWND hwnd) { WDL_LogicalSortStringKeyedArray flist(m_case_sensitive); int i; if (!(GetAsyncKeyState(VK_CONTROL)&0x8000)) { m_code_func_cache_lines = -1; // invalidate cache ensure_code_func_cache_valid(); for (i = 0; i < m_code_func_cache.GetSize(); i ++) { const char *p = m_code_func_cache.Get(i); const int line = *(int *)p; p += sizeof(int); const char *q = p+strlen(p)+1; char buf[512]; snprintf(buf,sizeof(buf),"%s%s",p,q); flist.AddUnsorted(buf,line); p += 4; } } get_extra_filepos_names(&flist,0); if (flist.GetSize()>1) { flist.Resort(); if (m_case_sensitive) flist.Resort(WDL_LogicalSortStringKeyedArray::cmpistr); } get_extra_filepos_names(&flist,1); if (flist.GetSize()) { HMENU hm=CreatePopupMenu(); int pos=0; for (i=0; i < flist.GetSize(); ++i) { const char* fname=NULL; int line=flist.Enumerate(i, &fname); InsertMenu(hm, pos++, MF_STRING|MF_BYPOSITION, line+1, fname); } POINT p; GetCursorPos(&p); int ret=TrackPopupMenu(hm, TPM_NONOTIFY|TPM_RETURNCMD, p.x, p.y, 0, hwnd, NULL); DestroyMenu(hm); if (ret > 0) { GoToLine(ret-1,true); } } else { doWatchInfo(0); } } void EEL_Editor::ensure_code_func_cache_valid() { const char *prefix = m_function_prefix; if (!prefix || !*prefix) return; const DWORD now = GetTickCount(); if (m_text.GetSize()==m_code_func_cache_lines && (now-m_code_func_cache_time)<5000) return; m_code_func_cache_lines = m_text.GetSize(); m_code_func_cache_time = now; m_code_func_cache.Empty(true,free); const int prefix_len = (int) strlen(m_function_prefix); for (int i=0; i < m_text.GetSize(); ++i) { WDL_FastString* s=m_text.Get(i); if (WDL_NORMALLY(s)) { const char *p = s->Get(); while (*p) { if (m_case_sensitive ? !strncmp(p,prefix,prefix_len) : !strnicmp(p,prefix,prefix_len)) { p+=prefix_len; while (*p == ' ') p++; if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') { const char *q = p+1; while ((*q >= '0' && *q <= '9') || (*q >= 'a' && *q <= 'z') || (*q >= 'A' && *q <= 'Z') || *q == ':' || // lua *q == '_' || *q == '.') q++; const char *endp = q; while (*q == ' ') q++; if (*q == '(') { const char *endq = q; while (*endq && *endq != ')') endq++; if (*endq) endq++; const char *r = endq; while (*r == ' ') r++; const int p_len = (int) (endp - p); const int q_len = (int) (endq - q); const int r_len = (int) strlen(r); // add function char *v = (char *)malloc(sizeof(int) + p_len + q_len + r_len + 3), *wr = v; if (WDL_NORMALLY(v)) { *(int *)wr = i; wr += sizeof(int); lstrcpyn_safe(wr,p,p_len+1); wr += p_len+1; lstrcpyn_safe(wr,q,q_len+1); wr += q_len+1; lstrcpyn_safe(wr,r,r_len+1); wr += r_len+1; m_code_func_cache.Add(v); } p = r; // continue parsing after parentheses } } } if (*p) p++; } } } } #ifdef WDL_IS_FAKE_CURSES LRESULT EEL_Editor::onMouseMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_LBUTTONDBLCLK: if (m_suggestion_hwnd) DestroyWindow(m_suggestion_hwnd); if (CURSES_INSTANCE && CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h) { const int y = ((short)HIWORD(lParam)) / CURSES_INSTANCE->m_font_h - m_top_margin; //const int x = ((short)LOWORD(lParam)) / CURSES_INSTANCE->m_font_w + m_offs_x; WDL_FastString *fs=m_text.Get(y + m_paneoffs_y[m_curpane]); if (fs && y >= 0) { if (!strncmp(fs->Get(),"import",6) && fs->Get()[6]>0 && isspace(fs->Get()[6])) { open_import_line(); return 1; } } // ctrl+doubleclicking a function goes to it if (!(g_eel_editor_flags&1) != !CTRL_KEY_DOWN) { WDL_FastString *l=m_text.Get(m_curs_y); if (l) { const char *p = l->Get(), *endp = p + l->GetLength(), *cursor = p + WDL_utf8_charpos_to_bytepos(p,m_curs_x); int state = 0, toklen = 0; const char *tok; while ((tok=sh_tokenize(&p,endp,&toklen,&state)) && cursor > tok+toklen); if (tok && cursor <= tok+toklen) { ensure_code_func_cache_valid(); while (toklen > 0) { for (int i = 0; i < m_code_func_cache.GetSize(); i ++) { const char *p = m_code_func_cache.Get(i); int line = *(int *)p; p+=sizeof(int); if (line != m_curs_y && strlen(p) == toklen && (m_case_sensitive ? !strncmp(p,tok,toklen) : !strnicmp(p,tok,toklen))) { GoToLine(line,true); return 0; } } // try removing any foo. prefixes while (toklen > 0 && *tok != '.') { tok++; toklen--; } tok++; toklen--; } } } } } break; case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: if (m_suggestion_hwnd) DestroyWindow(m_suggestion_hwnd); break; } return WDL_CursesEditor::onMouseMessage(hwnd,uMsg,wParam,lParam); } #endif