#ifdef _WIN32 #include #else #include "../swell/swell.h" #endif #include #include #ifndef CURSES_INSTANCE #define CURSES_INSTANCE ((win32CursesCtx*)m_cursesCtx) #endif #include "curses_editor.h" #include "../wdlutf8.h" #include "../win32_utf8.h" #include "../wdlcstring.h" #ifndef VALIDATE_TEXT_CHAR #define VALIDATE_TEXT_CHAR(thischar) ((thischar) >= 0 && (thischar >= 128 || isspace(thischar) || isgraph(thischar)) && !(thischar >= KEY_DOWN && thischar <= KEY_F12)) #endif #ifdef __APPLE__ #define CONTROL_KEY_NAME "Cmd" #else #define CONTROL_KEY_NAME "Ctrl" #endif WDL_FastString WDL_CursesEditor::s_fake_clipboard; int WDL_CursesEditor::s_overwrite=0; int WDL_CursesEditor::s_search_mode; // &1=case sensitive, &2=word. 5/6 = token matching static WDL_FastString s_goto_line_buf; static const char *searchmode_desc(int mode) { if (mode == 4 || mode == 5) return (mode&1) ? "TokenMatch":"tokenmatch"; return (mode&2) ? ((mode&1) ? "WordMatch":"wordmatch") : ((mode&1) ? "SubString":"substring"); } #ifndef WM_MOUSEWHEEL #define WM_MOUSEWHEEL 0x20A #endif static void __curses_onresize(win32CursesCtx *ctx) { WDL_CursesEditor *p = (WDL_CursesEditor *)ctx->user_data; if (p) { p->draw(); p->setCursorIfVisible(); } } WDL_CursesEditor::WDL_CursesEditor(void *cursesCtx) { m_newline_mode=0; m_write_leading_tabs=0; m_max_undo_states = 500; m_indent_size=2; m_cursesCtx = cursesCtx; m_top_margin=1; m_bottom_margin=1; m_selecting=0; m_select_x1=m_select_y1=m_select_x2=m_select_y2=0; m_ui_state=UI_STATE_NORMAL; m_offs_x=0; m_curs_x=m_curs_y=0; m_want_x=-1; m_undoStack_pos=-1; m_clean_undopos=0; m_curpane=0; m_pane_div=1.0; m_paneoffs_y[0]=m_paneoffs_y[1]=0; m_curpane=0; m_scrollcap=0; m_scrollcap_yoffs=0; m_filelastmod=0; m_status_lastlen=0; #ifdef WDL_IS_FAKE_CURSES if (m_cursesCtx) { CURSES_INSTANCE->user_data = this; CURSES_INSTANCE->onMouseMessage = _onMouseMessage; CURSES_INSTANCE->want_scrollbar=1; // 1 or 2 chars wide CURSES_INSTANCE->do_update = __curses_onresize; } #endif initscr(); cbreak(); noecho(); nonl(); intrflush(stdscr,FALSE); keypad(stdscr,TRUE); nodelay(stdscr,TRUE); raw(); // disable ctrl+C etc. no way to kill if allow quit isn't defined, yay. start_color(); #ifdef WDL_IS_FAKE_CURSES if (!curses_win32_global_user_colortab && (!m_cursesCtx || !CURSES_INSTANCE->user_colortab)) #endif { init_pair(1, COLOR_WHITE, COLOR_BLUE); // COLOR_BOTTOMLINE init_pair(2, COLOR_BLACK, COLOR_CYAN); // COLOR_SELECTION init_pair(3, RGB(0,255,255),COLOR_BLACK); // SYNTAX_HIGHLIGHT1 init_pair(4, RGB(0,255,0),COLOR_BLACK); // SYNTAX_HIGHLIGHT2 init_pair(5, RGB(96,128,192),COLOR_BLACK); // SYNTAX_COMMENT init_pair(6, COLOR_WHITE, COLOR_RED); // SYNTAX_ERROR init_pair(7, RGB(255,255,0), COLOR_BLACK); // SYNTAX_FUNC #ifdef WDL_IS_FAKE_CURSES init_pair(8, RGB(255,128,128), COLOR_BLACK); // SYNTAX_REGVAR init_pair(9, RGB(0,192,255), COLOR_BLACK); // SYNTAX_KEYWORD init_pair(10, RGB(255,192,192), COLOR_BLACK); // SYNTAX_STRING init_pair(11, RGB(192,255,128), COLOR_BLACK); // SYNTAX_STRINGVAR init_pair(12, COLOR_BLACK, COLOR_CYAN); // COLOR_MESSAGE (maps to COLOR_SELECTION) init_pair(13, COLOR_WHITE, COLOR_RED); // COLOR_TOPLINE (maps to SYNTAX_ERROR) init_pair(14, RGB(192,192,0), COLOR_BLACK); // SYNTAX_FUNC2 #endif } erase(); refresh(); } int WDL_CursesEditor::GetPaneDims(int* paney, int* paneh) // returns ypos of divider { const int pane_divy=(int)(m_pane_div*(double)(LINES-m_top_margin-m_bottom_margin-1)); if (paney) { paney[0]=m_top_margin; paney[1]=m_top_margin+pane_divy+1; } if (paneh) { paneh[0]=pane_divy+(m_pane_div >= 1.0 ? 1 : 0); paneh[1]=LINES-pane_divy-m_top_margin-m_bottom_margin-1; } return pane_divy; } int WDL_CursesEditor::getVisibleLines() const { return LINES-m_bottom_margin-m_top_margin; } #ifdef WDL_IS_FAKE_CURSES LRESULT WDL_CursesEditor::onMouseMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static int s_mousedown[2]; switch (uMsg) { case WM_CAPTURECHANGED: break; case WM_MOUSEMOVE: if (GetCapture()==hwnd && CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h) { POINT pt = { (short)LOWORD(lParam), (short)HIWORD(lParam) }; int cx=pt.x/CURSES_INSTANCE->m_font_w; int cy=pt.y/CURSES_INSTANCE->m_font_h; int paney[2], paneh[2]; const int pane_divy=GetPaneDims(paney, paneh); if (m_scrollcap) { int i=m_scrollcap-1; if (i == 2) // divider { int divy=cy-paney[0]; if (divy != pane_divy) { if (divy <= 0 || divy >= LINES-m_top_margin-m_bottom_margin-1) { if (divy <= 0.0) m_paneoffs_y[0]=m_paneoffs_y[1]; m_curpane=0; m_pane_div=1.0; } else { m_pane_div=(double)divy/(double)(LINES-m_top_margin-m_bottom_margin-1); } draw(); draw_status_state(); } } else { int prevoffs=m_paneoffs_y[i]; int pxy=paney[i]*CURSES_INSTANCE->m_font_h; int pxh=paneh[i]*CURSES_INSTANCE->m_font_h; if (pxh > CURSES_INSTANCE->scroll_h[i]) { m_paneoffs_y[i]=(m_text.GetSize()-paneh[i])*(pt.y-m_scrollcap_yoffs-pxy)/(pxh-CURSES_INSTANCE->scroll_h[i]); int maxscroll=m_text.GetSize()-paneh[i]+4; if (m_paneoffs_y[i] > maxscroll) m_paneoffs_y[i]=maxscroll; if (m_paneoffs_y[i] < 0) m_paneoffs_y[i]=0; } if (m_paneoffs_y[i] != prevoffs) { draw(); draw_status_state(); const int col=m_curs_x-m_offs_x; int line=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane]; if (line >= paney[m_curpane] && line < paney[m_curpane]+paneh[m_curpane]) move(line, col); } } return 0; } if (!m_selecting && (cx != s_mousedown[0] || cy != s_mousedown[1])) { m_select_x2=m_select_x1=m_curs_x; m_select_y2=m_select_y1=m_curs_y; m_selecting=1; } int x=cx+m_offs_x; int y=cy+m_paneoffs_y[m_curpane]-paney[m_curpane]; if (m_selecting && (m_select_x2!=x || m_select_y2 != y)) { if (y < m_paneoffs_y[m_curpane] && m_paneoffs_y[m_curpane] > 0) { m_paneoffs_y[m_curpane]--; } else if (y >= m_paneoffs_y[m_curpane]+paneh[m_curpane] && m_paneoffs_y[m_curpane]+paneh[m_curpane] < m_text.GetSize()) { m_paneoffs_y[m_curpane]++; } if (x < m_offs_x && m_offs_x > 0) { m_offs_x--; } else if (x > m_offs_x+COLS) { int maxlen=0; int a; for (a=0; a < paneh[m_curpane]; a++) { WDL_FastString* s=m_text.Get(m_paneoffs_y[m_curpane]+a); if (s) { const int l = WDL_utf8_get_charlen(s->Get()); if (l > maxlen) maxlen=l; } } if (maxlen > m_offs_x+COLS-8) m_offs_x++; } m_select_y2=y; m_select_x2=x; if (m_select_y2<0) m_select_y2=0; else if (m_select_y2>=m_text.GetSize()) { m_select_y2=m_text.GetSize()-1; WDL_FastString *s=m_text.Get(m_select_y2); if (s) m_select_x2 = WDL_utf8_get_charlen(s->Get()); } if (m_select_x2<0)m_select_x2=0; WDL_FastString *s=m_text.Get(m_select_y2); if (s) { const int l = WDL_utf8_get_charlen(s->Get()); if (m_select_x2>l) m_select_x2 = l; } draw(); int y=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane]; if (y >= paney[m_curpane] && y < paney[m_curpane]+paneh[m_curpane]) setCursor(); } } break; case WM_LBUTTONDBLCLK: 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) { const char *url=fs->Get(); while (NULL != (url = strstr(url,"http://"))) { if (url != fs->Get() && url[-1] > 0 && isalnum(url[-1])) { url+=7; } else { const int soffs = (int) (url - fs->Get()); char tmp[512]; char *p=tmp; while (p < (tmp+sizeof(tmp)-1) && *url && *url != ' ' && *url != ')' && *url != '\t' && *url != '"' && *url != '\'' ) { *p++ = *url++; } *p=0; if (strlen(tmp) >= 10 && x >= soffs && x<(url-fs->Get())) { ShellExecute(hwnd,"open",tmp,"","",0); return 1; } } } } } WDL_FALLTHROUGH; case WM_LBUTTONDOWN: if (CURSES_INSTANCE && CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h) { int x = ((short)LOWORD(lParam)) / CURSES_INSTANCE->m_font_w; int y = ((short)HIWORD(lParam)) / CURSES_INSTANCE->m_font_h; const int tabcnt=GetTabCount(); if (y==0 && tabcnt>1) { int tsz=COLS/tabcnt; // this is duplicated in draw_top_line if (tsz>128)tsz=128; if (tsz<12) tsz=12; SwitchTab(x/tsz,false); return 1; } } WDL_FALLTHROUGH; case WM_RBUTTONDOWN: if (CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h) { int mousex=(short)LOWORD(lParam); int mousey=(short)HIWORD(lParam); int cx=mousex/CURSES_INSTANCE->m_font_w; int cy=mousey/CURSES_INSTANCE->m_font_h; if (cx > COLS) cx=COLS; if (cx < 0) cx=0; if (cy > LINES) cy=LINES; if (cy < 0) cy=0; m_ui_state=UI_STATE_NORMAL; // any click clears the state s_mousedown[0]=cx; s_mousedown[1]=cy; int paney[2], paneh[2]; const int pane_divy=GetPaneDims(paney, paneh); if (uMsg == WM_LBUTTONDOWN && m_pane_div > 0.0 && m_pane_div < 1.0 && cy == m_top_margin+pane_divy) { SetCapture(hwnd); m_scrollcap=3; return 0; } int pane=-1; if (cy >= paney[0] && cy < paney[0]+paneh[0]) pane=0; else if (cy >= paney[1] && cy < paney[1]+paneh[1]) pane=1; if ((uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONDBLCLK) && pane >= 0 && cx >= COLS-CURSES_INSTANCE->drew_scrollbar[pane]) { const int st=paney[pane]*CURSES_INSTANCE->m_font_h+CURSES_INSTANCE->scroll_y[pane]; const int sb=st+CURSES_INSTANCE->scroll_h[pane]; int prevoffs=m_paneoffs_y[pane]; if (mousey < st) { m_paneoffs_y[pane] -= paneh[pane]; if (m_paneoffs_y[pane] < 0) m_paneoffs_y[pane]=0; } else if (mousey < sb) { if (uMsg == WM_LBUTTONDOWN) { SetCapture(hwnd); m_scrollcap=pane+1; m_scrollcap_yoffs=mousey-st; } } else { m_paneoffs_y[pane] += paneh[pane]; int maxscroll=m_text.GetSize()-paneh[pane]+4; if (maxscroll < 0) maxscroll = 0; if (m_paneoffs_y[pane] > maxscroll) m_paneoffs_y[pane]=maxscroll; } if (prevoffs != m_paneoffs_y[pane]) { draw(); draw_status_state(); int y=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane]; if (y >= paney[m_curpane] && y < paney[m_curpane]+paneh[m_curpane]) setCursor(); } return 0; } int ox=m_curs_x , oy=m_curs_y; if (m_selecting && ox == m_select_x2 && oy == m_select_y2) { ox = m_select_x1; oy = m_select_y1; } if (uMsg == WM_LBUTTONDOWN) m_selecting=0; if (pane >= 0) m_curpane=pane; m_curs_x=cx+m_offs_x; m_curs_y=cy+m_paneoffs_y[m_curpane]-paney[m_curpane]; bool end = (m_curs_y > m_text.GetSize()-1); if (end) m_curs_y=m_text.GetSize()-1; if (m_curs_y < 0) m_curs_y = 0; WDL_FastString *s=m_text.Get(m_curs_y); if (m_curs_x < 0) m_curs_x = 0; const int slen = s ? WDL_utf8_get_charlen(s->Get()) : 0; if (s && (end || m_curs_x > slen)) m_curs_x=slen; if (uMsg == WM_LBUTTONDOWN && !!(GetAsyncKeyState(VK_SHIFT)&0x8000) && (m_curs_x != ox || m_curs_y != oy)) { m_select_x1=ox; m_select_y1=oy; m_select_x2=m_curs_x; m_select_y2=m_curs_y; m_selecting=1; } else if (uMsg == WM_LBUTTONDBLCLK && s && slen) { if (m_curs_x < slen) { int x1=WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x); int x2=x1+1; const char* p=s->Get(); while (x1 > 0 && p[x1-1] > 0 && (isalnum(p[x1-1]) || p[x1-1] == '_')) --x1; while (x2 < s->GetLength() && p[x2] > 0 && (isalnum(p[x2]) || p[x2] == '_')) ++x2; if (x2 > x1) { m_select_x1=WDL_utf8_bytepos_to_charpos(s->Get(),x1); m_curs_x=m_select_x2=WDL_utf8_bytepos_to_charpos(s->Get(),x2); m_select_y1=m_select_y2=m_curs_y; m_selecting=1; } } } onChar('L'-'A'+1); // refresh, update suggestions if (uMsg == WM_LBUTTONDOWN) { SetCapture(hwnd); } else if (uMsg == WM_RBUTTONDOWN) { onRightClick(hwnd); } } return 0; case WM_LBUTTONUP: ReleaseCapture(); m_scrollcap=0; m_scrollcap_yoffs=0; return 0; case WM_MOUSEWHEEL: if (CURSES_INSTANCE->m_font_h) { POINT p; GetCursorPos(&p); ScreenToClient(hwnd, &p); p.y /= CURSES_INSTANCE->m_font_h; int paney[2], paneh[2]; GetPaneDims(paney, paneh); int pane=-1; if (p.y >= paney[0] && p.y < paney[0]+paneh[0]) pane=0; else if (p.y >= paney[1] && p.y < paney[1]+paneh[1]) pane=1; if (pane < 0) pane=m_curpane; m_paneoffs_y[pane] -= ((short)HIWORD(wParam))/20; int maxscroll=m_text.GetSize()-paneh[pane]+4; if (m_paneoffs_y[pane] > maxscroll) m_paneoffs_y[pane]=maxscroll; if (m_paneoffs_y[pane] < 0) m_paneoffs_y[pane]=0; draw(); int y=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane]; if (y >= paney[m_curpane] && y < paney[m_curpane]+paneh[m_curpane]) setCursor(); else draw_status_state(); } break; } return 0; } #endif WDL_CursesEditor::~WDL_CursesEditor() { endwin(); m_text.Empty(true); m_undoStack.Empty(true); } int WDL_CursesEditor::init(const char *fn, const char *init_if_empty) { m_filename.Set(fn); FILE *fh=fopenUTF8(fn,"rb"); if (!fh) { if (init_if_empty) { fh=fopenUTF8(fn,"w+b"); if (fh) { fwrite(init_if_empty,1,strlen(init_if_empty),fh); fseek(fh,0,SEEK_SET); } } if (!fh) { saveUndoState(); m_clean_undopos=m_undoStack_pos; return 1; } } loadLines(fh); fclose(fh); return 0; } int WDL_CursesEditor::reload_file(bool clearundo) { FILE *fh=fopenUTF8(m_filename.Get(),"rb"); if (fh) { if (!clearundo) { preSaveUndoState(); } else { m_undoStack.Empty(true); m_undoStack_pos=-1; } m_text.Empty(true); loadLines(fh); fclose(fh); return 0; } return 1; } static void ReplaceTabs(WDL_FastString *str, int tabsz) { int x; char s[128]; // replace any \t with spaces int insert_sz=tabsz - 1; if (insert_sz<0) insert_sz=0; else if (insert_sz>128) insert_sz=128; if (insert_sz>0) memset(s,' ',insert_sz); for(x=0;xGetLength();x++) { char *p = (char *)str->Get(); if (p[x] == '\t') { p[x] = ' '; str->Insert(s,x+1,insert_sz); x+=insert_sz; } } } void WDL_CursesEditor::loadLines(FILE *fh) { int crcnt = 0; int tabstate = 0; int tab_cnv_size=5; int rdcnt=0; for (;;) { WDL_FastString *fs = NULL; for (;;) { char line[4096]; line[0]=0; fgets(line,sizeof(line),fh); if (!line[0]) break; if (!fs) fs = new WDL_FastString(line); else fs->Append(line); if (fs->Get()[fs->GetLength()-1] == '\n' || fs->GetLength() > 32*1024*1024) break; } if (!fs) break; if (!rdcnt++) { if ((unsigned char)fs->Get()[0] == 0xef && (unsigned char)fs->Get()[1] == 0xbb && (unsigned char)fs->Get()[2] == 0xbf) { // remove BOM (could track it, but currently EEL/etc don't support reading it anyway) fs->DeleteSub(0,3); if (!fs->GetLength()) break; } } while (fs->GetLength()>0) { char c = fs->Get()[fs->GetLength()-1]; if (c == '\r') crcnt++; else if (c != '\n') break; fs->DeleteSub(fs->GetLength()-1,1); } m_text.Add(fs); if (tabstate>=0) { const char *p = fs->Get(); if (*p == '\t' && !tabstate) tabstate=1; while (*p == '\t') p++; int spacecnt=0; while (*p == ' ') { p++; spacecnt++; } if (*p == '\t' || spacecnt>7) tabstate=-1; // tab after space, or more than 7 spaces = dont try to preserve tabs else if (spacecnt+1 > tab_cnv_size) tab_cnv_size = spacecnt+1; } } if (tabstate>0) { m_indent_size=tab_cnv_size; m_write_leading_tabs=tab_cnv_size; } else { m_write_leading_tabs=0; } int x; for (x=0;x m_text.GetSize()/2; // more than half of lines have crlf, then use crlf saveUndoState(); m_clean_undopos=m_undoStack_pos; updateLastModTime(); } void WDL_CursesEditor::draw_status_state() { int paney[2], paneh[2]; const int pane_divy=GetPaneDims(paney, paneh); attrset(COLOR_BOTTOMLINE); bkgdset(COLOR_BOTTOMLINE); int line=LINES-1; const char* whichpane=""; if (m_pane_div > 0.0 && m_pane_div < 1.0) { whichpane=(!m_curpane ? "Upper pane: " : "Lower pane: "); line=m_top_margin+pane_divy; move(line, 0); clrtoeol(); } char str[512]; const int pane_offs = m_paneoffs_y[!!m_curpane]; snprintf(str, sizeof(str), "%sLine %d/%d [%d-%d] Col %d [%s]%s", whichpane, m_curs_y+1, m_text.GetSize(), pane_offs+1, wdl_min(pane_offs+1+paneh[!!m_curpane],m_text.GetSize()), m_curs_x+1, (s_overwrite ? "OVR" : "INS"), (m_clean_undopos == m_undoStack_pos ? "" : "*")); int len=strlen(str); int x=COLS-len-1; if (!*whichpane) { if (len < m_status_lastlen) { int xpos = COLS-m_status_lastlen-1; if (xpos<0) xpos=0; move(line,xpos); while (xpos++ < x) addstr(" "); } m_status_lastlen = len; } else { m_status_lastlen=0; } mvaddnstr(line, x, str, len); clrtoeol(); attrset(0); bkgdset(0); const int col=m_curs_x-m_offs_x; line=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane]; if (line >= paney[m_curpane] && line < paney[m_curpane]+paneh[m_curpane]) move(line, col); } void WDL_CursesEditor::setCursor(int isVscroll, double ycenter) { int maxx=m_text.Get(m_curs_y) ? m_text.Get(m_curs_y)->GetLength() : 0; if (isVscroll) { if (m_want_x >= 0) m_curs_x=m_want_x; } else { if (m_curs_x < maxx) m_want_x=-1; } if(m_curs_x>maxx) { if (isVscroll) m_want_x=m_curs_x; m_curs_x=maxx; } int redraw=0; if (m_curs_x < m_offs_x) { redraw=1; m_offs_x=m_curs_x; } else { const int mw = COLS-3; if (m_curs_x >= m_offs_x + mw) { m_offs_x=m_curs_x-mw+1; redraw=1; } } int paney[2], paneh[2]; GetPaneDims(paney, paneh); int y; if (ycenter >= 0.0 && ycenter < 1.0) { y=(int)((double)paneh[m_curpane]*ycenter); m_paneoffs_y[m_curpane]=m_curs_y-y; if (m_paneoffs_y[m_curpane] < 0) { y += m_paneoffs_y[m_curpane]; m_paneoffs_y[m_curpane]=0; } redraw=1; } else { y=m_curs_y-m_paneoffs_y[m_curpane]; if (y < 0) { m_paneoffs_y[m_curpane] += y; y=0; redraw=1; } else if (y >= paneh[m_curpane]) { m_paneoffs_y[m_curpane] += y-paneh[m_curpane]+1; y=paneh[m_curpane]-1; redraw=1; } } if (redraw) draw(); draw_status_state(); y += paney[m_curpane]; move(y, m_curs_x-m_offs_x); } void WDL_CursesEditor::setCursorIfVisible() { if (WDL_NOT_NORMALLY(m_curpane != 0 && m_curpane != 1)) return; int paney[2], paneh[2]; GetPaneDims(paney, paneh); int y=m_curs_y-m_paneoffs_y[m_curpane]; if (y >= 0 && y < paneh[m_curpane]) setCursor(); } void WDL_CursesEditor::draw_message(const char *str) { if (!CURSES_INSTANCE) return; int l=strlen(str); if (l && m_ui_state == UI_STATE_NORMAL) m_ui_state=UI_STATE_MESSAGE; if (l > COLS-2) l=COLS-2; if (str[0]) { attrset(COLOR_MESSAGE); bkgdset(COLOR_MESSAGE); } mvaddnstr(LINES-(m_bottom_margin>1?2:1),0,str,l); clrtoeol(); if (str[0]) { attrset(0); bkgdset(0); } int paney[2], paneh[2]; GetPaneDims(paney, paneh); const int col=m_curs_x-m_offs_x; int line=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane]; if (line >= paney[m_curpane] && line < paney[m_curpane]+paneh[m_curpane]) move(line, col); } void WDL_CursesEditor::draw_line_highlight(int y, const char *p, int *c_comment_state, int line_n) { attrset(A_NORMAL); mvaddstr(y,0,p + WDL_utf8_charpos_to_bytepos(p,m_offs_x)); clrtoeol(); } void WDL_CursesEditor::getselectregion(int &minx, int &miny, int &maxx, int &maxy) { if (m_select_y2 < m_select_y1) { miny=m_select_y2; maxy=m_select_y1; minx=m_select_x2; maxx=m_select_x1; } else if (m_select_y1 < m_select_y2) { miny=m_select_y1; maxy=m_select_y2; minx=m_select_x1; maxx=m_select_x2; } else { miny=maxy=m_select_y1; minx=wdl_min(m_select_x1,m_select_x2); maxx=wdl_max(m_select_x1,m_select_x2); } } void WDL_CursesEditor::doDrawString(int y, int line_n, const char *p, int *c_comment_state) { draw_line_highlight(y,p,c_comment_state, line_n); if (m_selecting) { int miny,maxy,minx,maxx; getselectregion(minx,miny,maxx,maxy); if (line_n >= miny && line_n <= maxy && (miny != maxy || minx < maxx)) { minx-=m_offs_x; maxx-=m_offs_x; const int cols = COLS; if (line_n > miny) minx=0; if (line_n < maxy) maxx=cols; if (minx<0)minx=0; if (minx > cols) minx=cols; if (maxx > cols) maxx=cols; if (maxx > minx) { attrset(COLOR_SELECTION); p += WDL_utf8_charpos_to_bytepos(p,m_offs_x+minx); mvaddnstr(y,minx, p, WDL_utf8_charpos_to_bytepos(p,maxx-minx)); attrset(A_NORMAL); } else if (maxx==minx && !*p) { attrset(COLOR_SELECTION); mvaddstr(y,minx," "); attrset(A_NORMAL); } } } } int WDL_CursesEditor::GetCommentStateForLineStart(int line) // pass current line/col, updates with previous interesting point, returns true if start of comment, or false if end of previous comment { return 0; } void WDL_CursesEditor::draw(int lineidx) { if (m_top_margin != 0) m_top_margin = GetTabCount()>1 ? 2 : 1; int paney[2], paneh[2]; const int pane_divy=GetPaneDims(paney, paneh); #ifdef WDL_IS_FAKE_CURSES if (!m_cursesCtx) return; CURSES_INSTANCE->offs_y[0]=m_paneoffs_y[0]; CURSES_INSTANCE->offs_y[1]=m_paneoffs_y[1]; CURSES_INSTANCE->div_y=pane_divy; CURSES_INSTANCE->tot_y=m_text.GetSize(); CURSES_INSTANCE->scrollbar_topmargin = m_top_margin; CURSES_INSTANCE->scrollbar_botmargin = m_bottom_margin; #endif attrset(A_NORMAL); if (lineidx >= 0) { int comment_state = GetCommentStateForLineStart(lineidx); WDL_FastString *s=m_text.Get(lineidx); if (s) { int y=lineidx-m_paneoffs_y[0]; if (y >= 0 && y < paneh[0]) { doDrawString(paney[0]+y, lineidx, s->Get(), &comment_state); } y=lineidx-m_paneoffs_y[1]; if (y >= 0 && y < paneh[1]) { doDrawString(paney[1]+y, lineidx, s->Get(), &comment_state); } } return; } __curses_invalidatefull((win32CursesCtx*)m_cursesCtx,false); draw_top_line(); attrset(A_NORMAL); bkgdset(A_NORMAL); move(m_top_margin,0); clrtoeol(); m_status_lastlen=0; int pane, i; for (pane=0; pane < 2; ++pane) { int ln=m_paneoffs_y[pane]; int y=paney[pane]; int h=paneh[pane]; int comment_state=GetCommentStateForLineStart(ln); for(i=0; i < h; ++i, ++ln, ++y) { WDL_FastString *s=m_text.Get(ln); if (!s) { move(y,0); clrtoeol(); } else { doDrawString(y,ln,s->Get(),&comment_state); } } } attrset(COLOR_BOTTOMLINE); bkgdset(COLOR_BOTTOMLINE); if (m_bottom_margin>0) { move(LINES-1, 0); #define BOLD(x) { attrset(COLOR_BOTTOMLINE|A_BOLD); addstr(x); attrset(COLOR_BOTTOMLINE&~A_BOLD); } if (m_selecting) { mvaddstr(LINES-1,0,"SELECTING ESC:cancel " CONTROL_KEY_NAME "+("); BOLD("C"); addstr("opy "); BOLD("X"); addstr(":cut "); BOLD("V"); addstr(":paste)"); } else { mvaddstr(LINES-1, 0, CONTROL_KEY_NAME "+("); if (m_pane_div <= 0.0 || m_pane_div >= 1.0) { BOLD("P"); addstr("ane "); } else { BOLD("O"); addstr("therpane "); addstr("no"); BOLD("P"); addstr("anes "); } BOLD("F"); addstr("ind/"); BOLD("R"); addstr("eplace "); draw_bottom_line(); addstr(")"); } #undef BOLD clrtoeol(); } attrset(0); bkgdset(0); __curses_invalidatefull((win32CursesCtx*)m_cursesCtx,true); } void WDL_CursesEditor::draw_bottom_line() { // implementers add key commands here } static const char *countLeadingTabs(const char *p, int *ntabs, int tabsz) { if (tabsz>0) while (*p) { int i; for (i=0;iGet(),&tabcnt,m_write_leading_tabs); while (tabcnt-->0) fputc('\t',fp); fwrite(p,1,strlen(p),fp); if (m_newline_mode==1) fputc('\r',fp); fputc('\n',fp); } } fclose(fp); sync(); updateLastModTime(); m_clean_undopos = m_undoStack_pos; return 0; } void WDL_CursesEditor::updateLastModTime() { struct stat srcstat; if (!statUTF8(m_filename.Get(), &srcstat)) { #ifndef __APPLE__ m_filelastmod = srcstat.st_mtime; #else m_filelastmod = srcstat.st_mtimespec.tv_sec; #endif } else { m_filelastmod = 0; } } void WDL_CursesEditor::indentSelect(int amt) { if (m_selecting) // remove selected text { int miny,maxy,minx,maxx; int x; getselectregion(minx,miny,maxx,maxy); if (maxy >= miny) { m_select_x1 = 0; m_select_y1 = miny; m_select_y2 = maxy; m_select_x2 = maxx; if (maxx<1 && maxy>miny) maxy--; // exclude empty final line if (m_curs_y >= miny && m_curs_y <=maxy) { m_curs_x += amt; if (m_curs_x<0)m_curs_x=0; } if (amt<0) { int minspc=-amt; for (x = miny; x <= maxy; x ++) { WDL_FastString *s=m_text.Get(x); if (s) { int a=0; while (aGet()[a]== ' ') a++; minspc = a; } } if (minspc>0 && minspc < -amt) amt = -minspc; } for (x = miny; x <= maxy; x ++) { WDL_FastString *s=m_text.Get(x); if (s) { if (amt>0) { int a; for(a=0;aInsert(" ",0,wdl_min(amt-a,16)); } else if (amt<0) { int a=0; while (a<-amt && s->Get()[a]== ' ') a++; s->DeleteSub(0,a); } } if (x==m_select_y2) m_select_x2 = s ? s->GetLength() : 0; } } } } void WDL_CursesEditor::removeSelect() { if (m_selecting) // remove selected text { int miny,maxy,minx,maxx; int x; getselectregion(minx,miny,maxx,maxy); m_curs_x = minx; m_curs_y = miny; if (m_curs_y < 0) m_curs_y=0; if (minx != maxx|| miny != maxy) { int fht=0,lht=0; for (x = miny; x <= maxy; x ++) { WDL_FastString *s=m_text.Get(x); if (s) { const int sx=x == miny ? WDL_utf8_charpos_to_bytepos(s->Get(),minx) : 0; const int ex=x == maxy ? WDL_utf8_charpos_to_bytepos(s->Get(),maxx) : s->GetLength(); if (x != maxy && sx == 0 && ex == s->GetLength()) // remove entire line { m_text.Delete(x,true); if (x==miny) miny--; x--; maxy--; } else { if (x==miny) fht=1; if (x == maxy) lht=1; s->DeleteSub(sx,ex-sx); } } } if (fht && lht && miny+1 == maxy) { m_text.Get(miny)->Append(m_text.Get(maxy)->Get()); m_text.Delete(maxy,true); } } m_selecting=0; if (m_curs_y >= m_text.GetSize()) { m_curs_y = m_text.GetSize(); m_text.Add(new WDL_FastString); } } } static const char *skip_indent(const char *tstr, int skipsz) { while (*tstr == ' ' && skipsz-->0) tstr++; return tstr; } static WDL_FastString *newIndentedFastString(const char *tstr, int indent_to_pos) { WDL_FastString *s=new WDL_FastString; if (indent_to_pos>=0) { int x; for (x=0;xAppend(" "); } s->Append(tstr); return s; } void WDL_CursesEditor::highlight_line(int line) { if (line >= 0 && line <= m_text.GetSize()) { bool nosel=false; if (line == m_text.GetSize()) { line--; nosel=true; } m_curs_x=0; m_curs_y=line; WDL_FastString* s=m_text.Get(line); if (s && s->GetLength()) { if (nosel) m_curs_x = WDL_utf8_get_charlen(s->Get()); else { m_select_x1=0; m_select_x2=WDL_utf8_get_charlen(s->Get()); m_select_y1=m_select_y2=m_curs_y; m_selecting=1; } draw(); } setCursor(); } } void WDL_CursesEditor::GoToLine(int line, bool dosel) { WDL_FastString *fs = m_text.Get(line); m_curs_y=line; m_select_x1=0; m_curs_x = m_select_x2 = WDL_NORMALLY(fs) ? WDL_utf8_get_charlen(fs->Get()) : 0; m_select_y1=m_select_y2=line; m_selecting=dosel?1:0; setCursor(0,0.25); } // tweak tokens to make them more useful in searching static void tweak_tok(const char *tok, const char **np, int *len) { int l = *len; if (WDL_NOT_NORMALLY(*np != tok+l)) return; if (WDL_NOT_NORMALLY(l < 1)) return; if (l == 1) { if ((tok[0] == '=' || tok[0] == '!' || tok[0] == '<' || tok[0] == '>') && tok[1] == '=') { l = (tok[0] == '=' || tok[0] == '!') && tok[2] == '=' ? 3 : 2; *np = tok + l; *len = l; } } else if (*tok == '.') { *len = 1; *np = tok + 1; } else { const char *p = tok; while (++p < tok+l) { if (*p == '.') { *len = (int) (p-tok); *np = p; break; } } } } int WDL_CursesEditor::search_line(const char *str, const WDL_FastString *line, int startx, bool backwards, int *match_len) // returns offset of next match, or -1 if none { const char *p = line->Get(); const int linelen = line->GetLength(); const int srchlen = (int) strlen(str); if (s_search_mode == 4 || s_search_mode == 5) { const char *lptr = p; int best_match = -1, best_match_len = 0, lstate = 0; for (;;) { int llen=0; const char *ltok = sh_tokenize(&lptr, p + linelen, &llen,&lstate); if (!ltok) break; if (!lstate) tweak_tok(ltok,&lptr,&llen); const int ltok_offs = (int) (ltok - p); if (backwards && ltok_offs > startx) break; // see if the sequence of tokens at ltok matches const char *aptr = str, *bptr = ltok; int astate=0, bstate=lstate; int last_endtok_offs = ltok_offs; for (;;) { int alen=0, blen=0; const char *atok = sh_tokenize(&aptr, str+srchlen, &alen,&astate); if (!atok) // end of search term { if (backwards || ltok_offs >= startx) { best_match = ltok_offs; best_match_len = last_endtok_offs - ltok_offs; } break; } const char *btok = sh_tokenize(&bptr, p+linelen, &blen, &bstate); if (!btok) break; // end of line without match if (!astate) tweak_tok(atok,&aptr,&alen); if (!bstate) tweak_tok(btok,&bptr,&blen); if (alen != blen || ((s_search_mode&1) ? strncmp(atok,btok,alen) : strnicmp(atok,btok,alen))) break; last_endtok_offs = (int) (btok + blen - p); } if (!backwards && best_match>=0) break; } if (match_len) *match_len = best_match_len; return best_match; } if (match_len) *match_len = srchlen; const int dstartx = backwards?-1:1; for (; backwards ? (startx>=0) : (startx <= linelen-srchlen); startx+=dstartx) if ((s_search_mode&1) ? !strncmp(p+startx,str,srchlen) : !strnicmp(p+startx,str,srchlen)) { if (!(s_search_mode&2)) return startx; if ((startx==0 || p[startx-1]<0 || !isalnum(p[startx-1])) && (p[startx+srchlen]<=0 || !isalnum(p[startx+srchlen]))) return startx; } return -1; } static const char *ellipsify(const char *str, char *buf, int bufsz) { int str_len = (int) strlen(str); if (str_len < bufsz-8 || bufsz < 8) return str; int l1 = 0; while (l1 < bufsz/2) { if (WDL_NOT_NORMALLY(!str[l1])) return str; int sz=wdl_utf8_parsechar(str+l1,NULL); l1+=sz; } int l2 = l1; while (l1 + (str_len - l2) > bufsz-4) { if (WDL_NOT_NORMALLY(!str[l2])) return str; int sz=wdl_utf8_parsechar(str+l2,NULL); l2+=sz; } snprintf(buf,bufsz,"%.*s...%s",l1,str,str+l2); return buf; } void WDL_CursesEditor::runSearch(bool backwards, bool replaceAll) { char buf[512]; buf[0]=0; if (replaceAll && m_search_string.GetLength()) { preSaveUndoState(); int cnt = 0, linecnt = 0; const int numlines = m_text.GetSize(); for (int line = 0; line < numlines; line ++) { WDL_FastString *tl = m_text.Get(line); if (WDL_NOT_NORMALLY(!tl)) continue; bool repl = false; int startx = 0; while (startx < tl->GetLength()) { int matchlen=0; int bytepos = search_line(m_search_string.Get(),tl, startx, false, &matchlen); if (bytepos < 0) break; int tbp, cursx_bytepos = -1, selx1_bytepos=-1, selx2_bytepos=-1; #define PRECHK_PAIR(py, px, bpv) \ if ((py) == line && (tbp=WDL_utf8_charpos_to_bytepos(tl->Get(),(px))) >= bytepos) { \ if (tbp < bytepos + matchlen) (px) = WDL_utf8_bytepos_to_charpos(tl->Get(),bytepos); \ else (bpv) = tbp; \ } PRECHK_PAIR(m_curs_y,m_curs_x,cursx_bytepos) PRECHK_PAIR(m_select_y1,m_select_x1,selx1_bytepos) PRECHK_PAIR(m_select_y2,m_select_x2,selx2_bytepos) tl->DeleteSub(bytepos,matchlen); tl->Insert(m_replace_string.Get(),bytepos); #define POSTCHK_PAIR(px, bpv) \ if ((bpv) >= 0) (px) = WDL_utf8_bytepos_to_charpos(tl->Get(),(bpv) - matchlen + m_replace_string.GetLength()); POSTCHK_PAIR(m_curs_x,cursx_bytepos) POSTCHK_PAIR(m_select_x1,selx1_bytepos); POSTCHK_PAIR(m_select_x2,selx2_bytepos); #undef PRECHK_PAIR #undef POSTCHK_PAIR startx = bytepos + m_replace_string.GetLength(); repl = true; cnt++; } if (repl) linecnt++; } if (cnt) snprintf(buf,sizeof(buf),"Replaced %d instance%s on %d line%s",cnt, cnt==1?"":"s", linecnt, linecnt==1?"":"s"); saveUndoState(); } else if (m_search_string.GetLength()) { char elbuf[50]; const int numlines = m_text.GetSize(); if (numlines) for (int y = 0; y <= numlines; y ++) { int line = m_curs_y + (backwards ? -y : y), wrapflag=0; if (line >= numlines) { line -= numlines; wrapflag = 1; } else if (line < 0) { line += numlines; wrapflag = 1; } WDL_FastString *tl = m_text.Get(line); if (WDL_NOT_NORMALLY(!tl)) continue; const int startx = y==0 ? (backwards ? m_curs_x-1 : m_curs_x+1) : (backwards ? tl->GetLength()-1 : 0); if (backwards && startx<0) continue; int matchlen=0; int bytepos = search_line(m_search_string.Get(),tl, startx > 0 ? WDL_utf8_charpos_to_bytepos(tl->Get(),startx) : 0, backwards, &matchlen); if (bytepos >= 0) { m_select_y1=m_select_y2=m_curs_y=line; m_select_x1=m_curs_x=WDL_utf8_bytepos_to_charpos(tl->Get(),bytepos); m_select_x2=WDL_utf8_bytepos_to_charpos(tl->Get(),bytepos+matchlen); m_selecting=1; // make sure the end is on screen m_offs_x = 0; m_curs_x = wdl_max(m_select_x1,m_select_x2) + COLS/4; setCursor(); m_curs_x = m_select_x1; snprintf(buf,sizeof(buf),"Found @ Line %d Col %d %s'%s' (Shift+)F3|" CONTROL_KEY_NAME "+G:(prev)next",m_curs_y+1,m_curs_x+1,wrapflag?"(wrapped) ":"",ellipsify(m_search_string.Get(),elbuf,sizeof(elbuf))); break; } } if (!buf[0]) snprintf(buf,sizeof(buf),"%s '%s' not found",searchmode_desc(s_search_mode),ellipsify(m_search_string.Get(),elbuf,sizeof(elbuf))); } draw(); setCursor(); if (buf[0]) draw_message(buf); } static int categorizeCharForWordNess(int c) { if (c >= 0 && c < 256) { if (isspace(c)) return 0; if (isalnum(c) || c == '_') return 1; if (c == ';') return 2; // I prefer this, since semicolons are somewhat special } return 3; } #define CTRL_KEY_DOWN (GetAsyncKeyState(VK_CONTROL)&0x8000) #define SHIFT_KEY_DOWN (GetAsyncKeyState(VK_SHIFT)&0x8000) #define ALT_KEY_DOWN (GetAsyncKeyState(VK_MENU)&0x8000) void WDL_CursesEditor::getLinesFromClipboard(WDL_FastString &buf, WDL_PtrList &lines) { #ifdef WDL_IS_FAKE_CURSES if (CURSES_INSTANCE) { HANDLE h; OpenClipboard(CURSES_INSTANCE->m_hwnd); #ifdef CF_UNICODETEXT h=GetClipboardData(CF_UNICODETEXT); if (h) { wchar_t *t=(wchar_t *)GlobalLock(h); int s=(int)(GlobalSize(h)/2); while (s-- > 0) { char b[32]; if (!*t) break; WDL_MakeUTFChar(b,*t++,sizeof(b)); buf.Append(b); } GlobalUnlock(t); } #endif if (!buf.GetLength()) { h=GetClipboardData(CF_TEXT); if (h) { char *t=(char *)GlobalLock(h); int s=(int)(GlobalSize(h)); buf.Set(t,s); GlobalUnlock(t); } } CloseClipboard(); } else #endif { buf.Set(s_fake_clipboard.Get()); } if (buf.Get() && buf.Get()[0]) { ReplaceTabs(&buf,m_indent_size); char *src=(char*)buf.Get(); while (*src) { char *seek=src; while (*seek && *seek != '\r' && *seek != '\n') seek++; char hadclr=*seek; if (*seek) *seek++=0; lines.Add(src); if (hadclr == '\r' && *seek == '\n') seek++; if (hadclr && !*seek) { lines.Add(""); } src=seek; } } } void WDL_CursesEditor::run_line_editor(int c, WDL_FastString *fs) { char tmp[512]; switch (c) { case -1: break; case 0: m_line_editor_edited=false; draw_top_line(); break; case 27: draw(); setCursor(); // fallthrough case '\r': case '\n': m_ui_state=UI_STATE_NORMAL; return; case KEY_BACKSPACE: if (!fs->GetLength()) return; { const char *p = fs->Get(); for (int x = 0;;) { int sz=wdl_utf8_parsechar(p+x,NULL); if (!p[x+sz]) { fs->SetLen(x); m_line_editor_edited=true; break; } x+=sz; } } break; case KEY_IC: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) return; WDL_FALLTHROUGH; case 'V'-'A'+1: { WDL_PtrList lines; WDL_FastString buf; getLinesFromClipboard(buf,lines); if (!lines.Get(0)) return; if (!m_line_editor_edited) { fs->Set(""); m_line_editor_edited=true; } fs->Append(lines.Get(0)); } break; case KEY_UP: if (m_ui_state == UI_STATE_REPLACE || m_ui_state == UI_STATE_SEARCH) { if (--s_search_mode<0) s_search_mode=5; } break; case KEY_DOWN: if (m_ui_state == UI_STATE_REPLACE || m_ui_state == UI_STATE_SEARCH) { if (++s_search_mode>5) s_search_mode=0; } break; default: if (!VALIDATE_TEXT_CHAR(c)) return; if (!m_line_editor_edited) fs->Set(""); m_line_editor_edited=true; WDL_MakeUTFChar(tmp,c,sizeof(tmp)); fs->Append(tmp); break; } const char *search_help = COLS > 70 ? " (Up/Down:mode " CONTROL_KEY_NAME "+R:replace ESC:cancel)" : COLS > 40 ? "(U/D:mode " CONTROL_KEY_NAME "+R:repl)" : ""; char elbuf[50]; switch (m_ui_state) { case UI_STATE_REPLACE: snprintf(tmp,sizeof(tmp),"Replace all (%s) '%s': ",searchmode_desc(s_search_mode),ellipsify(m_search_string.Get(),elbuf,sizeof(elbuf))); break; case UI_STATE_SEARCH: snprintf(tmp,sizeof(tmp),"Find %s%s: ",searchmode_desc(s_search_mode), search_help); break; case UI_STATE_GOTO_LINE: lstrcpyn_safe(tmp, "Go to line (ESC:cancel): ",sizeof(tmp)); break; default: return; } attrset(COLOR_MESSAGE); bkgdset(COLOR_MESSAGE); mvaddstr(LINES-1,0,tmp); addstr(fs->Get()); clrtoeol(); attrset(0); bkgdset(0); } int WDL_CursesEditor::onChar(int c) { switch (m_ui_state) { case UI_STATE_MESSAGE: m_ui_state=UI_STATE_NORMAL; draw(); setCursor(); break; case UI_STATE_SAVE_ON_CLOSE: if (c>=0 && (isalnum(c) || isprint(c) || c==27)) { if (c == 27) { m_ui_state=UI_STATE_NORMAL; draw(); draw_message("Cancelled close of file."); setCursor(); return 0; } if (toupper(c) == 'N' || toupper(c) == 'Y') { if (toupper(c) == 'Y') { if(updateFile()) { m_ui_state=UI_STATE_NORMAL; draw(); draw_message("Error writing file, changes not saved!"); setCursor(); return 0; } } CloseCurrentTab(); delete this; // this no longer valid, return 1 to avoid future calls in onChar() return 1; } } return 0; case UI_STATE_SAVE_AS_NEW: if (c>=0 && (isalnum(c) || isprint(c) || c==27 || c == '\r' || c=='\n')) { m_ui_state=UI_STATE_NORMAL; if (toupper(c) == 'N' || c == 27) { draw(); draw_message("Cancelled create new file."); setCursor(); return 0; } AddTab(m_newfn.Get()); } return 0; case UI_STATE_SEARCH: case UI_STATE_REPLACE: { uiState chgstate = c == 1+'R'-'A' ? UI_STATE_REPLACE : c == 1+'F'-'A' ? UI_STATE_SEARCH : UI_STATE_NORMAL; if (chgstate != UI_STATE_NORMAL) { if (chgstate == UI_STATE_REPLACE && !m_search_string.GetLength()) chgstate = UI_STATE_SEARCH; c = m_ui_state == chgstate ? 27 : 0; m_ui_state = chgstate; } const bool is_replace = m_ui_state == UI_STATE_REPLACE; run_line_editor(c,is_replace ? &m_replace_string : &m_search_string); if (c == '\r' || c == '\n') runSearch(false, is_replace); } return 0; case UI_STATE_GOTO_LINE: if (c == 1+'J'-'A') c=27; run_line_editor(c,&s_goto_line_buf); if (c == '\r' || c == '\n') { const char *p = s_goto_line_buf.Get(); while (*p == ' ' || *p == '\t') p++; int rel = 0; if (*p == '+') rel=1; else if (*p == '-') rel=-1; if (rel) p++; while (*p == ' ' || *p == '\t') p++; int a = atoi(p); if (a > 0) { if (rel) a = m_curs_y + a*rel; else a--; m_curs_y = wdl_clamp(a,0,m_text.GetSize()); WDL_FastString *fs = m_text.Get(m_curs_y); m_curs_x = fs ? WDL_utf8_get_charlen(fs->Get()) : 0; m_selecting=0; draw(); setCursor(0,-1.0); } else { draw(); setCursor(); } } return 0; case UI_STATE_NORMAL: break; } // UI_STATE_NORMAL WDL_ASSERT(m_ui_state == UI_STATE_NORMAL /* unhandled state? */); if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN && c =='W'-'A'+1) { if (GetTab(0) == this) return 0; // first in list = do nothing if (IsDirty()) { m_ui_state=UI_STATE_SAVE_ON_CLOSE; attrset(COLOR_MESSAGE); bkgdset(COLOR_MESSAGE); mvaddstr(LINES-1,0,"Save file before closing (y/N)? "); clrtoeol(); attrset(0); bkgdset(0); } else { CloseCurrentTab(); delete this; // context no longer valid! return 1; } return 0; } if ((c==27 || c==29 || (c >= KEY_F1 && c<=KEY_F10)) && CTRL_KEY_DOWN) { int idx=c-KEY_F1; bool rel=true; if (c==27) idx=-1; else if (c==29) idx=1; else rel=false; SwitchTab(idx,rel); return 1; } if (c==KEY_DOWN || c==KEY_UP || c==KEY_PPAGE||c==KEY_NPAGE || c==KEY_RIGHT||c==KEY_LEFT||c==KEY_HOME||c==KEY_END) { if (SHIFT_KEY_DOWN) { if (!m_selecting) { m_select_x2=m_select_x1=m_curs_x; m_select_y2=m_select_y1=m_curs_y; m_selecting=1; } } else if (m_selecting) { m_selecting=0; if (c == KEY_LEFT || c == KEY_RIGHT || c == KEY_UP || c == KEY_DOWN) { int miny,maxy,minx,maxx; getselectregion(minx,miny,maxx,maxy); if ((m_curs_y > miny || (m_curs_y == miny && m_curs_x >= minx)) && (m_curs_y < maxy || (m_curs_y == maxy && m_curs_x <= maxx))) { m_curs_x = c == KEY_LEFT || c == KEY_UP ? minx : maxx; m_curs_y = c == KEY_LEFT || c == KEY_UP ? miny : maxy; draw(); setCursor(); return 0; } } draw(); } } switch(c) { case 'O'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { if (m_pane_div <= 0.0 || m_pane_div >= 1.0) { onChar('P'-'A'+1); } if (m_pane_div > 0.0 && m_pane_div < 1.0) { m_curpane=!m_curpane; draw(); draw_status_state(); int paney[2], paneh[2]; GetPaneDims(paney, paneh); if (m_curs_y-m_paneoffs_y[m_curpane] < 0) m_curs_y=m_paneoffs_y[m_curpane]; else if (m_curs_y-m_paneoffs_y[m_curpane] >= paneh[m_curpane]) m_curs_y=paneh[m_curpane]+m_paneoffs_y[m_curpane]-1; setCursor(); } } break; case 'P'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { if (m_pane_div <= 0.0 || m_pane_div >= 1.0) { m_pane_div=0.5; m_paneoffs_y[1]=m_paneoffs_y[0]; } else { m_pane_div=1.0; if (m_curpane) m_paneoffs_y[0]=m_paneoffs_y[1]; m_curpane=0; } draw(); draw_status_state(); int paney[2], paneh[2]; GetPaneDims(paney, paneh); setCursor(); } break; case 407: case 'Z'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { if (m_undoStack_pos > 0) { m_undoStack_pos--; loadUndoState(m_undoStack.Get(m_undoStack_pos),1); draw(); setCursor(); char buf[512]; snprintf(buf,sizeof(buf),"Undid action - %d items in undo buffer",m_undoStack_pos); draw_message(buf); } else { draw_message("Can't Undo"); } break; } // fall through case 'Y'-'A'+1: if ((c == 'Z'-'A'+1 || !SHIFT_KEY_DOWN) && !ALT_KEY_DOWN) { if (m_undoStack_pos < m_undoStack.GetSize()-1) { m_undoStack_pos++; loadUndoState(m_undoStack.Get(m_undoStack_pos),0); draw(); setCursor(); char buf[512]; snprintf(buf,sizeof(buf),"Redid action - %d items in redo buffer",m_undoStack.GetSize()-m_undoStack_pos-1); draw_message(buf); } else { draw_message("Can't Redo"); } } break; case KEY_IC: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { s_overwrite=!s_overwrite; setCursor(); break; } // fall through case 'V'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { // generate a m_clipboard using win32 clipboard data WDL_PtrList lines; WDL_FastString buf; getLinesFromClipboard(buf,lines); if (lines.GetSize()) { removeSelect(); // insert lines at m_curs_y,m_curs_x if (m_curs_y > m_text.GetSize()) m_curs_y=m_text.GetSize(); if (m_curs_y < 0) m_curs_y=0; preSaveUndoState(); do_paste_lines(lines); draw(); setCursor(); draw_message("Pasted"); saveUndoState(); } else { setCursor(); draw_message("Clipboard empty"); } } break; case KEY_DC: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { WDL_FastString *s; if (m_selecting) { preSaveUndoState(); removeSelect(); draw(); saveUndoState(); setCursor(); } else if ((s=m_text.Get(m_curs_y))) { const int xbyte = WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x); if (xbyte < s->GetLength()) { preSaveUndoState(); const int xbytesz=WDL_utf8_charpos_to_bytepos(s->Get()+xbyte,1); bool hadCom = LineCanAffectOtherLines(s->Get(),xbyte,xbytesz); s->DeleteSub(xbyte,xbytesz); if (!hadCom) hadCom = LineCanAffectOtherLines(s->Get(),xbyte,0); draw(hadCom ? -1 : m_curs_y); saveUndoState(); setCursor(); } else // append next line to us { if (m_curs_y < m_text.GetSize()-1) { preSaveUndoState(); WDL_FastString *nl=m_text.Get(m_curs_y+1); if (nl) { const char *p = nl->Get(); while ((*p == ' ' || *p == '\t') && (p[1] == ' ' || p[1] == '\t')) p++; s->Append(p); } m_text.Delete(m_curs_y+1,true); draw(); saveUndoState(); setCursor(); } } } break; } WDL_FALLTHROUGH; case 'C'-'A'+1: case 'X'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN && m_selecting) { if (c!= 'C'-'A'+1) m_selecting=0; int miny,maxy,minx,maxx; int x; getselectregion(minx,miny,maxx,maxy); const char *status=""; char statusbuf[512]; if (minx != maxx|| miny != maxy) { s_fake_clipboard.Set(""); int lht=0,fht=0; if (c != 'C'-'A'+1) preSaveUndoState(); for (x = miny; x <= maxy; x ++) { WDL_FastString *s=m_text.Get(x); if (s) { const char *str=s->Get(); int sx=x == miny ? WDL_utf8_charpos_to_bytepos(s->Get(),minx) : 0; const int ex=x == maxy ? WDL_utf8_charpos_to_bytepos(s->Get(),maxx) : s->GetLength(); if (x != miny) s_fake_clipboard.Append("\r\n"); const int oldlen = s_fake_clipboard.GetLength(); if (x == miny && maxy > miny && sx > 0) { // first line of multi-line copy, not starting at first byte of line int icnt = 0; while (str[icnt] == ' ') icnt++; for (int nl = x+1; icnt > 0 && nl <= maxy; nl ++) { WDL_FastString *cs = m_text.Get(nl); if (cs) { int c = 0; while (c < icnt && cs->Get()[c] == ' ') c++; if (c < icnt && cs->Get()[c]) { if (nl == maxy && c >= WDL_utf8_charpos_to_bytepos(cs->Get(),maxx)) break; // last line, ignore if whitespace exceeds what we're copying anyway icnt = 0; } } } // if all later copied lines do not have non-whitespace in the first line's indentation if (icnt > 0) { // then we'll copy that line's indentation s_fake_clipboard.Append(str,icnt); // and skip any whitespace in our selection while (str[sx] == ' ') sx++; } } if (ex>sx) s_fake_clipboard.Append(str+sx,ex-sx); if (m_write_leading_tabs>0 && sx==0 && oldlen < s_fake_clipboard.GetLength()) { const char *p = s_fake_clipboard.Get() + oldlen; int nt=0; const char *sp = countLeadingTabs(p,&nt,m_write_leading_tabs); if (nt && sp > p) { s_fake_clipboard.DeleteSub(oldlen,(int)(sp-p)); while (nt>0) { int c = nt; if (c > 8) c=8; nt -= c; s_fake_clipboard.Insert("\t\t\t\t\t\t\t\t",oldlen,c); } } } if (c != 'C'-'A'+1) { if (sx == 0 && ex == s->GetLength()) // remove entire line { m_text.Delete(x,true); if (x==miny) miny--; x--; maxy--; } else { if (x==miny) fht=1; if (x == maxy) lht=1; s->DeleteSub(sx,ex-sx); } } } } if (fht && lht && miny+1 == maxy) { m_text.Get(miny)->Append(m_text.Get(maxy)->Get()); m_text.Delete(maxy,true); } if (c != 'C'-'A'+1) { m_curs_y=miny; if (m_curs_y < 0) m_curs_y=0; m_curs_x=minx; saveUndoState(); snprintf(statusbuf,sizeof(statusbuf),"Cut %d bytes",s_fake_clipboard.GetLength()); } else snprintf(statusbuf,sizeof(statusbuf),"Copied %d bytes",s_fake_clipboard.GetLength()); #ifdef WDL_IS_FAKE_CURSES if (CURSES_INSTANCE) { #ifdef CF_UNICODETEXT const int l=(WDL_utf8_get_charlen(s_fake_clipboard.Get())+1)*sizeof(wchar_t); HANDLE h=GlobalAlloc(GMEM_MOVEABLE,l); wchar_t *t=(wchar_t*)GlobalLock(h); if (t) { WDL_MBtoWideStr(t,s_fake_clipboard.Get(),l); GlobalUnlock(h); } OpenClipboard(CURSES_INSTANCE->m_hwnd); EmptyClipboard(); SetClipboardData(CF_UNICODETEXT,h); #else int l=s_fake_clipboard.GetLength()+1; HANDLE h=GlobalAlloc(GMEM_MOVEABLE,l); void *t=GlobalLock(h); if (t) { memcpy(t,s_fake_clipboard.Get(),l); GlobalUnlock(h); } OpenClipboard(CURSES_INSTANCE->m_hwnd); EmptyClipboard(); SetClipboardData(CF_TEXT,h); #endif CloseClipboard(); } #endif status=statusbuf; } else status="No selection"; draw(); setCursor(); draw_message(status); } break; case 'D'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { if (m_selecting) { int miny,maxy,minx,maxx; getselectregion(minx,miny,maxx,maxy); if (minx != maxx|| miny != maxy) { WDL_FastString dup; int x; for (x = miny; x <= maxy; x ++) { WDL_FastString *s=m_text.Get(x); if (s) { const char *str=s->Get(); const int sx=x == miny ? WDL_utf8_charpos_to_bytepos(str,minx) : 0; const int ex=x == maxy ? WDL_utf8_charpos_to_bytepos(str,maxx) : s->GetLength(); if (dup.GetLength()) dup.Append("\n"); dup.Append(ex-sx?str+sx:"",ex-sx); } } if (dup.GetLength()) { preSaveUndoState(); WDL_PtrList lines; char *p = (char *)dup.Get(); for (;;) { const char *basep = p; while (*p && *p != '\n') p++; lines.Add(basep); if (!*p) break; *p++=0; } m_curs_x=maxx; m_curs_y=maxy; do_paste_lines(lines); m_curs_x=maxx; m_curs_y=maxy; draw(); setCursor(); draw_message("Duplicated selection"); saveUndoState(); } } } else if (m_text.Get(m_curs_y)) { preSaveUndoState(); m_text.Insert(m_curs_y, new WDL_FastString(m_text.Get(m_curs_y))); draw(); setCursor(); draw_message("Duplicated line"); saveUndoState(); } } break; case 'A'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { m_selecting=1; m_select_x1=0; m_select_y1=0; m_select_y2=m_text.GetSize()-1; m_select_x2=0; if (m_text.Get(m_select_y2)) m_select_x2=m_text.Get(m_select_y2)->GetLength(); draw(); setCursor(); } break; case 27: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN && m_selecting) { m_selecting=0; draw(); setCursor(); break; } break; case KEY_F3: case 'G'-'A'+1: if (!ALT_KEY_DOWN && m_search_string.GetLength()) { runSearch(SHIFT_KEY_DOWN != 0, false); return 0; } WDL_FALLTHROUGH; // fall through case 'R'-'A'+1: case 'F'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { if (m_selecting && m_select_y1==m_select_y2) { WDL_FastString* s=m_text.Get(m_select_y1); if (s) { const char* p=s->Get(); int xlo=wdl_min(m_select_x1, m_select_x2); int xhi=wdl_max(m_select_x1, m_select_x2); xlo = WDL_utf8_charpos_to_bytepos(p,xlo); xhi = WDL_utf8_charpos_to_bytepos(p,xhi); if (xhi > xlo) { m_search_string.Set(p+xlo, xhi-xlo); } } } if (c == 'R'-'A'+1 && !m_search_string.GetLength()) { draw_message("Use " CONTROL_KEY_NAME "+F first, or run " CONTROL_KEY_NAME "+R from Find prompt"); } else { m_ui_state=c == 'R'-'A'+1 ? UI_STATE_REPLACE : UI_STATE_SEARCH; run_line_editor(0,m_ui_state == UI_STATE_REPLACE ? &m_replace_string : &m_search_string); } } break; case KEY_DOWN: { if (CTRL_KEY_DOWN) { int paney[2], paneh[2]; GetPaneDims(paney, paneh); int maxscroll=m_text.GetSize()-paneh[m_curpane]+4; if (m_paneoffs_y[m_curpane] < maxscroll-1) { m_paneoffs_y[m_curpane]++; if (m_curs_y < m_paneoffs_y[m_curpane]) m_curs_y=m_paneoffs_y[m_curpane]; draw(); } } else { m_curs_y++; if (m_curs_y>=m_text.GetSize()) m_curs_y=m_text.GetSize()-1; if (m_curs_y < 0) m_curs_y=0; } if (m_selecting) { setCursor(1); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); } setCursor(1); } break; case KEY_UP: { if (CTRL_KEY_DOWN) { if (m_paneoffs_y[m_curpane] > 0) { int paney[2], paneh[2]; GetPaneDims(paney, paneh); m_paneoffs_y[m_curpane]--; if (m_curs_y > m_paneoffs_y[m_curpane]+paneh[m_curpane]-1) m_curs_y = m_paneoffs_y[m_curpane]+paneh[m_curpane]-1; if (m_curs_y < 0) m_curs_y=0; draw(); } } else { if(m_curs_y>0) m_curs_y--; } if (m_selecting) { setCursor(1); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); } setCursor(1); } break; case KEY_PPAGE: { if (m_curs_y > m_paneoffs_y[m_curpane]) { m_curs_y=m_paneoffs_y[m_curpane]; if (m_curs_y < 0) m_curs_y=0; } else { int paney[2], paneh[2]; GetPaneDims(paney, paneh); m_curs_y -= paneh[m_curpane]; if (m_curs_y < 0) m_curs_y=0; m_paneoffs_y[m_curpane]=m_curs_y; } if (m_selecting) { setCursor(1); m_select_x2=m_curs_x; m_select_y2=m_curs_y; } draw(); setCursor(1); } break; case KEY_NPAGE: { int paney[2], paneh[2]; GetPaneDims(paney, paneh); if (m_curs_y >= m_paneoffs_y[m_curpane]+paneh[m_curpane]-1) m_paneoffs_y[m_curpane]=m_curs_y-1; m_curs_y = m_paneoffs_y[m_curpane]+paneh[m_curpane]-1; if (m_curs_y >= m_text.GetSize()) m_curs_y=m_text.GetSize()-1; if (m_curs_y < 0) m_curs_y=0; if (m_selecting) { setCursor(1); m_select_x2=m_curs_x; m_select_y2=m_curs_y; } draw(); setCursor(1); } break; case KEY_RIGHT: { if (1) // wrap across lines { WDL_FastString *s = m_text.Get(m_curs_y); if (s && m_curs_x >= WDL_utf8_get_charlen(s->Get()) && m_curs_y < m_text.GetSize()) { m_curs_y++; m_curs_x = -1; } } if(m_curs_x<0) { m_curs_x=0; } else { if (CTRL_KEY_DOWN) { WDL_FastString *s = m_text.Get(m_curs_y); if (!s) break; int bytepos = WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x); if (bytepos >= s->GetLength()) break; int lastType = categorizeCharForWordNess(s->Get()[bytepos++]); while (bytepos < s->GetLength()) { int thisType = categorizeCharForWordNess(s->Get()[bytepos]); if (thisType != lastType && thisType != 0) break; lastType=thisType; bytepos++; } m_curs_x = WDL_utf8_bytepos_to_charpos(s->Get(),bytepos); } else { m_curs_x++; } } if (m_selecting) { setCursor(); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); } setCursor(); } break; case KEY_LEFT: { bool doMove=true; if (1) // wrap across lines { WDL_FastString *s = m_text.Get(m_curs_y); if (s && m_curs_y>0 && m_curs_x == 0) { s = m_text.Get(--m_curs_y); if (s) { m_curs_x = WDL_utf8_get_charlen(s->Get()); doMove=false; } } } if(m_curs_x>0 && doMove) { if (CTRL_KEY_DOWN) { WDL_FastString *s = m_text.Get(m_curs_y); if (!s) break; int bytepos = WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x); if (bytepos > s->GetLength()) bytepos = s->GetLength(); bytepos--; int lastType = categorizeCharForWordNess(s->Get()[bytepos--]); while (bytepos >= 0) { int thisType = categorizeCharForWordNess(s->Get()[bytepos]); if (thisType != lastType && lastType != 0) break; lastType=thisType; bytepos--; } m_curs_x = WDL_utf8_bytepos_to_charpos(s->Get(),bytepos+1); } else { m_curs_x--; } } if (m_selecting) { setCursor(); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); } setCursor(); } break; case KEY_HOME: { const int old_x = m_curs_x; m_curs_x=0; if (!CTRL_KEY_DOWN) { const WDL_FastString *ln = m_text.Get(m_curs_y); if (ln) { int i = 0; while (ln->Get()[i] == ' ' || ln->Get()[i] == '\t') i++; if (ln->Get()[i] && i != old_x) m_curs_x = i; } } else m_curs_y=0; if (m_selecting) { setCursor(); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); } m_want_x=-1; // clear in case we're already at the start of the line setCursor(); } break; case KEY_END: { if (CTRL_KEY_DOWN) m_curs_y=wdl_max(m_text.GetSize()-1,0); const WDL_FastString *ln = m_text.Get(m_curs_y); if (ln) m_curs_x=WDL_utf8_get_charlen(ln->Get()); if (m_selecting) { setCursor(); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); } m_want_x=-1; // clear in case we're already at the end of the line setCursor(); } break; case KEY_BACKSPACE: // backspace, baby if (m_selecting) { preSaveUndoState(); removeSelect(); draw(); saveUndoState(); setCursor(); } else if (m_curs_x > 0) { WDL_FastString *tl=m_text.Get(m_curs_y); if (tl) { preSaveUndoState(); int del_sz=1; if (m_indent_size > 0 && !(m_curs_x % m_indent_size)) { const char *p = tl->Get(); int i=0; while (i=m_indent_size) { del_sz=m_indent_size; } } const int xbyte = WDL_utf8_charpos_to_bytepos(tl->Get(),m_curs_x - del_sz); const int xbytesz=WDL_utf8_charpos_to_bytepos(tl->Get()+xbyte,del_sz); bool hadCom = LineCanAffectOtherLines(tl->Get(), xbyte,xbytesz); tl->DeleteSub(xbyte,xbytesz); m_curs_x-=del_sz; if (!hadCom) hadCom = LineCanAffectOtherLines(tl->Get(),xbyte,0); draw(hadCom?-1:m_curs_y); saveUndoState(); setCursor(); } } else // append current line to previous line { WDL_FastString *fl=m_text.Get(m_curs_y-1), *tl=m_text.Get(m_curs_y); if (!tl) { m_curs_y--; if (fl) m_curs_x=WDL_utf8_get_charlen(fl->Get()); draw(); saveUndoState(); setCursor(); } else if (fl) { preSaveUndoState(); m_curs_x=WDL_utf8_get_charlen(fl->Get()); // if tl is all whitespace, don't bother appending to the previous line const char *p = tl->Get(); while (*p == ' ' || *p == '\t') p++; if (*p) fl->Append(tl->Get()); m_text.Delete(m_curs_y--,true); draw(); saveUndoState(); setCursor(); } } break; case 'L'-'A'+1: draw(); setCursor(); break; case 'J'-'A'+1: if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) { s_goto_line_buf.Set(""); m_ui_state=UI_STATE_GOTO_LINE; run_line_editor(0,&s_goto_line_buf); } break; case '\r': //KEY_ENTER: //insert newline preSaveUndoState(); if (m_selecting) { removeSelect(); draw(); setCursor(); } if (m_curs_y >= m_text.GetSize()) { m_curs_y=m_text.GetSize(); m_text.Add(new WDL_FastString); } if (s_overwrite) { WDL_FastString *s = m_text.Get(m_curs_y); int plen=0; const char *pb=NULL; if (s) { pb = s->Get(); while (plen < m_curs_x && (pb[plen]== ' ' || pb[plen] == '\t')) plen++; } if (++m_curs_y >= m_text.GetSize()) { m_curs_y = m_text.GetSize(); WDL_FastString *ns=new WDL_FastString; if (plen>0) ns->Set(pb,plen); m_text.Insert(m_curs_y,ns); } s = m_text.Get(m_curs_y); if (s && plen > s->GetLength()) plen=s->GetLength(); m_curs_x=s ? WDL_utf8_bytepos_to_charpos(s->Get(),plen) : plen; } else { WDL_FastString *s = m_text.Get(m_curs_y); if (s) { int bytepos = WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x); if (CTRL_KEY_DOWN || bytepos > s->GetLength()) bytepos = s->GetLength(); WDL_FastString *nl = new WDL_FastString(); int plen=0; const char *pb = s->Get(); while (pb[plen]== ' ' || pb[plen] == '\t') plen++; if (plen>0) nl->Set(pb,plen); const char *insert = pb+bytepos; while (*insert == ' ' || *insert == '\t') insert++; nl->Append(insert); m_text.Insert(++m_curs_y,nl); s->SetLen(bytepos); m_curs_x=WDL_utf8_bytepos_to_charpos(nl->Get(),plen); } } m_offs_x=0; draw(); saveUndoState(); setCursor(); break; case '\t': if (m_selecting) { preSaveUndoState(); bool isRev = !!(GetAsyncKeyState(VK_SHIFT)&0x8000); indentSelect(isRev?-m_indent_size:m_indent_size); // indent selection: draw(); setCursor(); saveUndoState(); break; } else if (GetAsyncKeyState(VK_SHIFT)&0x8000) { if (m_curs_x > 0) { WDL_FastString *tl=m_text.Get(m_curs_y); if (tl) { int del_pos = m_curs_x, del_sz; for (del_sz = 0; del_sz < m_curs_x && tl->Get()[del_sz] == ' '; del_sz++); if (del_sz < m_curs_x && tl->Get()[WDL_utf8_charpos_to_bytepos(tl->Get(),m_curs_x - 1)] == ' ') { // spaces before cursor but not at start of line for (del_sz = 1; m_curs_x - del_sz - 1 >= 0 && del_sz < wdl_max(m_indent_size,1) && tl->Get()[WDL_utf8_charpos_to_bytepos(tl->Get(),m_curs_x - del_sz - 1)] == ' '; del_sz++); } else if (del_sz > 0) { // adjust leading indentation int rem = 1; if (m_indent_size > 0) { rem = del_sz % m_indent_size; if (!rem) rem = m_indent_size; } if (del_sz > rem) del_sz = rem; del_pos = del_sz; } if (del_sz > 0) { preSaveUndoState(); const int xbyte = WDL_utf8_charpos_to_bytepos(tl->Get(),del_pos - del_sz); const int xbytesz=WDL_utf8_charpos_to_bytepos(tl->Get()+xbyte,del_sz); bool hadCom = LineCanAffectOtherLines(tl->Get(), xbyte,xbytesz); tl->DeleteSub(xbyte,xbytesz); m_curs_x-=del_sz; if (!hadCom) hadCom = LineCanAffectOtherLines(tl->Get(),xbyte,0); draw(hadCom?-1:m_curs_y); saveUndoState(); setCursor(); } } } break; } WDL_FALLTHROUGH; default: //insert char if(VALIDATE_TEXT_CHAR(c)) { preSaveUndoState(); if (m_selecting) { removeSelect(); draw(); setCursor(); } if (!m_text.Get(m_curs_y)) m_text.Insert(m_curs_y,new WDL_FastString); WDL_FastString *ss; if ((ss=m_text.Get(m_curs_y))) { char str[64]; int slen=1,slen_bytes=1; if (c == '\t') { slen = wdl_min(m_indent_size,64); if (slen<1) slen=1; const int partial = m_curs_x % slen; if (partial) slen -= partial; int x; for(x=0;xGet(),m_curs_x); bool hadCom = LineCanAffectOtherLines(ss->Get(),xbyte,0); if (s_overwrite) { const int xbytesz_del=WDL_utf8_charpos_to_bytepos(ss->Get()+xbyte,slen); if (!hadCom) hadCom = LineCanAffectOtherLines(ss->Get(),xbyte,xbytesz_del); ss->DeleteSub(xbyte,xbytesz_del); } ss->Insert(str,xbyte,slen_bytes); if (!hadCom) hadCom = LineCanAffectOtherLines(ss->Get(),xbyte,slen_bytes); m_curs_x += slen; draw(hadCom ? -1 : m_curs_y); } saveUndoState(); setCursor(); } break; } return 0; } void WDL_CursesEditor::preSaveUndoState() { editUndoRec *rec= m_undoStack.Get(m_undoStack_pos); if (rec) { rec->m_offs_x[1]=m_offs_x; rec->m_curs_x[1]=m_curs_x; rec->m_curs_y[1]=m_curs_y; rec->m_curpaneoffs_y[1]=m_paneoffs_y[m_curpane]; } } void WDL_CursesEditor::saveUndoState() { editUndoRec *rec=new editUndoRec; rec->m_offs_x[1]=rec->m_offs_x[0]=m_offs_x; rec->m_curs_x[1]=rec->m_curs_x[0]=m_curs_x; rec->m_curs_y[1]=rec->m_curs_y[0]=m_curs_y; rec->m_curpaneoffs_y[1]=rec->m_curpaneoffs_y[0]=m_paneoffs_y[m_curpane]; editUndoRec *lrec[5]; lrec[0] = m_undoStack.Get(m_undoStack_pos); lrec[1] = m_undoStack.Get(m_undoStack_pos+1); lrec[2] = m_undoStack.Get(m_undoStack_pos-1); lrec[3] = m_undoStack.Get(m_undoStack_pos-2); lrec[4] = m_undoStack.Get(m_undoStack_pos-3); int x; for (x = 0; x < m_text.GetSize(); x ++) { refcntString *ns = NULL; const WDL_FastString *s=m_text.Get(x); const int s_len=s?s->GetLength():0; if (s_len>0) { const char *cs=s?s->Get():""; int w; for(w=0;w<5 && !ns;w++) { if (lrec[w]) { int y; for (y=0; y<15; y ++) { refcntString *a = lrec[w]->m_htext.Get(x + ((y&1) ? -(y+1)/2 : y/2)); if (a && a->getStrLen() == s_len && !strcmp(a->getStr(),cs)) { ns = a; break; } } } } if (!ns) ns = new refcntString(cs,s_len); ns->AddRef(); } else { // ns is NULL, blank line } rec->m_htext.Add(ns); } for (x = m_undoStack.GetSize()-1; x > m_undoStack_pos; x --) { m_undoStack.Delete(x,true); } if (m_clean_undopos > m_undoStack_pos) m_clean_undopos=-1; if (m_undoStack.GetSize() > m_max_undo_states) { for (x = 1; x < m_undoStack.GetSize()/2; x += 2) { if (x != m_clean_undopos)// don't delete our preferred special (last saved) one { m_undoStack.Delete(x,true); if (m_clean_undopos > x) m_clean_undopos--; x--; } } m_undoStack_pos = m_undoStack.GetSize()-1; } m_undoStack_pos++; m_undoStack.Add(rec); } void WDL_CursesEditor::loadUndoState(editUndoRec *rec, int idx) { int x; m_text.Empty(true); for (x = 0; x < rec->m_htext.GetSize(); x ++) { refcntString *s=rec->m_htext.Get(x); m_text.Add(new WDL_FastString(s?s->getStr():"")); } m_offs_x=rec->m_offs_x[idx]; m_curs_x=rec->m_curs_x[idx]; m_curs_y=rec->m_curs_y[idx]; m_paneoffs_y[m_curpane]=rec->m_curpaneoffs_y[idx]; m_selecting=false; } void WDL_CursesEditor::RunEditor() { WDL_DestroyCheck chk(&destroy_check); int x; for(x=0;x<16;x++) { if (!chk.isOK() || !CURSES_INSTANCE) break; int thischar = getch(); if (thischar==ERR) break; if (onChar(thischar)) break; } } void WDL_CursesEditor::draw_top_line() { int ypos=0; if (m_top_margin > 1) { int xpos=0; int x; move(ypos++,0); const int cnt= GetTabCount(); int tsz=16; // this is duplicated in onMouseMessage if (cnt>0) tsz=COLS/cnt; if (tsz>128)tsz=128; if (tsz<12) tsz=12; for (x= 0; x < cnt && xpos < COLS; x ++) { WDL_CursesEditor *ed = GetTab(x); if (ed) { char buf[128 + 8]; memset(buf,' ',tsz); const char *p = WDL_get_filepart(ed->GetFileName()); const int lp=strlen(p); int skip=0; if (x<9) { if (tsz>16) { const char *s = "<" CONTROL_KEY_NAME "+"; memcpy(buf,s,skip = (int)strlen(s)); } buf[skip++]='F'; buf[skip++] = '1'+x; buf[skip++] = '>'; skip++; } memcpy(buf+skip,p,wdl_min(tsz-1-skip,lp)); buf[tsz]=0; int l = tsz; if (l > COLS-xpos) l = COLS-xpos; if (ed == this) { attrset(SYNTAX_HIGHLIGHT2|A_BOLD); } else { attrset(A_NORMAL); } addnstr(buf,l); xpos += l; } } if (xpos < COLS) clrtoeol(); } attrset(COLOR_TOPLINE|A_BOLD); bkgdset(COLOR_TOPLINE); const char *p=GetFileName(); move(ypos,0); if (COLS>4) { const int pl = (int) strlen(p); if (pl > COLS-1 && COLS > 4) { addstr("..."); p+=pl - (COLS-1) + 4; } addstr(p); } clrtoeol(); } void WDL_CursesEditor::OpenFileInTab(const char *fnp) { if (!fnp[0]) return; FILE *fp = fopen(fnp,"rb"); if (!fp) { WDL_FastString s(fnp); m_newfn.Set(fnp); if (COLS > 25) { int allowed = COLS-25; if (s.GetLength()>allowed) { s.DeleteSub(0,s.GetLength() - allowed + 3); s.Insert("...",0); } s.Insert("Create new file '",0); s.Append("' (Y/n)? "); } else s.Set("Create new file (Y/n)? "); m_ui_state=UI_STATE_SAVE_AS_NEW; attrset(COLOR_MESSAGE); bkgdset(COLOR_MESSAGE); mvaddstr(LINES-1,0,s.Get()); clrtoeol(); attrset(0); bkgdset(0); } else { fclose(fp); int x; for (x=0;xGetFileName(),fnp)) { SwitchTab(x,false); return; } } AddTab(fnp); } } void WDL_CursesEditor::do_paste_lines(WDL_PtrList &lines) { WDL_FastString poststr; int x; int indent_to_pos = 0; int skip_source_indent=0; // number of characters of whitespace to (potentially) ignore when pasting for (x = 0; x < lines.GetSize(); x ++) { WDL_FastString *str=m_text.Get(m_curs_y); const char *tstr=lines.Get(x); if (!tstr) tstr=""; if (!x) { if (str) { int bytepos = WDL_utf8_charpos_to_bytepos(str->Get(),m_curs_x); if (bytepos < 0) bytepos=0; int tmp=str->GetLength(); if (bytepos > tmp) bytepos=tmp; poststr.Set(str->Get()+bytepos); str->SetLen(bytepos); if (bytepos > 0 && lines.GetSize()>1) { indent_to_pos = 0; while (str->Get()[indent_to_pos] == ' ') indent_to_pos++; skip_source_indent=1024; for (int i = 0; skip_source_indent > 0 && i < lines.GetSize(); i ++) { int a=0; const char *p = lines.Get(i); while (a < skip_source_indent && p[a] == ' ') a++; if (a < skip_source_indent && p[a]) skip_source_indent=a; } } str->Append(skip_indent(tstr,skip_source_indent)); } else { m_text.Insert(m_curs_y,(str=new WDL_FastString(skip_indent(tstr,skip_source_indent)))); } if (lines.GetSize() > 1) { m_curs_y++; } else { m_curs_x = WDL_utf8_get_charlen(str->Get()); str->Append(poststr.Get()); } } else if (x == lines.GetSize()-1) { WDL_FastString *s=newIndentedFastString(skip_indent(tstr,skip_source_indent),indent_to_pos); m_curs_x = WDL_utf8_get_charlen(s->Get()); s->Append(poststr.Get()); m_text.Insert(m_curs_y,s); } else { m_text.Insert(m_curs_y,newIndentedFastString(skip_indent(tstr,skip_source_indent),indent_to_pos)); m_curs_y++; } } }