Files
tlib/oversampling/WDL/win32_curses/eel_edit.cpp
2024-05-24 13:28:31 +02:00

2406 lines
70 KiB
C++

#ifdef _WIN32
#include <windows.h>
#include <windowsx.h>
#else
#include "../swell/swell.h"
#endif
#include <stdlib.h>
#include <string.h>
#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<char> 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<toklen;x++)
{
if (tok[x] == '.' && !mode) mode=1;
else if (tok[x] < '0' || tok[x] > '9')
{
if (mode != 2 || ((tok[x] < 'a' || tok[x] > 'f') && (tok[x] < 'A' || tok[x] > 'F')))
break;
}
}
if (x<toklen) err_right=toklen-x;
}
else if (tok[0] == '\'' || tok[0] == '\"')
{
start_of_tok = tok[0];
attr = SYNTAX_STRING;
}
else if (tok[0] == '$')
{
attr = SYNTAX_HIGHLIGHT2;
if (toklen >= 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<toklen;x++) if (tok[x] < '0' || tok[x] > '9') break;
if (x<toklen) err_right=toklen-x;
}
else err_right = toklen;
}
else if (ignoreSyntaxState==-1 && (tok[0] == '{' || tok[0] == '}'))
{
attr = SYNTAX_HIGHLIGHT1;
}
else if (is_pp)
{
if (tok[0] == '?')
last_comment_state = *c_comment_state;
if (sh_draw_parentokenstack_update(tok,toklen)) attr = SYNTAX_ERROR;
else attr = SYNTAX_KEYWORD;
}
else
{
const char *h="()+*-=/,|&%;!<>?:^!~[]";
while (*h && *h != tok[0]) h++;
if (*h)
{
if (*c_comment_state != STATE_BEFORE_CODE && sh_draw_parentokenstack_update(tok,toklen))
attr = SYNTAX_ERROR;
else
attr = SYNTAX_HIGHLIGHT1;
}
else
{
err_left=1;
if (tok[0] < 0) while (err_left < toklen && tok[err_left]<0) err_left++; // utf-8 skip
}
}
if (ignoreSyntaxState) err_left = err_right = 0;
if (err_left > 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;x<line || need_code;x++)
{
WDL_FastString *t = m_text.Get(x);
if (!t) break;
const char *p = t->Get();
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<line && (state != STATE_BEFORE_CODE || (toklen == 2 && is_preproc_token(tok))))
{
sh_func_ontoken(tok,toklen);
sh_draw_parentokenstack_update(tok,toklen);
}
}
}
if (x<line) state_ret = state;
}
return state_ret;
}
const char *EEL_Editor::sh_tokenize(const char **ptr, const char *endptr, int *lenOut, int *state)
{
int in_preproc = 0, prev_state = state ? *state : 0;
if (prev_state & STATE_PREPROC)
{
in_preproc = prev_state & STATE_PREPROC_MASK;
*state &= ~STATE_PREPROC_MASK;
}
const char *p = nseel_simple_tokenizer(ptr, endptr, lenOut, state);
if (in_preproc) *state |= in_preproc;
if (!p || !state) return p;
if (in_preproc)
{
if (*lenOut >= 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<eel_sh_token> *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<WDL_FastString> *lines, WDL_TypedBuf<eel_sh_token> *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;l<end_line;l++)
{
WDL_FastString *t = lines->Get(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; } // <?
}
if (WDL_NORMALLY(ok))
{
eel_sh_token t(l,(int)(tok-start_p),2,'P');
toklist->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; } // <?
}
if (WDL_NORMALLY(ok))
{
eel_sh_token t(l,(int)(tok-start_p),2,'p');
toklist->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<WDL_FastString> *text, int curx, int cury, int *newx, int *newy, const char **errmsg, EEL_Editor *editor)
{
static WDL_TypedBuf<eel_sh_token> 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<WDL_FastString> 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 <?
if (!is_after)
{
switch (hit_tok->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 && tokpos<toksz)
{
const eel_sh_token *tok = toklist.Get() + tokpos;
const char this_c = tok->get_c();
if (mode != 8 && mode != 9)
{
if (dir < 0)
{
if (this_c == 'P') // <?
{
if (mode == 0)
{
*newx=tok->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 "(x<y?z)", don't match for the ?
}
break;
case 6: // scanning back from : to ?, if any
case 7: // semicolon searches same as colon, effectively
if (this_c == ':') pc3++;
else if (this_c == '?')
{
if (pc3>0) 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]=<uninit> 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;x<code.GetLength();x++)
{
if (code.Get()[x]>0 && 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<fonth)
{
ypos = fonth;
use_h = cursor_line_y-fonth;
}
}
else
use_h = par_cr.bottom - ypos;
}
SetWindowPos(m_suggestion_hwnd,NULL,xpos,ypos,use_w,use_h, SWP_NOZORDER|SWP_NOACTIVATE);
InvalidateRect(m_suggestion_hwnd,NULL,FALSE);
ShowWindow(m_suggestion_hwnd,SW_SHOWNA);
GetCursorPos(&m_suggestion_hwnd_initmousepos);
}
did_fuzzy = true;
const char *p = m_suggestion_list.get(wdl_max(m_suggestion_hwnd_sel,0));
if (p && peek_get_token_info(p,sug,sizeof(sug),~0,m_curs_y)) break;
}
}
}
}
}
}
if (!did_fuzzy && m_suggestion_hwnd) DestroyWindow(m_suggestion_hwnd);
finish_sug:
if (strcmp(sug,m_suggestion.Get()) && m_ui_state == UI_STATE_NORMAL)
{
m_suggestion.Set(sug);
if (sug[0])
{
m_suggestion_x=m_curs_x;
m_suggestion_y=m_curs_y;
draw_top_line();
setCursor();
}
}
if (!sug[0] && m_suggestion_y>=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<int> 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<int>::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