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