4 #include "../swell/swell.h"
8 #ifndef CURSES_INSTANCE
9 #define CURSES_INSTANCE ((win32CursesCtx*)m_cursesCtx)
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))
24 #define CONTROL_KEY_NAME "Cmd"
26 #define CONTROL_KEY_NAME "Ctrl"
29 WDL_FastString
WDL_CursesEditor::s_fake_clipboard
;
30 int WDL_CursesEditor::s_overwrite
=0;
31 char WDL_CursesEditor::s_search_string
[256];
34 #define WM_MOUSEWHEEL 0x20A
37 static void __curses_onresize(win32CursesCtx
*ctx
)
39 WDL_CursesEditor
*p
= (WDL_CursesEditor
*)ctx
->user_data
;
46 WDL_CursesEditor::WDL_CursesEditor(void *cursesCtx
)
49 m_write_leading_tabs
=0;
50 m_max_undo_states
= 500;
52 m_cursesCtx
= cursesCtx
;
58 m_select_x1
=m_select_y1
=m_select_x2
=m_select_y2
=0;
59 m_ui_state
=UI_STATE_NORMAL
;
68 m_paneoffs_y
[0]=m_paneoffs_y
[1]=0;
77 #ifdef WDL_IS_FAKE_CURSES
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
;
91 intrflush(stdscr
,FALSE
);
94 raw(); // disable ctrl+C etc. no way to kill if allow quit isn't defined, yay.
97 #ifdef WDL_IS_FAKE_CURSES
98 if (!curses_win32_global_user_colortab
&& (!m_cursesCtx
|| !CURSES_INSTANCE
->user_colortab
))
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
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));
129 paney
[0]=m_top_margin
;
130 paney
[1]=m_top_margin
+pane_divy
+1;
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;
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];
152 case WM_CAPTURECHANGED
:
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
);
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];
182 m_pane_div
=(double)divy
/(double)(LINES
-m_top_margin
-m_bottom_margin
-1);
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
)
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
);
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
;
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)
239 else if (x
> m_offs_x
+COLS
)
243 for (a
=0; a
< paneh
[m_curpane
]; a
++)
245 WDL_FastString
* s
=m_text
.Get(m_paneoffs_y
[m_curpane
]+a
);
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
++;
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
);
268 const int l
= WDL_utf8_get_charlen(s
->Get());
269 if (m_select_x2
>l
) m_select_x2
= l
;
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();
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
]);
286 const char *url
=fs
->Get();
288 while (NULL
!= (url
= strstr(url
,"http://")))
290 if (url
!= fs
->Get() && url
[-1] > 0 && isalnum(url
[-1]))
296 const int soffs
= (int) (url
- fs
->Get());
299 while (p
< (tmp
+sizeof(tmp
)-1) &&
300 *url
&& *url
!= ' ' && *url
!= ')' && *url
!= '\t' && *url
!= '"' && *url
!= '\'' )
305 if (strlen(tmp
) >= 10 && x
>= soffs
&& x
<(url
-fs
->Get()))
307 ShellExecute(hwnd
,"open",tmp
,"","",0);
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)
324 // this is duplicated in draw_top_line
327 SwitchTab(x
/tsz
,false);
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
;
345 if (cy
> LINES
) cy
=LINES
;
348 m_ui_state
=UI_STATE_NORMAL
; // any click clears the state
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
)
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
];
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
)
385 m_scrollcap_yoffs
=mousey
-st
;
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
])
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();
406 if (uMsg
== WM_LBUTTONDOWN
) m_selecting
=0;
408 if (pane
>= 0) m_curpane
=pane
;
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
))
431 m_select_x2
=m_curs_x
;
432 m_select_y2
=m_curs_y
;
435 else if (uMsg
== WM_LBUTTONDBLCLK
&& s
&& slen
)
439 int x1
=WDL_utf8_charpos_to_bytepos(s
->Get(),m_curs_x
);
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
;
447 m_curs_x
=m_select_x2
=x2
;
448 m_select_y1
=m_select_y2
=m_curs_y
;
457 if (uMsg
== WM_LBUTTONDOWN
)
461 else if (uMsg
== WM_RBUTTONDOWN
)
475 if (CURSES_INSTANCE
->m_font_h
)
479 ScreenToClient(hwnd
, &p
);
480 p
.y
/= CURSES_INSTANCE
->m_font_h
;
482 int paney
[2], paneh
[2];
483 GetPaneDims(paney
, paneh
);
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;
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();
508 WDL_CursesEditor::~WDL_CursesEditor()
512 m_undoStack
.Empty(true);
515 int WDL_CursesEditor::init(const char *fn
, const char *init_if_empty
)
518 FILE *fh
=fopenUTF8(fn
,"rb");
524 fh
=fopenUTF8(fn
,"w+b");
527 fwrite(init_if_empty
,1,strlen(init_if_empty
),fh
);
529 fseek(fh
,0,SEEK_SET
);
535 m_clean_undopos
=m_undoStack_pos
;
546 int WDL_CursesEditor::reload_file(bool clearundo
)
548 FILE *fh
=fopenUTF8(m_filename
.Get(),"rb");
557 m_undoStack
.Empty(true);
570 static void ReplaceTabs(WDL_FastString
*str
, int tabsz
)
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();
586 str
->Insert(s
,x
+1,insert_sz
);
592 void WDL_CursesEditor::loadLines(FILE *fh
)
602 fgets(line
,sizeof(line
),fh
);
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)
615 memmove(line
,line
+3,l
+1);
620 while(l
>0 && (line
[l
-1]=='\r' || line
[l
-1]=='\n'))
622 if (line
[l
-1] == '\r') crcnt
++;
627 m_text
.Add(new WDL_FastString(line
));
631 const char *p
= line
;
632 if (*p
== '\t' && !tabstate
) tabstate
=1;
633 while (*p
== '\t') p
++;
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;
643 m_indent_size
=tab_cnv_size
;
644 m_write_leading_tabs
=tab_cnv_size
;
648 m_write_leading_tabs
=0;
652 for (x
=0;x
<m_text
.GetSize();x
++)
654 WDL_FastString
*s
= m_text
.Get(x
);
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
661 m_clean_undopos
=m_undoStack_pos
;
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
);
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
;
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
? "" : "*"));
692 if (len
< m_status_lastlen
)
694 int xpos
= COLS
-m_status_lastlen
-1;
697 while (xpos
++ < x
) addstr(" ");
699 m_status_lastlen
= len
;
706 mvaddnstr(line
, x
, str
, len
);
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;
723 if (m_want_x
>= 0) m_curs_x
=m_want_x
;
729 if (isVscroll
) m_want_x
=m_curs_x
;
735 if (m_curs_x
< m_offs_x
)
742 const int mw
= COLS
-3;
743 if (m_curs_x
>= m_offs_x
+ mw
)
745 m_offs_x
=m_curs_x
-mw
+1;
750 int paney
[2], paneh
[2];
751 GetPaneDims(paney
, paneh
);
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;
766 y
=m_curs_y
-m_paneoffs_y
[m_curpane
];
769 m_paneoffs_y
[m_curpane
] += y
;
773 else if (y
>= paneh
[m_curpane
])
775 m_paneoffs_y
[m_curpane
] += y
-paneh
[m_curpane
]+1;
776 y
=paneh
[m_curpane
]-1;
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;
794 if (l
&& m_ui_state
== UI_STATE_NORMAL
) m_ui_state
=UI_STATE_MESSAGE
;
795 if (l
> COLS
-2) l
=COLS
-2;
798 attrset(COLOR_MESSAGE
);
799 bkgdset(COLOR_MESSAGE
);
801 mvaddnstr(LINES
-(m_bottom_margin
>1?2:1),0,str
,l
);
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
)
821 mvaddstr(y
,0,p
+ WDL_utf8_charpos_to_bytepos(p
,m_offs_x
));
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
;
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
);
851 int miny
,maxy
,minx
,maxx
;
852 getselectregion(minx
,miny
,maxx
,maxy
);
854 if (line_n
>= miny
&& line_n
<= maxy
&& (miny
!= maxy
|| minx
< maxx
))
859 const int cols
= COLS
;
861 if (line_n
> miny
) minx
=0;
862 if (line_n
< maxy
) maxx
=cols
;
865 if (minx
> cols
) minx
=cols
;
866 if (maxx
> cols
) maxx
=cols
;
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
));
875 else if (maxx
==minx
&& !*p
)
877 attrset(COLOR_SELECTION
);
878 mvaddstr(y
,minx
," ");
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
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
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
;
915 int comment_state
= GetCommentStateForLineStart(lineidx
);
916 WDL_FastString
*s
=m_text
.Get(lineidx
);
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
);
933 __curses_invalidatefull((win32CursesCtx
*)m_cursesCtx
,false);
940 move(m_top_margin
,0);
946 for (pane
=0; pane
< 2; ++pane
)
948 int ln
=m_paneoffs_y
[pane
];
952 int comment_state
=GetCommentStateForLineStart(ln
);
954 for(i
=0; i
< h
; ++i
, ++ln
, ++y
)
956 WDL_FastString
*s
=m_text
.Get(ln
);
964 doDrawString(y
,ln
,s
->Get(),&comment_state
);
969 attrset(COLOR_BOTTOMLINE
);
970 bkgdset(COLOR_BOTTOMLINE
);
972 if (m_bottom_margin
>0)
975 #define BOLD(x) { attrset(COLOR_BOTTOMLINE|A_BOLD); addstr(x); attrset(COLOR_BOTTOMLINE&~A_BOLD); }
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)");
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 ");
993 BOLD("O"); addstr("therpane ");
994 addstr("no"); BOLD("P"); addstr("anes ");
996 BOLD("F"); addstr("ind ");
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
)
1020 for (i
=0;i
<tabsz
;i
++) if (p
[i
]!=' ') return p
;
1027 int WDL_CursesEditor::updateFile()
1029 FILE *fp
=fopenUTF8(m_filename
.Get(),"wb");
1032 for (x
= 0; x
< m_text
.GetSize(); x
++)
1034 WDL_FastString
*s
= m_text
.Get(x
);
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
);
1048 updateLastModTime();
1049 m_clean_undopos
= m_undoStack_pos
;
1054 void WDL_CursesEditor::updateLastModTime()
1056 struct stat srcstat
;
1057 if (!statUTF8(m_filename
.Get(), &srcstat
))
1060 m_filelastmod
= srcstat
.st_mtime
;
1062 m_filelastmod
= srcstat
.st_mtimespec
.tv_sec
;
1071 void WDL_CursesEditor::indentSelect(int amt
)
1073 if (m_selecting
) // remove selected text
1075 int miny
,maxy
,minx
,maxx
;
1077 getselectregion(minx
,miny
,maxx
,maxy
);
1084 if (maxx
<1 && maxy
>miny
) maxy
--; // exclude empty final line
1086 if (m_curs_y
>= miny
&& m_curs_y
<=maxy
)
1089 if (m_curs_x
<0)m_curs_x
=0;
1095 for (x
= miny
; x
<= maxy
; x
++)
1097 WDL_FastString
*s
=m_text
.Get(x
);
1101 while (a
<minspc
&& s
->Get()[a
]== ' ') a
++;
1105 if (minspc
>0 && minspc
< -amt
) amt
= -minspc
;
1108 for (x
= miny
; x
<= maxy
; x
++)
1110 WDL_FastString
*s
=m_text
.Get(x
);
1116 for(a
=0;a
<amt
;a
+=16)
1117 s
->Insert(" ",0,wdl_min(amt
-a
,16));
1122 while (a
<-amt
&& s
->Get()[a
]== ' ') 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
;
1138 getselectregion(minx
,miny
,maxx
,maxy
);
1141 if (m_curs_y
< 0) m_curs_y
=0;
1143 if (minx
!= maxx
|| miny
!= maxy
)
1146 for (x
= miny
; x
<= maxy
; x
++)
1148 WDL_FastString
*s
=m_text
.Get(x
);
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
--;
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);
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
++;
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)
1193 for (x
=0;x
<indent_to_pos
;x
++) s
->Append(" ");
1200 void WDL_CursesEditor::highlight_line(int line
)
1202 if (line
>= 0 && line
<= m_text
.GetSize())
1205 if (line
== m_text
.GetSize()) { line
--; nosel
=true; }
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());
1217 m_select_x2
=WDL_utf8_get_charlen(s
->Get());
1218 m_select_y1
=m_select_y2
=m_curs_y
;
1228 void WDL_CursesEditor::runSearch()
1230 if (s_search_string
[0])
1232 int wrapflag
=0,found
=0;
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
);
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
);
1262 if (!found
&& (m_curs_y
>0 || m_curs_x
> 0))
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
);
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
);
1295 snprintf(buf
,sizeof(buf
),"Found %s'%s' " CONTROL_KEY_NAME
"+G:next",wrapflag
?"(wrapped) ":"",s_search_string
);
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
));
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
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
)
1331 OpenClipboard(CURSES_INSTANCE
->m_hwnd
);
1332 #ifdef CF_UNICODETEXT
1333 h
=GetClipboardData(CF_UNICODETEXT
);
1336 wchar_t *t
=(wchar_t *)GlobalLock(h
);
1337 int s
=(int)(GlobalSize(h
)/2);
1342 WDL_MakeUTFChar(b
,*t
++,sizeof(b
));
1350 if (!buf
.GetLength())
1352 h
=GetClipboardData(CF_TEXT
);
1355 char *t
=(char *)GlobalLock(h
);
1356 int s
=(int)(GlobalSize(h
));
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();
1377 while (*seek
&& *seek
!= '\r' && *seek
!= '\n') seek
++;
1379 if (*seek
) *seek
++=0;
1382 if (hadclr
== '\r' && *seek
== '\n') seek
++;
1384 if (hadclr
&& !*seek
)
1394 int WDL_CursesEditor::onChar(int c
)
1397 if (m_ui_state
== UI_STATE_MESSAGE
)
1399 m_ui_state
=UI_STATE_NORMAL
;
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
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)? ");
1423 // context no longer valid!
1429 if (m_ui_state
== UI_STATE_SAVE_ON_CLOSE
)
1431 if (c
>=0 && (isalnum(c
) || isprint(c
) || c
==27))
1435 m_ui_state
=UI_STATE_NORMAL
;
1437 draw_message("Cancelled close of file.");
1441 if (toupper(c
) == 'N' || toupper(c
) == 'Y')
1443 if (toupper(c
) == 'Y')
1447 m_ui_state
=UI_STATE_NORMAL
;
1449 draw_message("Error writing file, changes not saved!");
1457 // this no longer valid, return 1 to avoid future calls in onChar()
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)
1472 draw_message("Cancelled create new file.");
1477 AddTab(m_newfn
.Get());
1482 if ((c
==27 || c
==29 || (c
>= KEY_F1
&& c
<=KEY_F10
)) && CTRL_KEY_DOWN
)
1487 else if (c
==29) idx
=1;
1495 if (m_ui_state
== UI_STATE_SEARCH
|| m_ui_state
== UI_STATE_SEARCH2
)
1499 case '\r': case '\n':
1500 m_ui_state
=UI_STATE_NORMAL
;
1504 m_ui_state
=UI_STATE_NORMAL
;
1507 draw_message("Find cancelled.");
1510 if (s_search_string
[0])
1512 char *p
= s_search_string
;
1515 int sz
=wdl_utf8_parsechar(p
,NULL
);
1524 m_ui_state
=UI_STATE_SEARCH2
;
1527 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
) break;
1531 WDL_PtrList
<const char> lines
;
1533 getLinesFromClipboard(buf
,lines
);
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
));
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);
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
);
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
)
1574 m_select_x2
=m_select_x1
=m_curs_x
; m_select_y2
=m_select_y1
=m_curs_y
;
1578 else if (m_selecting
) { m_selecting
=0; draw(); }
1584 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
1586 if (m_pane_div
<= 0.0 || m_pane_div
>= 1.0)
1590 if (m_pane_div
> 0.0 && m_pane_div
< 1.0)
1592 m_curpane
=!m_curpane
;
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;
1604 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
1606 if (m_pane_div
<= 0.0 || m_pane_div
>= 1.0)
1609 m_paneoffs_y
[1]=m_paneoffs_y
[0];
1614 if (m_curpane
) m_paneoffs_y
[0]=m_paneoffs_y
[1];
1618 draw_status_state();
1620 int paney
[2], paneh
[2];
1621 GetPaneDims(paney
, paneh
);
1628 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
1630 if (m_undoStack_pos
> 0)
1633 loadUndoState(m_undoStack
.Get(m_undoStack_pos
),1);
1637 snprintf(buf
,sizeof(buf
),"Undid action - %d items in undo buffer",m_undoStack_pos
);
1642 draw_message("Can't Undo");
1648 if ((c
== 'Z'-'A'+1 || !SHIFT_KEY_DOWN
) && !ALT_KEY_DOWN
)
1650 if (m_undoStack_pos
< m_undoStack
.GetSize()-1)
1653 loadUndoState(m_undoStack
.Get(m_undoStack_pos
),0);
1657 snprintf(buf
,sizeof(buf
),"Redid action - %d items in redo buffer",m_undoStack
.GetSize()-m_undoStack_pos
-1);
1662 draw_message("Can't Redo");
1667 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
1669 s_overwrite
=!s_overwrite
;
1675 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
1677 // generate a m_clipboard using win32 clipboard data
1678 WDL_PtrList
<const char> lines
;
1680 getLinesFromClipboard(buf
,lines
);
1682 if (lines
.GetSize())
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;
1690 WDL_FastString poststr
;
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
);
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
1720 skip_source_indent
=1024;
1721 for (i
= 0; skip_source_indent
> 0 && i
< lines
.GetSize(); i
++)
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
));
1737 m_text
.Insert(m_curs_y
,(str
=new WDL_FastString(skip_indent(tstr
,skip_source_indent
))));
1740 if (lines
.GetSize() > 1)
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
);
1759 m_text
.Insert(m_curs_y
,newIndentedFastString(skip_indent(tstr
,skip_source_indent
),indent_to_pos
));
1765 draw_message("Pasted");
1771 draw_message("Clipboard empty");
1777 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
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())
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
);
1803 else // append next line to us
1805 if (m_curs_y
< m_text
.GetSize()-1)
1809 WDL_FastString
*nl
=m_text
.Get(m_curs_y
+1);
1812 s
->Append(nl
->Get());
1814 m_text
.Delete(m_curs_y
+1,true);
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
;
1831 getselectregion(minx
,miny
,maxx
,maxy
);
1832 const char *status
="";
1833 char statusbuf
[512];
1835 if (minx
!= maxx
|| miny
!= maxy
)
1838 s_fake_clipboard
.Set("");
1841 if (c
!= 'C'-'A'+1) preSaveUndoState();
1843 for (x
= miny
; x
<= maxy
; x
++)
1845 WDL_FastString
*s
=m_text
.Get(x
);
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
;
1861 const char *sp
= countLeadingTabs(p
,&nt
,m_write_leading_tabs
);
1864 s_fake_clipboard
.DeleteSub(oldlen
,(int)(sp
-p
));
1870 s_fake_clipboard
.Insert("\t\t\t\t\t\t\t\t",oldlen
,c
);
1877 if (sx
== 0 && ex
== s
->GetLength()) // remove entire line
1879 m_text
.Delete(x
,true);
1880 if (x
==miny
) miny
--;
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);
1896 if (m_curs_y
< 0) m_curs_y
=0;
1899 snprintf(statusbuf
,sizeof(statusbuf
),"Cut %d bytes",bytescopied
);
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
);
1913 WDL_MBtoWideStr(t
,s_fake_clipboard
.Get(),l
);
1916 OpenClipboard(CURSES_INSTANCE
->m_hwnd
);
1918 SetClipboardData(CF_UNICODETEXT
,h
);
1920 int l
=s_fake_clipboard
.GetLength()+1;
1921 HANDLE h
=GlobalAlloc(GMEM_MOVEABLE
,l
);
1922 void *t
=GlobalLock(h
);
1925 memcpy(t
,s_fake_clipboard
.Get(),l
);
1928 OpenClipboard(CURSES_INSTANCE
->m_hwnd
);
1930 SetClipboardData(CF_TEXT
,h
);
1938 else status
="No selection";
1942 draw_message(status
);
1946 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
1951 m_select_y2
=m_text
.GetSize()-1;
1953 if (m_text
.Get(m_select_y2
))
1954 m_select_x2
=m_text
.Get(m_select_y2
)->GetLength();
1960 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
&& m_selecting
)
1970 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
&& s_search_string
[0])
1977 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
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
);
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
);
2003 m_ui_state
=UI_STATE_SEARCH
; // find, initial
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
];
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(); }
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;
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(); }
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;
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
; }
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
; }
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; }
2101 WDL_FastString
*s
= m_text
.Get(m_curs_y
);
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;
2114 m_curs_x
= WDL_utf8_bytepos_to_charpos(s
->Get(),bytepos
);
2121 if (m_selecting
) { setCursor(); m_select_x2
=m_curs_x
; m_select_y2
=m_curs_y
; draw(); }
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
);
2136 m_curs_x
= WDL_utf8_get_charlen(s
->Get());
2142 if(m_curs_x
>0 && doMove
)
2146 WDL_FastString
*s
= m_text
.Get(m_curs_y
);
2148 int bytepos
= WDL_utf8_charpos_to_bytepos(s
->Get(),m_curs_x
);
2149 if (bytepos
> s
->GetLength()) bytepos
= s
->GetLength();
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;
2160 m_curs_x
= WDL_utf8_bytepos_to_charpos(s
->Get(),bytepos
+1);
2167 if (m_selecting
) { setCursor(); m_select_x2
=m_curs_x
; m_select_y2
=m_curs_y
; draw(); }
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(); }
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(); }
2187 case KEY_BACKSPACE
: // backspace, baby
2196 else if (m_curs_x
> 0)
2198 WDL_FastString
*tl
=m_text
.Get(m_curs_y
);
2204 if (m_indent_size
> 0 && !(m_curs_x
% m_indent_size
))
2206 const char *p
= tl
->Get();
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
);
2222 if (!hadCom
) hadCom
= LineCanAffectOtherLines(tl
->Get(),xbyte
,0);
2223 draw(hadCom
?-1:m_curs_y
);
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
);
2234 if (fl
) m_curs_x
=WDL_utf8_get_charlen(fl
->Get());
2242 m_curs_x
=WDL_utf8_get_charlen(fl
->Get());
2243 fl
->Append(tl
->Get());
2245 m_text
.Delete(m_curs_y
--,true);
2253 if (!SHIFT_KEY_DOWN
&& !ALT_KEY_DOWN
)
2259 case 13: //KEY_ENTER:
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
);
2271 WDL_FastString
*s
= m_text
.Get(m_curs_y
);
2273 const char *pb
=NULL
;
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
;
2292 WDL_FastString
*s
= m_text
.Get(m_curs_y
);
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();
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
);
2307 m_curs_x
=WDL_utf8_bytepos_to_charpos(nl
->Get(),plen
);
2321 bool isRev
= !!(GetAsyncKeyState(VK_SHIFT
)&0x8000);
2322 indentSelect(isRev
?-m_indent_size
:m_indent_size
);
2323 // indent selection:
2331 if(VALIDATE_TEXT_CHAR(c
))
2335 if (m_selecting
) { removeSelect(); draw(); setCursor(); }
2336 if (!m_text
.Get(m_curs_y
)) m_text
.Insert(m_curs_y
,new WDL_FastString
);
2339 if ((ss
=m_text
.Get(m_curs_y
)))
2342 int slen
=1,slen_bytes
=1;
2345 slen
= wdl_min(m_indent_size
,64);
2348 const int partial
= m_curs_x
% slen
;
2349 if (partial
) slen
-= partial
;
2352 for(x
=0;x
<slen
;x
++) str
[x
]=' ';
2356 else if (c
== 'n' && !SHIFT_KEY_DOWN
&& ALT_KEY_DOWN
)
2363 slen_bytes
= WDL_MakeUTFChar(str
,c
,32);
2368 const int xbyte
= WDL_utf8_charpos_to_bytepos(ss
->Get(),m_curs_x
);
2370 bool hadCom
= LineCanAffectOtherLines(ss
->Get(),xbyte
,0);
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
);
2383 draw(hadCom
? -1 : m_curs_y
);
2393 void WDL_CursesEditor::preSaveUndoState()
2395 editUndoRec
*rec
= m_undoStack
.Get(m_undoStack_pos
);
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);
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;
2435 const char *cs
=s
?s
->Get():"";
2437 for(w
=0;w
<5 && !ns
;w
++)
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
))
2454 if (!ns
) ns
= new refcntString(cs
,s_len
);
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
)
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
--;
2485 m_undoStack_pos
= m_undoStack
.GetSize()-1;
2488 m_undoStack
.Add(rec
);
2491 void WDL_CursesEditor::loadUndoState(editUndoRec
*rec
, int idx
)
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];
2513 void WDL_CursesEditor::RunEditor()
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()
2530 if (m_top_margin
> 1)
2535 const int cnt
= GetTabCount();
2537 // this is duplicated in onMouseMessage
2538 if (cnt
>0) tsz
=COLS
/cnt
;
2539 if (tsz
>128)tsz
=128;
2542 for (x
= 0; x
< cnt
&& xpos
< COLS
; x
++)
2544 WDL_CursesEditor
*ed
= GetTab(x
);
2548 memset(buf
,' ',tsz
);
2549 const char *p
= WDL_get_filepart(ed
->GetFileName());
2550 const int lp
=strlen(p
);
2556 const char *s
= "<" CONTROL_KEY_NAME
"+";
2557 memcpy(buf
,s
,skip
= (int)strlen(s
));
2560 buf
[skip
++] = '1'+x
;
2564 memcpy(buf
+skip
,p
,wdl_min(tsz
-1-skip
,lp
));
2567 if (l
> COLS
-xpos
) l
= COLS
-xpos
;
2570 attrset(SYNTAX_HIGHLIGHT2
|A_BOLD
);
2580 if (xpos
< COLS
) clrtoeol();
2582 attrset(COLOR_TOPLINE
|A_BOLD
);
2583 bkgdset(COLOR_TOPLINE
);
2584 const char *p
=GetFileName();
2588 const int pl
= (int) strlen(p
);
2589 if (pl
> COLS
-1 && COLS
> 4)
2592 p
+=pl
- (COLS
-1) + 4;
2599 void WDL_CursesEditor::OpenFileInTab(const char *fnp
)
2601 if (!fnp
[0]) return;
2603 FILE *fp
= fopen(fnp
,"rb");
2606 WDL_FastString
s(fnp
);
2611 int allowed
= COLS
-25;
2612 if (s
.GetLength()>allowed
)
2614 s
.DeleteSub(0,s
.GetLength() - allowed
+ 3);
2617 s
.Insert("Create new file '",0);
2618 s
.Append("' (Y/n)? ");
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());
2635 for (x
=0;x
<GetTabCount();x
++)
2637 WDL_CursesEditor
*e
= GetTab(x
);
2638 if (e
&& !stricmp(e
->GetFileName(),fnp
))