Create FUNDING.yml
[wdl/wdl-ol.git] / WDL / win32_curses / curses_editor.cpp
blob2bfc8147edc420ee64135a8747329fbd9138d87f
1 #ifdef _WIN32
2 #include <windows.h>
3 #else
4 #include "../swell/swell.h"
5 #endif
6 #include <stdlib.h>
7 #include <string.h>
8 #ifndef CURSES_INSTANCE
9 #define CURSES_INSTANCE ((win32CursesCtx*)m_cursesCtx)
10 #endif
12 #include "curses_editor.h"
13 #include "../wdlutf8.h"
14 #include "../win32_utf8.h"
15 #include "../wdlcstring.h"
17 #ifndef VALIDATE_TEXT_CHAR
18 #define VALIDATE_TEXT_CHAR(thischar) ((thischar) >= 0 && (thischar >= 128 || isspace(thischar) || isgraph(thischar)) && !(thischar >= KEY_DOWN && thischar <= KEY_F12))
19 #endif
23 #ifdef __APPLE__
24 #define CONTROL_KEY_NAME "Cmd"
25 #else
26 #define CONTROL_KEY_NAME "Ctrl"
27 #endif
29 WDL_FastString WDL_CursesEditor::s_fake_clipboard;
30 int WDL_CursesEditor::s_overwrite=0;
31 char WDL_CursesEditor::s_search_string[256];
33 #ifndef WM_MOUSEWHEEL
34 #define WM_MOUSEWHEEL 0x20A
35 #endif
37 static void __curses_onresize(win32CursesCtx *ctx)
39 WDL_CursesEditor *p = (WDL_CursesEditor *)ctx->user_data;
40 if (p)
42 p->draw();
43 p->setCursor();
46 WDL_CursesEditor::WDL_CursesEditor(void *cursesCtx)
48 m_newline_mode=0;
49 m_write_leading_tabs=0;
50 m_max_undo_states = 500;
51 m_indent_size=2;
52 m_cursesCtx = cursesCtx;
54 m_top_margin=1;
55 m_bottom_margin=1;
57 m_selecting=0;
58 m_select_x1=m_select_y1=m_select_x2=m_select_y2=0;
59 m_ui_state=UI_STATE_NORMAL;
60 m_offs_x=0;
61 m_curs_x=m_curs_y=0;
62 m_want_x=-1;
63 m_undoStack_pos=-1;
64 m_clean_undopos=0;
66 m_curpane=0;
67 m_pane_div=1.0;
68 m_paneoffs_y[0]=m_paneoffs_y[1]=0;
70 m_curpane=0;
71 m_scrollcap=0;
72 m_scrollcap_yoffs=0;
74 m_filelastmod=0;
75 m_status_lastlen=0;
77 #ifdef WDL_IS_FAKE_CURSES
78 if (m_cursesCtx)
80 CURSES_INSTANCE->user_data = this;
81 CURSES_INSTANCE->onMouseMessage = _onMouseMessage;
82 CURSES_INSTANCE->want_scrollbar=1; // 1 or 2 chars wide
83 CURSES_INSTANCE->do_update = __curses_onresize;
85 #endif
87 initscr();
88 cbreak();
89 noecho();
90 nonl();
91 intrflush(stdscr,FALSE);
92 keypad(stdscr,TRUE);
93 nodelay(stdscr,TRUE);
94 raw(); // disable ctrl+C etc. no way to kill if allow quit isn't defined, yay.
95 start_color();
97 #ifdef WDL_IS_FAKE_CURSES
98 if (!curses_win32_global_user_colortab && (!m_cursesCtx || !CURSES_INSTANCE->user_colortab))
99 #endif
101 init_pair(1, COLOR_WHITE, COLOR_BLUE); // COLOR_BOTTOMLINE
102 init_pair(2, COLOR_BLACK, COLOR_CYAN); // COLOR_SELECTION
103 init_pair(3, RGB(0,255,255),COLOR_BLACK); // SYNTAX_HIGHLIGHT1
104 init_pair(4, RGB(0,255,0),COLOR_BLACK); // SYNTAX_HIGHLIGHT2
105 init_pair(5, RGB(96,128,192),COLOR_BLACK); // SYNTAX_COMMENT
106 init_pair(6, COLOR_WHITE, COLOR_RED); // SYNTAX_ERROR
107 init_pair(7, RGB(255,255,0), COLOR_BLACK); // SYNTAX_FUNC
109 #ifdef WDL_IS_FAKE_CURSES
110 init_pair(8, RGB(255,128,128), COLOR_BLACK); // SYNTAX_REGVAR
111 init_pair(9, RGB(0,192,255), COLOR_BLACK); // SYNTAX_KEYWORD
112 init_pair(10, RGB(255,192,192), COLOR_BLACK); // SYNTAX_STRING
113 init_pair(11, RGB(192,255,128), COLOR_BLACK); // SYNTAX_STRINGVAR
114 init_pair(12, COLOR_BLACK, COLOR_CYAN); // COLOR_MESSAGE (maps to COLOR_SELECTION)
115 init_pair(13, COLOR_WHITE, COLOR_RED); // COLOR_TOPLINE (maps to SYNTAX_ERROR)
116 init_pair(14, RGB(192,192,0), COLOR_BLACK); // SYNTAX_FUNC2
117 #endif
120 erase();
121 refresh();
124 int WDL_CursesEditor::GetPaneDims(int* paney, int* paneh) // returns ypos of divider
126 const int pane_divy=(int)(m_pane_div*(double)(LINES-m_top_margin-m_bottom_margin-1));
127 if (paney)
129 paney[0]=m_top_margin;
130 paney[1]=m_top_margin+pane_divy+1;
132 if (paneh)
134 paneh[0]=pane_divy+(m_pane_div >= 1.0 ? 1 : 0);
135 paneh[1]=LINES-pane_divy-m_top_margin-m_bottom_margin-1;
138 return pane_divy;
142 int WDL_CursesEditor::getVisibleLines() const { return LINES-m_bottom_margin-m_top_margin; }
145 #ifdef WDL_IS_FAKE_CURSES
146 LRESULT WDL_CursesEditor::onMouseMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
148 static int s_mousedown[2];
150 switch (uMsg)
152 case WM_CAPTURECHANGED:
153 break;
155 case WM_MOUSEMOVE:
156 if (GetCapture()==hwnd && CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h)
158 POINT pt = { (short)LOWORD(lParam), (short)HIWORD(lParam) };
159 int cx=pt.x/CURSES_INSTANCE->m_font_w;
160 int cy=pt.y/CURSES_INSTANCE->m_font_h;
162 int paney[2], paneh[2];
163 const int pane_divy=GetPaneDims(paney, paneh);
165 if (m_scrollcap)
167 int i=m_scrollcap-1;
169 if (i == 2) // divider
171 int divy=cy-paney[0];
172 if (divy != pane_divy)
174 if (divy <= 0 || divy >= LINES-m_top_margin-m_bottom_margin-1)
176 if (divy <= 0.0) m_paneoffs_y[0]=m_paneoffs_y[1];
177 m_curpane=0;
178 m_pane_div=1.0;
180 else
182 m_pane_div=(double)divy/(double)(LINES-m_top_margin-m_bottom_margin-1);
184 draw();
185 draw_status_state();
188 else
190 int prevoffs=m_paneoffs_y[i];
191 int pxy=paney[i]*CURSES_INSTANCE->m_font_h;
192 int pxh=paneh[i]*CURSES_INSTANCE->m_font_h;
194 if (pxh > CURSES_INSTANCE->scroll_h[i])
196 m_paneoffs_y[i]=(m_text.GetSize()-paneh[i])*(pt.y-m_scrollcap_yoffs-pxy)/(pxh-CURSES_INSTANCE->scroll_h[i]);
197 int maxscroll=m_text.GetSize()-paneh[i]+4;
198 if (m_paneoffs_y[i] > maxscroll) m_paneoffs_y[i]=maxscroll;
199 if (m_paneoffs_y[i] < 0) m_paneoffs_y[i]=0;
202 if (m_paneoffs_y[i] != prevoffs)
204 draw();
205 draw_status_state();
207 const int col=m_curs_x-m_offs_x;
208 int line=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane];
209 if (line >= paney[m_curpane] && line < paney[m_curpane]+paneh[m_curpane]) move(line, col);
213 return 0;
216 if (!m_selecting && (cx != s_mousedown[0] || cy != s_mousedown[1]))
218 m_select_x2=m_select_x1=m_curs_x;
219 m_select_y2=m_select_y1=m_curs_y;
220 m_selecting=1;
223 int x=cx+m_offs_x;
224 int y=cy+m_paneoffs_y[m_curpane]-paney[m_curpane];
225 if (m_selecting && (m_select_x2!=x || m_select_y2 != y))
227 if (y < m_paneoffs_y[m_curpane] && m_paneoffs_y[m_curpane] > 0)
229 m_paneoffs_y[m_curpane]--;
231 else if (y >= m_paneoffs_y[m_curpane]+paneh[m_curpane] && m_paneoffs_y[m_curpane]+paneh[m_curpane] < m_text.GetSize())
233 m_paneoffs_y[m_curpane]++;
235 if (x < m_offs_x && m_offs_x > 0)
237 m_offs_x--;
239 else if (x > m_offs_x+COLS)
241 int maxlen=0;
242 int a;
243 for (a=0; a < paneh[m_curpane]; a++)
245 WDL_FastString* s=m_text.Get(m_paneoffs_y[m_curpane]+a);
246 if (s)
248 const int l = WDL_utf8_get_charlen(s->Get());
249 if (l > maxlen) maxlen=l;
252 if (maxlen > m_offs_x+COLS-8) m_offs_x++;
255 m_select_y2=y;
256 m_select_x2=x;
257 if (m_select_y2<0) m_select_y2=0;
258 else if (m_select_y2>=m_text.GetSize())
260 m_select_y2=m_text.GetSize()-1;
261 WDL_FastString *s=m_text.Get(m_select_y2);
262 if (s) m_select_x2 = WDL_utf8_get_charlen(s->Get());
264 if (m_select_x2<0)m_select_x2=0;
265 WDL_FastString *s=m_text.Get(m_select_y2);
266 if (s)
268 const int l = WDL_utf8_get_charlen(s->Get());
269 if (m_select_x2>l) m_select_x2 = l;
271 draw();
273 int y=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane];
274 if (y >= paney[m_curpane] && y < paney[m_curpane]+paneh[m_curpane]) setCursor();
277 break;
278 case WM_LBUTTONDBLCLK:
279 if (CURSES_INSTANCE && CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h)
281 const int y = ((short)HIWORD(lParam)) / CURSES_INSTANCE->m_font_h - m_top_margin;
282 const int x = ((short)LOWORD(lParam)) / CURSES_INSTANCE->m_font_w + m_offs_x;
283 WDL_FastString *fs=m_text.Get(y + m_paneoffs_y[m_curpane]);
284 if (fs && y >= 0)
286 const char *url=fs->Get();
288 while (NULL != (url = strstr(url,"http://")))
290 if (url != fs->Get() && url[-1] > 0 && isalnum(url[-1]))
292 url+=7;
294 else
296 const int soffs = (int) (url - fs->Get());
297 char tmp[512];
298 char *p=tmp;
299 while (p < (tmp+sizeof(tmp)-1) &&
300 *url && *url != ' ' && *url != ')' && *url != '\t' && *url != '"' && *url != '\'' )
302 *p++ = *url++;
304 *p=0;
305 if (strlen(tmp) >= 10 && x >= soffs && x<(url-fs->Get()))
307 ShellExecute(hwnd,"open",tmp,"","",0);
308 return 1;
315 case WM_LBUTTONDOWN:
316 if (CURSES_INSTANCE && CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h)
318 int x = ((short)LOWORD(lParam)) / CURSES_INSTANCE->m_font_w;
319 int y = ((short)HIWORD(lParam)) / CURSES_INSTANCE->m_font_h;
320 const int tabcnt=GetTabCount();
321 if (y==0 && tabcnt>1)
323 int tsz=COLS/tabcnt;
324 // this is duplicated in draw_top_line
325 if (tsz>128)tsz=128;
326 if (tsz<12) tsz=12;
327 SwitchTab(x/tsz,false);
329 return 1;
333 // passthrough
334 case WM_RBUTTONDOWN:
336 if (CURSES_INSTANCE->m_font_w && CURSES_INSTANCE->m_font_h)
338 int mousex=(short)LOWORD(lParam);
339 int mousey=(short)HIWORD(lParam);
341 int cx=mousex/CURSES_INSTANCE->m_font_w;
342 int cy=mousey/CURSES_INSTANCE->m_font_h;
343 if (cx > COLS) cx=COLS;
344 if (cx < 0) cx=0;
345 if (cy > LINES) cy=LINES;
346 if (cy < 0) cy=0;
348 m_ui_state=UI_STATE_NORMAL; // any click clears the state
349 s_mousedown[0]=cx;
350 s_mousedown[1]=cy;
352 int paney[2], paneh[2];
353 const int pane_divy=GetPaneDims(paney, paneh);
355 if (uMsg == WM_LBUTTONDOWN && m_pane_div > 0.0 && m_pane_div < 1.0 && cy == m_top_margin+pane_divy)
357 SetCapture(hwnd);
358 m_scrollcap=3;
359 return 0;
362 int pane=-1;
363 if (cy >= paney[0] && cy < paney[0]+paneh[0]) pane=0;
364 else if (cy >= paney[1] && cy < paney[1]+paneh[1]) pane=1;
366 if ((uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONDBLCLK) && pane >= 0 &&
367 cx >= COLS-CURSES_INSTANCE->drew_scrollbar[pane])
369 const int st=paney[pane]*CURSES_INSTANCE->m_font_h+CURSES_INSTANCE->scroll_y[pane];
370 const int sb=st+CURSES_INSTANCE->scroll_h[pane];
372 int prevoffs=m_paneoffs_y[pane];
374 if (mousey < st)
376 m_paneoffs_y[pane] -= paneh[pane];
377 if (m_paneoffs_y[pane] < 0) m_paneoffs_y[pane]=0;
379 else if (mousey < sb)
381 if (uMsg == WM_LBUTTONDOWN)
383 SetCapture(hwnd);
384 m_scrollcap=pane+1;
385 m_scrollcap_yoffs=mousey-st;
388 else
390 m_paneoffs_y[pane] += paneh[pane];
391 int maxscroll=m_text.GetSize()-paneh[pane]+4;
392 if (m_paneoffs_y[pane] > maxscroll) m_paneoffs_y[pane]=maxscroll;
395 if (prevoffs != m_paneoffs_y[pane])
397 draw();
398 draw_status_state();
400 int y=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane];
401 if (y >= paney[m_curpane] && y < paney[m_curpane]+paneh[m_curpane]) setCursor();
402 return 0;
406 if (uMsg == WM_LBUTTONDOWN) m_selecting=0;
408 if (pane >= 0) m_curpane=pane;
410 int ox=m_curs_x;
411 int oy=m_curs_y;
413 m_curs_x=cx+m_offs_x;
414 m_curs_y=cy+m_paneoffs_y[m_curpane]-paney[m_curpane];
416 bool end = (m_curs_y > m_text.GetSize()-1);
417 if (end) m_curs_y=m_text.GetSize()-1;
418 if (m_curs_y < 0) m_curs_y = 0;
420 WDL_FastString *s=m_text.Get(m_curs_y);
421 if (m_curs_x < 0) m_curs_x = 0;
422 const int slen = s ? WDL_utf8_get_charlen(s->Get()) : 0;
424 if (s && (end || m_curs_x > slen)) m_curs_x=slen;
426 if (uMsg == WM_LBUTTONDOWN && !!(GetAsyncKeyState(VK_SHIFT)&0x8000) &&
427 (m_curs_x != ox || m_curs_y != oy))
429 m_select_x1=ox;
430 m_select_y1=oy;
431 m_select_x2=m_curs_x;
432 m_select_y2=m_curs_y;
433 m_selecting=1;
435 else if (uMsg == WM_LBUTTONDBLCLK && s && slen)
437 if (m_curs_x < slen)
439 int x1=WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x);
440 int x2=x1+1;
441 const char* p=s->Get();
442 while (x1 > 0 && p[x1-1] > 0 && (isalnum(p[x1-1]) || p[x1-1] == '_')) --x1;
443 while (x2 < s->GetLength() && p[x2] > 0 && (isalnum(p[x2]) || p[x2] == '_')) ++x2;
444 if (x2 > x1)
446 m_select_x1=x1;
447 m_curs_x=m_select_x2=x2;
448 m_select_y1=m_select_y2=m_curs_y;
449 m_selecting=1;
454 draw();
455 setCursor();
457 if (uMsg == WM_LBUTTONDOWN)
459 SetCapture(hwnd);
461 else if (uMsg == WM_RBUTTONDOWN)
463 onRightClick(hwnd);
466 return 0;
468 case WM_LBUTTONUP:
469 ReleaseCapture();
470 m_scrollcap=0;
471 m_scrollcap_yoffs=0;
472 return 0;
474 case WM_MOUSEWHEEL:
475 if (CURSES_INSTANCE->m_font_h)
477 POINT p;
478 GetCursorPos(&p);
479 ScreenToClient(hwnd, &p);
480 p.y /= CURSES_INSTANCE->m_font_h;
482 int paney[2], paneh[2];
483 GetPaneDims(paney, paneh);
484 int pane=-1;
485 if (p.y >= paney[0] && p.y < paney[0]+paneh[0]) pane=0;
486 else if (p.y >= paney[1] && p.y < paney[1]+paneh[1]) pane=1;
487 if (pane < 0) pane=m_curpane;
489 m_paneoffs_y[pane] -= ((short)HIWORD(wParam))/20;
491 int maxscroll=m_text.GetSize()-paneh[pane]+4;
492 if (m_paneoffs_y[pane] > maxscroll) m_paneoffs_y[pane]=maxscroll;
493 if (m_paneoffs_y[pane] < 0) m_paneoffs_y[pane]=0;
495 draw();
497 int y=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane];
498 if (y >= paney[m_curpane] && y < paney[m_curpane]+paneh[m_curpane]) setCursor();
499 else draw_status_state();
501 break;
503 return 0;
505 #endif
508 WDL_CursesEditor::~WDL_CursesEditor()
510 endwin();
511 m_text.Empty(true);
512 m_undoStack.Empty(true);
515 int WDL_CursesEditor::init(const char *fn, const char *init_if_empty)
517 m_filename.Set(fn);
518 FILE *fh=fopenUTF8(fn,"rb");
520 if (!fh)
522 if (init_if_empty)
524 fh=fopenUTF8(fn,"w+b");
525 if (fh)
527 fwrite(init_if_empty,1,strlen(init_if_empty),fh);
529 fseek(fh,0,SEEK_SET);
532 if (!fh)
534 saveUndoState();
535 m_clean_undopos=m_undoStack_pos;
536 return 1;
540 loadLines(fh);
541 fclose(fh);
543 return 0;
546 int WDL_CursesEditor::reload_file(bool clearundo)
548 FILE *fh=fopenUTF8(m_filename.Get(),"rb");
549 if (fh)
551 if (!clearundo)
553 preSaveUndoState();
555 else
557 m_undoStack.Empty(true);
558 m_undoStack_pos=-1;
561 m_text.Empty(true);
562 loadLines(fh);
563 fclose(fh);
565 return 0;
567 return 1;
570 static void ReplaceTabs(WDL_FastString *str, int tabsz)
572 int x;
573 char s[128];
574 // replace any \t with spaces
575 int insert_sz=tabsz - 1;
576 if (insert_sz<0) insert_sz=0;
577 else if (insert_sz>128) insert_sz=128;
579 if (insert_sz>0) memset(s,' ',insert_sz);
580 for(x=0;x<str->GetLength();x++)
582 char *p = (char *)str->Get();
583 if (p[x] == '\t')
585 p[x] = ' ';
586 str->Insert(s,x+1,insert_sz);
587 x+=insert_sz;
592 void WDL_CursesEditor::loadLines(FILE *fh)
594 int crcnt = 0;
595 int tabstate = 0;
596 int tab_cnv_size=5;
597 int rdcnt=0;
598 for (;;)
600 char line[4096];
601 line[0]=0;
602 fgets(line,sizeof(line),fh);
603 if (!line[0]) break;
605 int l=strlen(line);
607 if (!rdcnt++)
609 if ((unsigned char)line[0] == 0xef &&
610 (unsigned char)line[1] == 0xbb &&
611 (unsigned char)line[2] == 0xbf)
613 // remove BOM (could track it, but currently EEL/etc don't support reading it anyway)
614 l -= 3;
615 memmove(line,line+3,l+1);
616 if (!line[0]) break;
620 while(l>0 && (line[l-1]=='\r' || line[l-1]=='\n'))
622 if (line[l-1] == '\r') crcnt++;
624 line[l-1]=0;
625 l--;
627 m_text.Add(new WDL_FastString(line));
629 if (tabstate>=0)
631 const char *p = line;
632 if (*p == '\t' && !tabstate) tabstate=1;
633 while (*p == '\t') p++;
635 int spacecnt=0;
636 while (*p == ' ') { p++; spacecnt++; }
637 if (*p == '\t' || spacecnt>7) tabstate=-1; // tab after space, or more than 7 spaces = dont try to preserve tabs
638 else if (spacecnt+1 > tab_cnv_size) tab_cnv_size = spacecnt+1;
641 if (tabstate>0)
643 m_indent_size=tab_cnv_size;
644 m_write_leading_tabs=tab_cnv_size;
646 else
648 m_write_leading_tabs=0;
651 int x;
652 for (x=0;x<m_text.GetSize();x++)
654 WDL_FastString *s = m_text.Get(x);
655 if (s)
656 ReplaceTabs(s,m_indent_size);
658 m_newline_mode=crcnt > m_text.GetSize()/2; // more than half of lines have crlf, then use crlf
660 saveUndoState();
661 m_clean_undopos=m_undoStack_pos;
662 updateLastModTime();
665 void WDL_CursesEditor::draw_status_state()
667 int paney[2], paneh[2];
668 const int pane_divy=GetPaneDims(paney, paneh);
670 attrset(COLOR_BOTTOMLINE);
671 bkgdset(COLOR_BOTTOMLINE);
673 int line=LINES-1;
674 const char* whichpane="";
675 if (m_pane_div > 0.0 && m_pane_div < 1.0)
677 whichpane=(!m_curpane ? "Upper pane: " : "Lower pane: ");
678 line=m_top_margin+pane_divy;
679 move(line, 0);
680 clrtoeol();
683 char str[512];
684 snprintf(str, sizeof(str), "%sLine %d/%d, Col %d [%s]%s",
685 whichpane, m_curs_y+1, m_text.GetSize(), m_curs_x,
686 (s_overwrite ? "OVR" : "INS"), (m_clean_undopos == m_undoStack_pos ? "" : "*"));
688 int len=strlen(str);
689 int x=COLS-len-1;
690 if (!*whichpane)
692 if (len < m_status_lastlen)
694 int xpos = COLS-m_status_lastlen-1;
695 if (xpos<0) xpos=0;
696 move(line,xpos);
697 while (xpos++ < x) addstr(" ");
699 m_status_lastlen = len;
701 else
703 m_status_lastlen=0;
706 mvaddnstr(line, x, str, len);
707 clrtoeol();
709 attrset(0);
710 bkgdset(0);
712 const int col=m_curs_x-m_offs_x;
713 line=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane];
714 if (line >= paney[m_curpane] && line < paney[m_curpane]+paneh[m_curpane]) move(line, col);
717 void WDL_CursesEditor::setCursor(int isVscroll, double ycenter)
719 int maxx=m_text.Get(m_curs_y) ? m_text.Get(m_curs_y)->GetLength() : 0;
721 if (isVscroll)
723 if (m_want_x >= 0) m_curs_x=m_want_x;
725 else m_want_x=-1;
727 if(m_curs_x>maxx)
729 if (isVscroll) m_want_x=m_curs_x;
730 m_curs_x=maxx;
733 int redraw=0;
735 if (m_curs_x < m_offs_x)
737 redraw=1;
738 m_offs_x=m_curs_x;
740 else
742 const int mw = COLS-3;
743 if (m_curs_x >= m_offs_x + mw)
745 m_offs_x=m_curs_x-mw+1;
746 redraw=1;
750 int paney[2], paneh[2];
751 GetPaneDims(paney, paneh);
752 int y;
753 if (ycenter >= 0.0 && ycenter < 1.0)
755 y=(int)((double)paneh[m_curpane]*ycenter);
756 m_paneoffs_y[m_curpane]=m_curs_y-y;
757 if (m_paneoffs_y[m_curpane] < 0)
759 y += m_paneoffs_y[m_curpane];
760 m_paneoffs_y[m_curpane]=0;
762 redraw=1;
764 else
766 y=m_curs_y-m_paneoffs_y[m_curpane];
767 if (y < 0)
769 m_paneoffs_y[m_curpane] += y;
770 y=0;
771 redraw=1;
773 else if (y >= paneh[m_curpane])
775 m_paneoffs_y[m_curpane] += y-paneh[m_curpane]+1;
776 y=paneh[m_curpane]-1;
777 redraw=1;
781 if (redraw) draw();
783 draw_status_state();
785 y += paney[m_curpane];
786 move(y, m_curs_x-m_offs_x);
789 void WDL_CursesEditor::draw_message(const char *str)
791 if (!CURSES_INSTANCE) return;
793 int l=strlen(str);
794 if (l && m_ui_state == UI_STATE_NORMAL) m_ui_state=UI_STATE_MESSAGE;
795 if (l > COLS-2) l=COLS-2;
796 if (str[0])
798 attrset(COLOR_MESSAGE);
799 bkgdset(COLOR_MESSAGE);
801 mvaddnstr(LINES-(m_bottom_margin>1?2:1),0,str,l);
802 clrtoeol();
803 if (str[0])
805 attrset(0);
806 bkgdset(0);
809 int paney[2], paneh[2];
810 GetPaneDims(paney, paneh);
812 const int col=m_curs_x-m_offs_x;
813 int line=m_curs_y+paney[m_curpane]-m_paneoffs_y[m_curpane];
814 if (line >= paney[m_curpane] && line < paney[m_curpane]+paneh[m_curpane]) move(line, col);
818 void WDL_CursesEditor::draw_line_highlight(int y, const char *p, int *c_comment_state)
820 attrset(A_NORMAL);
821 mvaddstr(y,0,p + WDL_utf8_charpos_to_bytepos(p,m_offs_x));
822 clrtoeol();
825 void WDL_CursesEditor::getselectregion(int &minx, int &miny, int &maxx, int &maxy)
827 if (m_select_y2 < m_select_y1)
829 miny=m_select_y2; maxy=m_select_y1;
830 minx=m_select_x2; maxx=m_select_x1;
832 else if (m_select_y1 < m_select_y2)
834 miny=m_select_y1; maxy=m_select_y2;
835 minx=m_select_x1; maxx=m_select_x2;
837 else
839 miny=maxy=m_select_y1;
840 minx=wdl_min(m_select_x1,m_select_x2);
841 maxx=wdl_max(m_select_x1,m_select_x2);
845 void WDL_CursesEditor::doDrawString(int y, int line_n, const char *p, int *c_comment_state)
847 draw_line_highlight(y,p,c_comment_state);
849 if (m_selecting)
851 int miny,maxy,minx,maxx;
852 getselectregion(minx,miny,maxx,maxy);
854 if (line_n >= miny && line_n <= maxy && (miny != maxy || minx < maxx))
856 minx-=m_offs_x;
857 maxx-=m_offs_x;
859 const int cols = COLS;
861 if (line_n > miny) minx=0;
862 if (line_n < maxy) maxx=cols;
864 if (minx<0)minx=0;
865 if (minx > cols) minx=cols;
866 if (maxx > cols) maxx=cols;
868 if (maxx > minx)
870 attrset(COLOR_SELECTION);
871 p += WDL_utf8_charpos_to_bytepos(p,m_offs_x+minx);
872 mvaddnstr(y,minx, p, WDL_utf8_charpos_to_bytepos(p,maxx-minx));
873 attrset(A_NORMAL);
875 else if (maxx==minx && !*p)
877 attrset(COLOR_SELECTION);
878 mvaddstr(y,minx," ");
879 attrset(A_NORMAL);
885 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
887 return 0;
891 void WDL_CursesEditor::draw(int lineidx)
893 if (m_top_margin != 0) m_top_margin = GetTabCount()>1 ? 2 : 1;
895 int paney[2], paneh[2];
896 const int pane_divy=GetPaneDims(paney, paneh);
898 #ifdef WDL_IS_FAKE_CURSES
899 if (m_cursesCtx)
901 CURSES_INSTANCE->offs_y[0]=m_paneoffs_y[0];
902 CURSES_INSTANCE->offs_y[1]=m_paneoffs_y[1];
903 CURSES_INSTANCE->div_y=pane_divy;
904 CURSES_INSTANCE->tot_y=m_text.GetSize();
906 CURSES_INSTANCE->scrollbar_topmargin = m_top_margin;
907 CURSES_INSTANCE->scrollbar_botmargin = m_bottom_margin;
909 #endif
911 attrset(A_NORMAL);
913 if (lineidx >= 0)
915 int comment_state = GetCommentStateForLineStart(lineidx);
916 WDL_FastString *s=m_text.Get(lineidx);
917 if (s)
919 int y=lineidx-m_paneoffs_y[0];
920 if (y >= 0 && y < paneh[0])
922 doDrawString(paney[0]+y, lineidx, s->Get(), &comment_state);
924 y=lineidx-m_paneoffs_y[1];
925 if (y >= 0 && y < paneh[1])
927 doDrawString(paney[1]+y, lineidx, s->Get(), &comment_state);
930 return;
933 __curses_invalidatefull((win32CursesCtx*)m_cursesCtx,false);
935 draw_top_line();
937 attrset(A_NORMAL);
938 bkgdset(A_NORMAL);
940 move(m_top_margin,0);
941 clrtoeol();
943 m_status_lastlen=0;
945 int pane, i;
946 for (pane=0; pane < 2; ++pane)
948 int ln=m_paneoffs_y[pane];
949 int y=paney[pane];
950 int h=paneh[pane];
952 int comment_state=GetCommentStateForLineStart(ln);
954 for(i=0; i < h; ++i, ++ln, ++y)
956 WDL_FastString *s=m_text.Get(ln);
957 if (!s)
959 move(y,0);
960 clrtoeol();
962 else
964 doDrawString(y,ln,s->Get(),&comment_state);
969 attrset(COLOR_BOTTOMLINE);
970 bkgdset(COLOR_BOTTOMLINE);
972 if (m_bottom_margin>0)
974 move(LINES-1, 0);
975 #define BOLD(x) { attrset(COLOR_BOTTOMLINE|A_BOLD); addstr(x); attrset(COLOR_BOTTOMLINE&~A_BOLD); }
976 if (m_selecting)
978 mvaddstr(LINES-1,0,"SELECTING ESC:cancel " CONTROL_KEY_NAME "+(");
979 BOLD("C"); addstr("opy ");
980 BOLD("X"); addstr(":cut ");
981 BOLD("V"); addstr(":paste)");
983 else
985 mvaddstr(LINES-1, 0, CONTROL_KEY_NAME "+(");
987 if (m_pane_div <= 0.0 || m_pane_div >= 1.0)
989 BOLD("P"); addstr("ane ");
991 else
993 BOLD("O"); addstr("therpane ");
994 addstr("no"); BOLD("P"); addstr("anes ");
996 BOLD("F"); addstr("ind ");
997 draw_bottom_line();
998 addstr(")");
1000 #undef BOLD
1001 clrtoeol();
1004 attrset(0);
1005 bkgdset(0);
1007 __curses_invalidatefull((win32CursesCtx*)m_cursesCtx,true);
1010 void WDL_CursesEditor::draw_bottom_line()
1012 // implementers add key commands here
1015 static const char *countLeadingTabs(const char *p, int *ntabs, int tabsz)
1017 if (tabsz>0) while (*p)
1019 int i;
1020 for (i=0;i<tabsz;i++) if (p[i]!=' ') return p;
1021 p+=tabsz;
1022 (*ntabs) += 1;
1024 return p;
1027 int WDL_CursesEditor::updateFile()
1029 FILE *fp=fopenUTF8(m_filename.Get(),"wb");
1030 if (!fp) return 1;
1031 int x;
1032 for (x = 0; x < m_text.GetSize(); x ++)
1034 WDL_FastString *s = m_text.Get(x);
1035 if (s)
1037 int tabcnt=0;
1038 const char *p = countLeadingTabs(s->Get(),&tabcnt,m_write_leading_tabs);
1039 while (tabcnt-->0) fputc('\t',fp);
1040 fwrite(p,1,strlen(p),fp);
1041 if (m_newline_mode==1) fputc('\r',fp);
1042 fputc('\n',fp);
1045 fclose(fp);
1046 sync();
1048 updateLastModTime();
1049 m_clean_undopos = m_undoStack_pos;
1051 return 0;
1054 void WDL_CursesEditor::updateLastModTime()
1056 struct stat srcstat;
1057 if (!statUTF8(m_filename.Get(), &srcstat))
1059 #ifndef __APPLE__
1060 m_filelastmod = srcstat.st_mtime;
1061 #else
1062 m_filelastmod = srcstat.st_mtimespec.tv_sec;
1063 #endif
1065 else
1067 m_filelastmod = 0;
1071 void WDL_CursesEditor::indentSelect(int amt)
1073 if (m_selecting) // remove selected text
1075 int miny,maxy,minx,maxx;
1076 int x;
1077 getselectregion(minx,miny,maxx,maxy);
1078 if (maxy >= miny)
1080 m_select_x1 = 0;
1081 m_select_y1 = miny;
1082 m_select_y2 = maxy;
1083 m_select_x2 = maxx;
1084 if (maxx<1 && maxy>miny) maxy--; // exclude empty final line
1086 if (m_curs_y >= miny && m_curs_y <=maxy)
1088 m_curs_x += amt;
1089 if (m_curs_x<0)m_curs_x=0;
1092 if (amt<0)
1094 int minspc=-amt;
1095 for (x = miny; x <= maxy; x ++)
1097 WDL_FastString *s=m_text.Get(x);
1098 if (s)
1100 int a=0;
1101 while (a<minspc && s->Get()[a]== ' ') a++;
1102 minspc = a;
1105 if (minspc>0 && minspc < -amt) amt = -minspc;
1108 for (x = miny; x <= maxy; x ++)
1110 WDL_FastString *s=m_text.Get(x);
1111 if (s)
1113 if (amt>0)
1115 int a;
1116 for(a=0;a<amt;a+=16)
1117 s->Insert(" ",0,wdl_min(amt-a,16));
1119 else if (amt<0)
1121 int a=0;
1122 while (a<-amt && s->Get()[a]== ' ') a++;
1123 s->DeleteSub(0,a);
1126 if (x==m_select_y2) m_select_x2 = s ? s->GetLength() : 0;
1132 void WDL_CursesEditor::removeSelect()
1134 if (m_selecting) // remove selected text
1136 int miny,maxy,minx,maxx;
1137 int x;
1138 getselectregion(minx,miny,maxx,maxy);
1139 m_curs_x = minx;
1140 m_curs_y = miny;
1141 if (m_curs_y < 0) m_curs_y=0;
1143 if (minx != maxx|| miny != maxy)
1145 int fht=0,lht=0;
1146 for (x = miny; x <= maxy; x ++)
1148 WDL_FastString *s=m_text.Get(x);
1149 if (s)
1151 const int sx=x == miny ? WDL_utf8_charpos_to_bytepos(s->Get(),minx) : 0;
1152 const int ex=x == maxy ? WDL_utf8_charpos_to_bytepos(s->Get(),maxx) : s->GetLength();
1154 if (x != maxy && sx == 0 && ex == s->GetLength()) // remove entire line
1156 m_text.Delete(x,true);
1157 if (x==miny) miny--;
1158 x--;
1159 maxy--;
1161 else { if (x==miny) fht=1; if (x == maxy) lht=1; s->DeleteSub(sx,ex-sx); }
1164 if (fht && lht && miny+1 == maxy)
1166 m_text.Get(miny)->Append(m_text.Get(maxy)->Get());
1167 m_text.Delete(maxy,true);
1171 m_selecting=0;
1172 if (m_curs_y >= m_text.GetSize())
1174 m_curs_y = m_text.GetSize();
1175 m_text.Add(new WDL_FastString);
1181 static const char *skip_indent(const char *tstr, int skipsz)
1183 while (*tstr == ' ' && skipsz-->0) tstr++;
1184 return tstr;
1187 static WDL_FastString *newIndentedFastString(const char *tstr, int indent_to_pos)
1189 WDL_FastString *s=new WDL_FastString;
1190 if (indent_to_pos>=0)
1192 int x;
1193 for (x=0;x<indent_to_pos;x++) s->Append(" ");
1195 s->Append(tstr);
1196 return s;
1200 void WDL_CursesEditor::highlight_line(int line)
1202 if (line >= 0 && line <= m_text.GetSize())
1204 bool nosel=false;
1205 if (line == m_text.GetSize()) { line--; nosel=true; }
1207 m_curs_x=0;
1208 m_curs_y=line;
1210 WDL_FastString* s=m_text.Get(line);
1211 if (s && s->GetLength())
1213 if (nosel) m_curs_x = WDL_utf8_get_charlen(s->Get());
1214 else
1216 m_select_x1=0;
1217 m_select_x2=WDL_utf8_get_charlen(s->Get());
1218 m_select_y1=m_select_y2=m_curs_y;
1219 m_selecting=1;
1221 draw();
1224 setCursor();
1228 void WDL_CursesEditor::runSearch()
1230 if (s_search_string[0])
1232 int wrapflag=0,found=0;
1233 int line;
1234 int numlines = m_text.GetSize();
1235 int startx=m_curs_x+1;
1237 const int srchlen=strlen(s_search_string);
1238 for (line = m_curs_y; line < numlines && !found; line ++)
1240 WDL_FastString *tl = m_text.Get(line);
1241 if (startx && tl) startx = WDL_utf8_charpos_to_bytepos(tl->Get(),startx);
1242 const char *p;
1244 if (tl && (p=tl->Get()))
1246 const int linelen = tl->GetLength();
1247 for (; startx <= linelen-srchlen; startx++)
1248 if (!strnicmp(p+startx,s_search_string,srchlen))
1250 m_select_y1=m_select_y2=m_curs_y=line;
1251 m_select_x1=m_curs_x=WDL_utf8_bytepos_to_charpos(p,startx);
1252 m_select_x2=m_curs_x+WDL_utf8_get_charlen(s_search_string);
1253 m_selecting=1;
1254 found=1;
1255 break;
1260 startx=0;
1262 if (!found && (m_curs_y>0 || m_curs_x > 0))
1264 wrapflag=1;
1265 numlines = wdl_min(m_curs_y+1,numlines);
1266 for (line = 0; line < numlines && !found; line ++)
1268 WDL_FastString *tl = m_text.Get(line);
1269 if (startx && tl) startx = WDL_utf8_charpos_to_bytepos(tl->Get(),startx);
1271 const char *p;
1273 if (tl && (p=tl->Get()))
1275 const int linelen = tl->GetLength();
1276 for (; startx <= linelen-srchlen; startx++)
1277 if (!strnicmp(p+startx,s_search_string,srchlen))
1279 m_select_y1=m_select_y2=m_curs_y=line;
1280 m_select_x1=m_curs_x=WDL_utf8_bytepos_to_charpos(p,startx);
1281 m_select_x2=m_curs_x+WDL_utf8_get_charlen(s_search_string);
1282 m_selecting=1;
1283 found=1;
1284 break;
1287 startx=0;
1290 if (found)
1292 draw();
1293 setCursor();
1294 char buf[512];
1295 snprintf(buf,sizeof(buf),"Found %s'%s' " CONTROL_KEY_NAME "+G:next",wrapflag?"(wrapped) ":"",s_search_string);
1296 draw_message(buf);
1297 return;
1301 draw();
1302 setCursor();
1303 char buf[512];
1304 if (s_search_string[0]) snprintf(buf,sizeof(buf),"String '%s' not found",s_search_string);
1305 else lstrcpyn_safe(buf,"No search string",sizeof(buf));
1306 draw_message(buf);
1309 static int categorizeCharForWordNess(int c)
1311 if (c >= 0 && c < 256)
1313 if (isspace(c)) return 0;
1314 if (isalnum(c) || c == '_') return 1;
1315 if (c == ';') return 2; // I prefer this, since semicolons are somewhat special
1317 return 3;
1320 #define CTRL_KEY_DOWN (GetAsyncKeyState(VK_CONTROL)&0x8000)
1321 #define SHIFT_KEY_DOWN (GetAsyncKeyState(VK_SHIFT)&0x8000)
1322 #define ALT_KEY_DOWN (GetAsyncKeyState(VK_MENU)&0x8000)
1325 void WDL_CursesEditor::getLinesFromClipboard(WDL_FastString &buf, WDL_PtrList<const char> &lines)
1327 #ifdef WDL_IS_FAKE_CURSES
1328 if (CURSES_INSTANCE)
1330 HANDLE h;
1331 OpenClipboard(CURSES_INSTANCE->m_hwnd);
1332 #ifdef CF_UNICODETEXT
1333 h=GetClipboardData(CF_UNICODETEXT);
1334 if (h)
1336 wchar_t *t=(wchar_t *)GlobalLock(h);
1337 int s=(int)(GlobalSize(h)/2);
1338 while (s-- > 0)
1340 char b[32];
1341 if (!*t) break;
1342 WDL_MakeUTFChar(b,*t++,sizeof(b));
1343 buf.Append(b);
1346 GlobalUnlock(t);
1349 #endif
1350 if (!buf.GetLength())
1352 h=GetClipboardData(CF_TEXT);
1353 if (h)
1355 char *t=(char *)GlobalLock(h);
1356 int s=(int)(GlobalSize(h));
1357 buf.Set(t,s);
1358 GlobalUnlock(t);
1361 CloseClipboard();
1363 else
1364 #endif
1366 buf.Set(s_fake_clipboard.Get());
1369 if (buf.Get() && buf.Get()[0])
1371 ReplaceTabs(&buf,m_indent_size);
1373 char *src=(char*)buf.Get();
1374 while (*src)
1376 char *seek=src;
1377 while (*seek && *seek != '\r' && *seek != '\n') seek++;
1378 char hadclr=*seek;
1379 if (*seek) *seek++=0;
1380 lines.Add(src);
1382 if (hadclr == '\r' && *seek == '\n') seek++;
1384 if (hadclr && !*seek)
1386 lines.Add("");
1388 src=seek;
1394 int WDL_CursesEditor::onChar(int c)
1396 // multitab
1397 if (m_ui_state == UI_STATE_MESSAGE)
1399 m_ui_state=UI_STATE_NORMAL;
1400 draw();
1401 setCursor();
1404 if (m_ui_state == UI_STATE_NORMAL && !SHIFT_KEY_DOWN && !ALT_KEY_DOWN && c =='W'-'A'+1)
1406 if (GetTab(0) == this) return 0; // first in list = do nothing
1408 if (IsDirty())
1410 m_ui_state=UI_STATE_SAVE_ON_CLOSE;
1411 attrset(COLOR_MESSAGE);
1412 bkgdset(COLOR_MESSAGE);
1413 mvaddstr(LINES-1,0,"Save file before closing (y/N)? ");
1414 clrtoeol();
1415 attrset(0);
1416 bkgdset(0);
1418 else
1420 CloseCurrentTab();
1422 delete this;
1423 // context no longer valid!
1424 return 1;
1426 return 0;
1429 if (m_ui_state == UI_STATE_SAVE_ON_CLOSE)
1431 if (c>=0 && (isalnum(c) || isprint(c) || c==27))
1433 if (c == 27)
1435 m_ui_state=UI_STATE_NORMAL;
1436 draw();
1437 draw_message("Cancelled close of file.");
1438 setCursor();
1439 return 0;
1441 if (toupper(c) == 'N' || toupper(c) == 'Y')
1443 if (toupper(c) == 'Y')
1445 if(updateFile())
1447 m_ui_state=UI_STATE_NORMAL;
1448 draw();
1449 draw_message("Error writing file, changes not saved!");
1450 setCursor();
1451 return 0;
1454 CloseCurrentTab();
1456 delete this;
1457 // this no longer valid, return 1 to avoid future calls in onChar()
1459 return 1;
1462 return 0;
1464 else if (m_ui_state == UI_STATE_SAVE_AS_NEW)
1466 if (c>=0 && (isalnum(c) || isprint(c) || c==27 || c == '\r' || c=='\n'))
1468 m_ui_state=UI_STATE_NORMAL;
1469 if (toupper(c) == 'N' || c == 27)
1471 draw();
1472 draw_message("Cancelled create new file.");
1473 setCursor();
1474 return 0;
1477 AddTab(m_newfn.Get());
1479 return 0;
1482 if ((c==27 || c==29 || (c >= KEY_F1 && c<=KEY_F10)) && CTRL_KEY_DOWN)
1484 int idx=c-KEY_F1;
1485 bool rel=true;
1486 if (c==27) idx=-1;
1487 else if (c==29) idx=1;
1488 else rel=false;
1489 SwitchTab(idx,rel);
1491 return 1;
1493 // end multitab
1495 if (m_ui_state == UI_STATE_SEARCH || m_ui_state == UI_STATE_SEARCH2)
1497 switch (c)
1499 case '\r': case '\n':
1500 m_ui_state=UI_STATE_NORMAL;
1501 runSearch();
1502 break;
1503 case 27:
1504 m_ui_state=UI_STATE_NORMAL;
1505 draw();
1506 setCursor();
1507 draw_message("Find cancelled.");
1508 break;
1509 case KEY_BACKSPACE:
1510 if (s_search_string[0])
1512 char *p = s_search_string;
1513 if (*p) for (;;)
1515 int sz=wdl_utf8_parsechar(p,NULL);
1516 if (!p[sz])
1518 *p=0;
1519 break;
1521 p+=sz;
1524 m_ui_state=UI_STATE_SEARCH2;
1525 break;
1526 case KEY_IC:
1527 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN) break;
1528 case 'V'-'A'+1:
1531 WDL_PtrList<const char> lines;
1532 WDL_FastString buf;
1533 getLinesFromClipboard(buf,lines);
1534 if (lines.Get(0))
1536 if (m_ui_state==UI_STATE_SEARCH)
1538 s_search_string[0]=0;
1539 m_ui_state=UI_STATE_SEARCH2;
1541 lstrcatn(s_search_string,lines.Get(0),sizeof(s_search_string));
1544 break;
1545 default:
1546 if (VALIDATE_TEXT_CHAR(c))
1548 int l=m_ui_state == UI_STATE_SEARCH ? 0 : strlen(s_search_string);
1549 m_ui_state = UI_STATE_SEARCH2;
1550 if (l < (int)sizeof(s_search_string)-8)
1552 WDL_MakeUTFChar(s_search_string+l,c,8);
1555 break;
1557 if (m_ui_state == UI_STATE_SEARCH || m_ui_state == UI_STATE_SEARCH2)
1559 attrset(COLOR_MESSAGE);
1560 bkgdset(COLOR_MESSAGE);
1561 mvaddstr(LINES-1,29,s_search_string);
1562 clrtoeol();
1563 attrset(0);
1564 bkgdset(0);
1566 return 0;
1568 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)
1570 if (SHIFT_KEY_DOWN)
1572 if (!m_selecting)
1574 m_select_x2=m_select_x1=m_curs_x; m_select_y2=m_select_y1=m_curs_y;
1575 m_selecting=1;
1578 else if (m_selecting) { m_selecting=0; draw(); }
1581 switch(c)
1583 case 'O'-'A'+1:
1584 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
1586 if (m_pane_div <= 0.0 || m_pane_div >= 1.0)
1588 onChar('P'-'A'+1);
1590 if (m_pane_div > 0.0 && m_pane_div < 1.0)
1592 m_curpane=!m_curpane;
1593 draw();
1594 draw_status_state();
1595 int paney[2], paneh[2];
1596 GetPaneDims(paney, paneh);
1597 if (m_curs_y-m_paneoffs_y[m_curpane] < 0) m_curs_y=m_paneoffs_y[m_curpane];
1598 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;
1599 setCursor();
1602 break;
1603 case 'P'-'A'+1:
1604 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
1606 if (m_pane_div <= 0.0 || m_pane_div >= 1.0)
1608 m_pane_div=0.5;
1609 m_paneoffs_y[1]=m_paneoffs_y[0];
1611 else
1613 m_pane_div=1.0;
1614 if (m_curpane) m_paneoffs_y[0]=m_paneoffs_y[1];
1615 m_curpane=0;
1617 draw();
1618 draw_status_state();
1620 int paney[2], paneh[2];
1621 GetPaneDims(paney, paneh);
1622 setCursor();
1624 break;
1626 case 407:
1627 case 'Z'-'A'+1:
1628 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
1630 if (m_undoStack_pos > 0)
1632 m_undoStack_pos--;
1633 loadUndoState(m_undoStack.Get(m_undoStack_pos),1);
1634 draw();
1635 setCursor();
1636 char buf[512];
1637 snprintf(buf,sizeof(buf),"Undid action - %d items in undo buffer",m_undoStack_pos);
1638 draw_message(buf);
1640 else
1642 draw_message("Can't Undo");
1644 break;
1646 // fall through
1647 case 'Y'-'A'+1:
1648 if ((c == 'Z'-'A'+1 || !SHIFT_KEY_DOWN) && !ALT_KEY_DOWN)
1650 if (m_undoStack_pos < m_undoStack.GetSize()-1)
1652 m_undoStack_pos++;
1653 loadUndoState(m_undoStack.Get(m_undoStack_pos),0);
1654 draw();
1655 setCursor();
1656 char buf[512];
1657 snprintf(buf,sizeof(buf),"Redid action - %d items in redo buffer",m_undoStack.GetSize()-m_undoStack_pos-1);
1658 draw_message(buf);
1660 else
1662 draw_message("Can't Redo");
1665 break;
1666 case KEY_IC:
1667 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
1669 s_overwrite=!s_overwrite;
1670 setCursor();
1671 break;
1673 // fall through
1674 case 'V'-'A'+1:
1675 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
1677 // generate a m_clipboard using win32 clipboard data
1678 WDL_PtrList<const char> lines;
1679 WDL_FastString buf;
1680 getLinesFromClipboard(buf,lines);
1682 if (lines.GetSize())
1684 removeSelect();
1685 // insert lines at m_curs_y,m_curs_x
1686 if (m_curs_y > m_text.GetSize()) m_curs_y=m_text.GetSize();
1687 if (m_curs_y < 0) m_curs_y=0;
1689 preSaveUndoState();
1690 WDL_FastString poststr;
1691 int x;
1692 int indent_to_pos = -1;
1693 int skip_source_indent=0; // number of characters of whitespace to (potentially) ignore when pasting
1695 for (x = 0; x < lines.GetSize(); x ++)
1697 WDL_FastString *str=m_text.Get(m_curs_y);
1698 const char *tstr=lines.Get(x);
1699 if (!tstr) tstr="";
1700 if (!x)
1702 if (str)
1704 int bytepos = WDL_utf8_charpos_to_bytepos(str->Get(),m_curs_x);
1706 if (bytepos < 0) bytepos=0;
1707 int tmp=str->GetLength();
1708 if (bytepos > tmp) bytepos=tmp;
1710 poststr.Set(str->Get()+bytepos);
1711 str->SetLen(bytepos);
1713 const char *p = str->Get();
1714 while (*p == ' ' || *p == '\t') p++;
1715 if (!*p && p > str->Get()) // if all whitespace leading up to this
1717 if (bytepos > 0)
1719 int i;
1720 skip_source_indent=1024;
1721 for (i = 0; skip_source_indent > 0 && i < lines.GetSize(); i ++)
1723 int a=0;
1724 const char *p = lines.Get(i);
1725 while (a < skip_source_indent && p[a] == ' ') a++;
1726 if (a < skip_source_indent && p[a]) skip_source_indent=a;
1730 indent_to_pos = bytepos;
1733 str->Append(skip_indent(tstr,skip_source_indent));
1735 else
1737 m_text.Insert(m_curs_y,(str=new WDL_FastString(skip_indent(tstr,skip_source_indent))));
1740 if (lines.GetSize() > 1)
1742 m_curs_y++;
1744 else
1746 m_curs_x = WDL_utf8_get_charlen(str->Get());
1747 str->Append(poststr.Get());
1750 else if (x == lines.GetSize()-1)
1752 WDL_FastString *s=newIndentedFastString(skip_indent(tstr,skip_source_indent),indent_to_pos);
1753 m_curs_x = WDL_utf8_get_charlen(s->Get());
1754 s->Append(poststr.Get());
1755 m_text.Insert(m_curs_y,s);
1757 else
1759 m_text.Insert(m_curs_y,newIndentedFastString(skip_indent(tstr,skip_source_indent),indent_to_pos));
1760 m_curs_y++;
1763 draw();
1764 setCursor();
1765 draw_message("Pasted");
1766 saveUndoState();
1768 else
1770 setCursor();
1771 draw_message("Clipboard empty");
1774 break;
1776 case KEY_DC:
1777 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
1779 WDL_FastString *s;
1780 if (m_selecting)
1782 preSaveUndoState();
1783 removeSelect();
1784 draw();
1785 saveUndoState();
1786 setCursor();
1788 else if ((s=m_text.Get(m_curs_y)))
1790 const int xbyte = WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x);
1791 if (xbyte < s->GetLength())
1793 preSaveUndoState();
1795 const int xbytesz=WDL_utf8_charpos_to_bytepos(s->Get()+xbyte,1);
1796 bool hadCom = LineCanAffectOtherLines(s->Get(),xbyte,xbytesz);
1797 s->DeleteSub(xbyte,xbytesz);
1798 if (!hadCom) hadCom = LineCanAffectOtherLines(s->Get(),xbyte,0);
1799 draw(hadCom ? -1 : m_curs_y);
1800 saveUndoState();
1801 setCursor();
1803 else // append next line to us
1805 if (m_curs_y < m_text.GetSize()-1)
1807 preSaveUndoState();
1809 WDL_FastString *nl=m_text.Get(m_curs_y+1);
1810 if (nl)
1812 s->Append(nl->Get());
1814 m_text.Delete(m_curs_y+1,true);
1816 draw();
1817 saveUndoState();
1818 setCursor();
1822 break;
1824 case 'C'-'A'+1:
1825 case 'X'-'A'+1:
1826 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN && m_selecting)
1828 if (c!= 'C'-'A'+1) m_selecting=0;
1829 int miny,maxy,minx,maxx;
1830 int x;
1831 getselectregion(minx,miny,maxx,maxy);
1832 const char *status="";
1833 char statusbuf[512];
1835 if (minx != maxx|| miny != maxy)
1837 int bytescopied=0;
1838 s_fake_clipboard.Set("");
1840 int lht=0,fht=0;
1841 if (c != 'C'-'A'+1) preSaveUndoState();
1843 for (x = miny; x <= maxy; x ++)
1845 WDL_FastString *s=m_text.Get(x);
1846 if (s)
1848 const char *str=s->Get();
1849 const int sx=x == miny ? WDL_utf8_charpos_to_bytepos(s->Get(),minx) : 0;
1850 const int ex=x == maxy ? WDL_utf8_charpos_to_bytepos(s->Get(),maxx) : s->GetLength();
1852 bytescopied += ex-sx + (x!=maxy);
1853 if (s_fake_clipboard.Get() && s_fake_clipboard.Get()[0]) s_fake_clipboard.Append("\r\n");
1855 const int oldlen = s_fake_clipboard.GetLength();
1856 s_fake_clipboard.Append(ex-sx?str+sx:"",ex-sx);
1857 if (m_write_leading_tabs>0 && sx==0 && oldlen < s_fake_clipboard.GetLength())
1859 const char *p = s_fake_clipboard.Get() + oldlen;
1860 int nt=0;
1861 const char *sp = countLeadingTabs(p,&nt,m_write_leading_tabs);
1862 if (nt && sp > p)
1864 s_fake_clipboard.DeleteSub(oldlen,(int)(sp-p));
1865 while (nt>0)
1867 int c = nt;
1868 if (c > 8) c=8;
1869 nt -= c;
1870 s_fake_clipboard.Insert("\t\t\t\t\t\t\t\t",oldlen,c);
1875 if (c != 'C'-'A'+1)
1877 if (sx == 0 && ex == s->GetLength()) // remove entire line
1879 m_text.Delete(x,true);
1880 if (x==miny) miny--;
1881 x--;
1882 maxy--;
1884 else { if (x==miny) fht=1; if (x == maxy) lht=1; s->DeleteSub(sx,ex-sx); }
1888 if (fht && lht && miny+1 == maxy)
1890 m_text.Get(miny)->Append(m_text.Get(maxy)->Get());
1891 m_text.Delete(maxy,true);
1893 if (c != 'C'-'A'+1)
1895 m_curs_y=miny;
1896 if (m_curs_y < 0) m_curs_y=0;
1897 m_curs_x=minx;
1898 saveUndoState();
1899 snprintf(statusbuf,sizeof(statusbuf),"Cut %d bytes",bytescopied);
1901 else
1902 snprintf(statusbuf,sizeof(statusbuf),"Copied %d bytes",bytescopied);
1904 #ifdef WDL_IS_FAKE_CURSES
1905 if (CURSES_INSTANCE)
1907 #ifdef CF_UNICODETEXT
1908 const int l=(WDL_utf8_get_charlen(s_fake_clipboard.Get())+1)*sizeof(wchar_t);
1909 HANDLE h=GlobalAlloc(GMEM_MOVEABLE,l);
1910 wchar_t *t=(wchar_t*)GlobalLock(h);
1911 if (t)
1913 WDL_MBtoWideStr(t,s_fake_clipboard.Get(),l);
1914 GlobalUnlock(h);
1916 OpenClipboard(CURSES_INSTANCE->m_hwnd);
1917 EmptyClipboard();
1918 SetClipboardData(CF_UNICODETEXT,h);
1919 #else
1920 int l=s_fake_clipboard.GetLength()+1;
1921 HANDLE h=GlobalAlloc(GMEM_MOVEABLE,l);
1922 void *t=GlobalLock(h);
1923 if (t)
1925 memcpy(t,s_fake_clipboard.Get(),l);
1926 GlobalUnlock(h);
1928 OpenClipboard(CURSES_INSTANCE->m_hwnd);
1929 EmptyClipboard();
1930 SetClipboardData(CF_TEXT,h);
1931 #endif
1932 CloseClipboard();
1934 #endif
1936 status=statusbuf;
1938 else status="No selection";
1940 draw();
1941 setCursor();
1942 draw_message(status);
1944 break;
1945 case 'A'-'A'+1:
1946 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
1948 m_selecting=1;
1949 m_select_x1=0;
1950 m_select_y1=0;
1951 m_select_y2=m_text.GetSize()-1;
1952 m_select_x2=0;
1953 if (m_text.Get(m_select_y2))
1954 m_select_x2=m_text.Get(m_select_y2)->GetLength();
1955 draw();
1956 setCursor();
1958 break;
1959 case 27:
1960 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN && m_selecting)
1962 m_selecting=0;
1963 draw();
1964 setCursor();
1965 break;
1967 break;
1968 case KEY_F3:
1969 case 'G'-'A'+1:
1970 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN && s_search_string[0])
1972 runSearch();
1973 return 0;
1975 // fall through
1976 case 'F'-'A'+1:
1977 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
1979 draw_message("");
1980 attrset(COLOR_MESSAGE);
1981 bkgdset(COLOR_MESSAGE);
1982 mvaddstr(LINES-1,0,"Find string (ESC to cancel): ");
1983 if (m_selecting && m_select_y1==m_select_y2)
1985 WDL_FastString* s=m_text.Get(m_select_y1);
1986 if (s)
1988 const char* p=s->Get();
1989 int xlo=wdl_min(m_select_x1, m_select_x2);
1990 int xhi=wdl_max(m_select_x1, m_select_x2);
1991 xlo = WDL_utf8_charpos_to_bytepos(p,xlo);
1992 xhi = WDL_utf8_charpos_to_bytepos(p,xhi);
1993 if (xhi > xlo && xhi-xlo < sizeof(s_search_string))
1995 lstrcpyn(s_search_string, p+xlo, xhi-xlo+1);
1999 addstr(s_search_string);
2000 clrtoeol();
2001 attrset(0);
2002 bkgdset(0);
2003 m_ui_state=UI_STATE_SEARCH; // find, initial
2005 break;
2006 case KEY_DOWN:
2008 if (CTRL_KEY_DOWN)
2010 int paney[2], paneh[2];
2011 GetPaneDims(paney, paneh);
2012 int maxscroll=m_text.GetSize()-paneh[m_curpane]+4;
2013 if (m_paneoffs_y[m_curpane] < maxscroll-1)
2015 m_paneoffs_y[m_curpane]++;
2016 if (m_curs_y < m_paneoffs_y[m_curpane]) m_curs_y=m_paneoffs_y[m_curpane];
2017 draw();
2020 else
2022 m_curs_y++;
2023 if (m_curs_y>=m_text.GetSize()) m_curs_y=m_text.GetSize()-1;
2024 if (m_curs_y < 0) m_curs_y=0;
2026 if (m_selecting) { setCursor(1); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); }
2027 setCursor(1);
2029 break;
2030 case KEY_UP:
2032 if (CTRL_KEY_DOWN)
2034 if (m_paneoffs_y[m_curpane] > 0)
2036 int paney[2], paneh[2];
2037 GetPaneDims(paney, paneh);
2038 m_paneoffs_y[m_curpane]--;
2039 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;
2040 if (m_curs_y < 0) m_curs_y=0;
2041 draw();
2044 else
2046 if(m_curs_y>0) m_curs_y--;
2048 if (m_selecting) { setCursor(1); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); }
2049 setCursor(1);
2051 break;
2052 case KEY_PPAGE:
2054 if (m_curs_y > m_paneoffs_y[m_curpane])
2056 m_curs_y=m_paneoffs_y[m_curpane];
2057 if (m_curs_y < 0) m_curs_y=0;
2059 else
2061 int paney[2], paneh[2];
2062 GetPaneDims(paney, paneh);
2063 m_curs_y -= paneh[m_curpane];
2064 if (m_curs_y < 0) m_curs_y=0;
2065 m_paneoffs_y[m_curpane]=m_curs_y;
2067 if (m_selecting) { setCursor(1); m_select_x2=m_curs_x; m_select_y2=m_curs_y; }
2068 draw();
2069 setCursor(1);
2071 break;
2072 case KEY_NPAGE:
2074 int paney[2], paneh[2];
2075 GetPaneDims(paney, paneh);
2076 if (m_curs_y >= m_paneoffs_y[m_curpane]+paneh[m_curpane]-1) m_paneoffs_y[m_curpane]=m_curs_y-1;
2077 m_curs_y = m_paneoffs_y[m_curpane]+paneh[m_curpane]-1;
2078 if (m_curs_y >= m_text.GetSize()) m_curs_y=m_text.GetSize()-1;
2079 if (m_curs_y < 0) m_curs_y=0;
2080 if (m_selecting) { setCursor(1); m_select_x2=m_curs_x; m_select_y2=m_curs_y; }
2081 draw();
2082 setCursor(1);
2084 break;
2085 case KEY_RIGHT:
2087 if (1) // wrap across lines
2089 WDL_FastString *s = m_text.Get(m_curs_y);
2090 if (s && m_curs_x >= WDL_utf8_get_charlen(s->Get()) && m_curs_y < m_text.GetSize()) { m_curs_y++; m_curs_x = -1; }
2093 if(m_curs_x<0)
2095 m_curs_x=0;
2097 else
2099 if (CTRL_KEY_DOWN)
2101 WDL_FastString *s = m_text.Get(m_curs_y);
2102 if (!s) break;
2103 int bytepos = WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x);
2105 if (bytepos >= s->GetLength()) break;
2106 int lastType = categorizeCharForWordNess(s->Get()[bytepos++]);
2107 while (bytepos < s->GetLength())
2109 int thisType = categorizeCharForWordNess(s->Get()[bytepos]);
2110 if (thisType != lastType && thisType != 0) break;
2111 lastType=thisType;
2112 bytepos++;
2114 m_curs_x = WDL_utf8_bytepos_to_charpos(s->Get(),bytepos);
2116 else
2118 m_curs_x++;
2121 if (m_selecting) { setCursor(); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); }
2122 setCursor();
2124 break;
2125 case KEY_LEFT:
2127 bool doMove=true;
2128 if (1) // wrap across lines
2130 WDL_FastString *s = m_text.Get(m_curs_y);
2131 if (s && m_curs_y>0 && m_curs_x == 0)
2133 s = m_text.Get(--m_curs_y);
2134 if (s)
2136 m_curs_x = WDL_utf8_get_charlen(s->Get());
2137 doMove=false;
2142 if(m_curs_x>0 && doMove)
2144 if (CTRL_KEY_DOWN)
2146 WDL_FastString *s = m_text.Get(m_curs_y);
2147 if (!s) break;
2148 int bytepos = WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x);
2149 if (bytepos > s->GetLength()) bytepos = s->GetLength();
2150 bytepos--;
2152 int lastType = categorizeCharForWordNess(s->Get()[bytepos--]);
2153 while (bytepos >= 0)
2155 int thisType = categorizeCharForWordNess(s->Get()[bytepos]);
2156 if (thisType != lastType && lastType != 0) break;
2157 lastType=thisType;
2158 bytepos--;
2160 m_curs_x = WDL_utf8_bytepos_to_charpos(s->Get(),bytepos+1);
2162 else
2164 m_curs_x--;
2167 if (m_selecting) { setCursor(); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); }
2168 setCursor();
2170 break;
2171 case KEY_HOME:
2173 m_curs_x=0;
2174 if (CTRL_KEY_DOWN) m_curs_y=0;
2175 if (m_selecting) { setCursor(); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); }
2176 setCursor();
2178 break;
2179 case KEY_END:
2181 if (m_text.Get(m_curs_y)) m_curs_x=WDL_utf8_get_charlen(m_text.Get(m_curs_y)->Get());
2182 if (CTRL_KEY_DOWN) m_curs_y=m_text.GetSize();
2183 if (m_selecting) { setCursor(); m_select_x2=m_curs_x; m_select_y2=m_curs_y; draw(); }
2184 setCursor();
2186 break;
2187 case KEY_BACKSPACE: // backspace, baby
2188 if (m_selecting)
2190 preSaveUndoState();
2191 removeSelect();
2192 draw();
2193 saveUndoState();
2194 setCursor();
2196 else if (m_curs_x > 0)
2198 WDL_FastString *tl=m_text.Get(m_curs_y);
2199 if (tl)
2201 preSaveUndoState();
2203 int del_sz=1;
2204 if (m_indent_size > 0 && !(m_curs_x % m_indent_size))
2206 const char *p = tl->Get();
2207 int i=0;
2208 while (i<m_curs_x && p[i]== ' ') i++;
2209 if (i == m_curs_x && m_curs_x>=m_indent_size)
2211 del_sz=m_indent_size;
2215 const int xbyte = WDL_utf8_charpos_to_bytepos(tl->Get(),m_curs_x - del_sz);
2216 const int xbytesz=WDL_utf8_charpos_to_bytepos(tl->Get()+xbyte,del_sz);
2218 bool hadCom = LineCanAffectOtherLines(tl->Get(), xbyte,xbytesz);
2219 tl->DeleteSub(xbyte,xbytesz);
2220 m_curs_x-=del_sz;
2222 if (!hadCom) hadCom = LineCanAffectOtherLines(tl->Get(),xbyte,0);
2223 draw(hadCom?-1:m_curs_y);
2224 saveUndoState();
2225 setCursor();
2228 else // append current line to previous line
2230 WDL_FastString *fl=m_text.Get(m_curs_y-1), *tl=m_text.Get(m_curs_y);
2231 if (!tl)
2233 m_curs_y--;
2234 if (fl) m_curs_x=WDL_utf8_get_charlen(fl->Get());
2235 draw();
2236 saveUndoState();
2237 setCursor();
2239 else if (fl)
2241 preSaveUndoState();
2242 m_curs_x=WDL_utf8_get_charlen(fl->Get());
2243 fl->Append(tl->Get());
2245 m_text.Delete(m_curs_y--,true);
2246 draw();
2247 saveUndoState();
2248 setCursor();
2251 break;
2252 case 'L'-'A'+1:
2253 if (!SHIFT_KEY_DOWN && !ALT_KEY_DOWN)
2255 draw();
2256 setCursor();
2258 break;
2259 case 13: //KEY_ENTER:
2260 //insert newline
2261 preSaveUndoState();
2263 if (m_selecting) { removeSelect(); draw(); setCursor(); }
2264 if (m_curs_y >= m_text.GetSize())
2266 m_curs_y=m_text.GetSize();
2267 m_text.Add(new WDL_FastString);
2269 if (s_overwrite)
2271 WDL_FastString *s = m_text.Get(m_curs_y);
2272 int plen=0;
2273 const char *pb=NULL;
2274 if (s)
2276 pb = s->Get();
2277 while (plen < m_curs_x && (pb[plen]== ' ' || pb[plen] == '\t')) plen++;
2279 if (++m_curs_y >= m_text.GetSize())
2281 m_curs_y = m_text.GetSize();
2282 WDL_FastString *ns=new WDL_FastString;
2283 if (plen>0) ns->Set(pb,plen);
2284 m_text.Insert(m_curs_y,ns);
2286 s = m_text.Get(m_curs_y);
2287 if (s && plen > s->GetLength()) plen=s->GetLength();
2288 m_curs_x=s ? WDL_utf8_bytepos_to_charpos(s->Get(),plen) : plen;
2290 else
2292 WDL_FastString *s = m_text.Get(m_curs_y);
2293 if (s)
2295 int bytepos = WDL_utf8_charpos_to_bytepos(s->Get(),m_curs_x);
2296 if (bytepos > s->GetLength()) bytepos = s->GetLength();
2297 WDL_FastString *nl = new WDL_FastString();
2298 int plen=0;
2299 const char *pb = s->Get();
2300 while (plen < bytepos && (pb[plen]== ' ' || pb[plen] == '\t')) plen++;
2302 if (plen>0) nl->Set(pb,plen);
2304 nl->Append(pb+bytepos);
2305 m_text.Insert(++m_curs_y,nl);
2306 s->SetLen(bytepos);
2307 m_curs_x=WDL_utf8_bytepos_to_charpos(nl->Get(),plen);
2310 m_offs_x=0;
2312 draw();
2313 saveUndoState();
2314 setCursor();
2315 break;
2316 case '\t':
2317 if (m_selecting)
2319 preSaveUndoState();
2321 bool isRev = !!(GetAsyncKeyState(VK_SHIFT)&0x8000);
2322 indentSelect(isRev?-m_indent_size:m_indent_size);
2323 // indent selection:
2324 draw();
2325 setCursor();
2326 saveUndoState();
2327 break;
2329 default:
2330 //insert char
2331 if(VALIDATE_TEXT_CHAR(c))
2333 preSaveUndoState();
2335 if (m_selecting) { removeSelect(); draw(); setCursor(); }
2336 if (!m_text.Get(m_curs_y)) m_text.Insert(m_curs_y,new WDL_FastString);
2338 WDL_FastString *ss;
2339 if ((ss=m_text.Get(m_curs_y)))
2341 char str[64];
2342 int slen=1,slen_bytes=1;
2343 if (c == '\t')
2345 slen = wdl_min(m_indent_size,64);
2346 if (slen<1) slen=1;
2348 const int partial = m_curs_x % slen;
2349 if (partial) slen -= partial;
2351 int x;
2352 for(x=0;x<slen;x++) str[x]=' ';
2353 slen_bytes=slen;
2355 #ifdef __APPLE__
2356 else if (c == 'n' && !SHIFT_KEY_DOWN && ALT_KEY_DOWN)
2358 str[0]='~';
2360 #endif
2361 else
2363 slen_bytes = WDL_MakeUTFChar(str,c,32);
2364 str[slen_bytes]=0;
2368 const int xbyte = WDL_utf8_charpos_to_bytepos(ss->Get(),m_curs_x);
2370 bool hadCom = LineCanAffectOtherLines(ss->Get(),xbyte,0);
2371 if (s_overwrite)
2373 const int xbytesz_del=WDL_utf8_charpos_to_bytepos(ss->Get()+xbyte,slen);
2374 if (!hadCom) hadCom = LineCanAffectOtherLines(ss->Get(),xbyte,xbytesz_del);
2375 ss->DeleteSub(xbyte,xbytesz_del);
2378 ss->Insert(str,xbyte,slen_bytes);
2379 if (!hadCom) hadCom = LineCanAffectOtherLines(ss->Get(),xbyte,slen_bytes);
2381 m_curs_x += slen;
2383 draw(hadCom ? -1 : m_curs_y);
2385 saveUndoState();
2386 setCursor();
2388 break;
2390 return 0;
2393 void WDL_CursesEditor::preSaveUndoState()
2395 editUndoRec *rec= m_undoStack.Get(m_undoStack_pos);
2396 if (rec)
2398 rec->m_offs_x[1]=m_offs_x;
2399 rec->m_curs_x[1]=m_curs_x;
2400 rec->m_curs_y[1]=m_curs_y;
2401 rec->m_curpane[1]=m_curpane;
2402 rec->m_pane_div[1]=m_pane_div;
2403 rec->m_paneoffs_y[1][0]=m_paneoffs_y[0];
2404 rec->m_paneoffs_y[1][1]=m_paneoffs_y[1];
2407 void WDL_CursesEditor::saveUndoState()
2409 editUndoRec *rec=new editUndoRec;
2410 rec->m_offs_x[1]=rec->m_offs_x[0]=m_offs_x;
2411 rec->m_curs_x[1]=rec->m_curs_x[0]=m_curs_x;
2412 rec->m_curs_y[1]=rec->m_curs_y[0]=m_curs_y;
2413 rec->m_curpane[1]=rec->m_curpane[0]=m_curpane;
2414 rec->m_pane_div[1]=rec->m_pane_div[0]=m_pane_div;
2415 rec->m_paneoffs_y[1][0]=rec->m_paneoffs_y[0][0]=m_paneoffs_y[0];
2416 rec->m_paneoffs_y[1][1]=rec->m_paneoffs_y[0][1]=m_paneoffs_y[1];
2418 editUndoRec *lrec[5];
2419 lrec[0] = m_undoStack.Get(m_undoStack_pos);
2420 lrec[1] = m_undoStack.Get(m_undoStack_pos+1);
2421 lrec[2] = m_undoStack.Get(m_undoStack_pos-1);
2422 lrec[3] = m_undoStack.Get(m_undoStack_pos-2);
2423 lrec[4] = m_undoStack.Get(m_undoStack_pos-3);
2425 int x;
2426 for (x = 0; x < m_text.GetSize(); x ++)
2428 refcntString *ns = NULL;
2430 const WDL_FastString *s=m_text.Get(x);
2431 const int s_len=s?s->GetLength():0;
2433 if (s_len>0)
2435 const char *cs=s?s->Get():"";
2436 int w;
2437 for(w=0;w<5 && !ns;w++)
2439 if (lrec[w])
2441 int y;
2442 for (y=0; y<15; y ++)
2444 refcntString *a = lrec[w]->m_htext.Get(x + ((y&1) ? -(y+1)/2 : y/2));
2445 if (a && a->getStrLen() == s_len && !strcmp(a->getStr(),cs))
2447 ns = a;
2448 break;
2454 if (!ns) ns = new refcntString(cs,s_len);
2456 ns->AddRef();
2458 else
2460 // ns is NULL, blank line
2463 rec->m_htext.Add(ns);
2466 for (x = m_undoStack.GetSize()-1; x > m_undoStack_pos; x --)
2468 m_undoStack.Delete(x,true);
2470 if (m_clean_undopos > m_undoStack_pos)
2471 m_clean_undopos=-1;
2473 if (m_undoStack.GetSize() > m_max_undo_states)
2475 for (x = 1; x < m_undoStack.GetSize()/2; x += 2)
2477 if (x != m_clean_undopos)// don't delete our preferred special (last saved) one
2479 m_undoStack.Delete(x,true);
2480 if (m_clean_undopos > x) m_clean_undopos--;
2481 x--;
2485 m_undoStack_pos = m_undoStack.GetSize()-1;
2487 m_undoStack_pos++;
2488 m_undoStack.Add(rec);
2491 void WDL_CursesEditor::loadUndoState(editUndoRec *rec, int idx)
2493 int x;
2494 m_text.Empty(true);
2495 for (x = 0; x < rec->m_htext.GetSize(); x ++)
2497 refcntString *s=rec->m_htext.Get(x);
2498 m_text.Add(new WDL_FastString(s?s->getStr():""));
2501 m_offs_x=rec->m_offs_x[idx];
2502 m_curs_x=rec->m_curs_x[idx];
2503 m_curs_y=rec->m_curs_y[idx];
2505 m_curpane=rec->m_curpane[idx];
2506 m_pane_div=rec->m_pane_div[idx];
2507 m_paneoffs_y[0]=rec->m_paneoffs_y[idx][0];
2508 m_paneoffs_y[1]=rec->m_paneoffs_y[idx][1];
2509 m_selecting=false;
2513 void WDL_CursesEditor::RunEditor()
2515 int x;
2516 for(x=0;x<16;x++)
2518 if (!CURSES_INSTANCE) break;
2520 int thischar = getch();
2521 if (thischar==ERR) break;
2523 if (onChar(thischar)) break;
2527 void WDL_CursesEditor::draw_top_line()
2529 int ypos=0;
2530 if (m_top_margin > 1)
2532 int xpos=0;
2533 int x;
2534 move(ypos++,0);
2535 const int cnt= GetTabCount();
2536 int tsz=16;
2537 // this is duplicated in onMouseMessage
2538 if (cnt>0) tsz=COLS/cnt;
2539 if (tsz>128)tsz=128;
2540 if (tsz<12) tsz=12;
2542 for (x= 0; x < cnt && xpos < COLS; x ++)
2544 WDL_CursesEditor *ed = GetTab(x);
2545 if (ed)
2547 char buf[128 + 8];
2548 memset(buf,' ',tsz);
2549 const char *p = WDL_get_filepart(ed->GetFileName());
2550 const int lp=strlen(p);
2551 int skip=0;
2552 if (x<9)
2554 if (tsz>16)
2556 const char *s = "<" CONTROL_KEY_NAME "+";
2557 memcpy(buf,s,skip = (int)strlen(s));
2559 buf[skip++]='F';
2560 buf[skip++] = '1'+x;
2561 buf[skip++] = '>';
2562 skip++;
2564 memcpy(buf+skip,p,wdl_min(tsz-1-skip,lp));
2565 buf[tsz]=0;
2566 int l = tsz;
2567 if (l > COLS-xpos) l = COLS-xpos;
2568 if (ed == this)
2570 attrset(SYNTAX_HIGHLIGHT2|A_BOLD);
2572 else
2574 attrset(A_NORMAL);
2576 addnstr(buf,l);
2577 xpos += l;
2580 if (xpos < COLS) clrtoeol();
2582 attrset(COLOR_TOPLINE|A_BOLD);
2583 bkgdset(COLOR_TOPLINE);
2584 const char *p=GetFileName();
2585 move(ypos,0);
2586 if (COLS>4)
2588 const int pl = (int) strlen(p);
2589 if (pl > COLS-1 && COLS > 4)
2591 addstr("...");
2592 p+=pl - (COLS-1) + 4;
2594 addstr(p);
2596 clrtoeol();
2599 void WDL_CursesEditor::OpenFileInTab(const char *fnp)
2601 if (!fnp[0]) return;
2603 FILE *fp = fopen(fnp,"rb");
2604 if (!fp)
2606 WDL_FastString s(fnp);
2607 m_newfn.Set(fnp);
2609 if (COLS > 25)
2611 int allowed = COLS-25;
2612 if (s.GetLength()>allowed)
2614 s.DeleteSub(0,s.GetLength() - allowed + 3);
2615 s.Insert("...",0);
2617 s.Insert("Create new file '",0);
2618 s.Append("' (Y/n)? ");
2620 else
2621 s.Set("Create new file (Y/n)? ");
2623 m_ui_state=UI_STATE_SAVE_AS_NEW;
2624 attrset(COLOR_MESSAGE);
2625 bkgdset(COLOR_MESSAGE);
2626 mvaddstr(LINES-1,0,s.Get());
2627 clrtoeol();
2628 attrset(0);
2629 bkgdset(0);
2631 else
2633 fclose(fp);
2634 int x;
2635 for (x=0;x<GetTabCount();x++)
2637 WDL_CursesEditor *e = GetTab(x);
2638 if (e && !stricmp(e->GetFileName(),fnp))
2640 SwitchTab(x,false);
2641 return;
2644 AddTab(fnp);