1 /* Invisible Vector Library
2 * simple FlexBox-based TUI engine
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module iv
.egtui
.editor
.ttyeditor
/*is aliced*/;
18 import std
.datetime
: SysTime
;
29 import iv
.egtui
.dialogs
;
30 import iv
.egeditor
.editor
;
31 import iv
.egeditor
.highlighters
;
32 import iv
.egtui
.editor
.highlighters
;
33 import iv
.egtui
.editor
.dialogs
;
36 // ////////////////////////////////////////////////////////////////////////// //
37 private string
normalizedAbsolutePath (string path
) {
39 return buildNormalizedPath(absolutePath(path
));
43 // ////////////////////////////////////////////////////////////////////////// //
44 class TtyEditor
: EditorEngine
{
45 enum TEDSingleOnly
; // only for single-line mode
46 enum TEDMultiOnly
; // only for multiline mode
47 enum TEDEditOnly
; // only for non-readonly mode
48 enum TEDROOnly
; // only for readonly mode
49 enum TEDRepeated
; // allow "repeation count"
50 enum TEDRepeatChar
; // this combo meant "repeat mode"
52 static struct TEDKey
{ string key
; string help
; bool hidden
; } // UDA
54 static string
TEDImplX(string key
, string help
, string code
, usize ln
) () {
55 static assert(key
.length
> 0, "wtf?!");
56 static assert(code
.length
> 0, "wtf?!");
57 string res
= "@TEDKey("~key
.stringof
~", "~help
.stringof
~") void _ted_";
59 while (pos
< key
.length
) {
61 if (key
.length
-pos
> 0 && key
[pos
] == '-') {
62 if (ch
== 'C' || ch
== 'c') { ++pos
; res
~= "Ctrl"; continue; }
63 if (ch
== 'M' || ch
== 'm') { ++pos
; res
~= "Alt"; continue; }
64 if (ch
== 'S' || ch
== 's') { ++pos
; res
~= "Shift"; continue; }
66 if (ch
== '^') { res
~= "Ctrl"; continue; }
67 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
68 if ((ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') || ch
== '_') res
~= ch
; else res
~= '_';
71 res
~= " () {"~code
~"}";
75 mixin template TEDImpl(string key
, string help
, string code
, usize ln
=__LINE__
) {
76 mixin(TEDImplX
!(key
, help
, code
, ln
));
79 mixin template TEDImpl(string key
, string code
, usize ln
=__LINE__
) {
80 mixin(TEDImplX
!(key
, "", code
, ln
));
84 TtyEvent
[32] comboBuf
;
85 int comboCount
; // number of items in `comboBuf`
92 CSpace
, // C-Space just pressed
93 // math ops; they pushing value to stack
100 int repeatCounter
= -1; // <0: not in counter typing
101 int repeatIteration
; // 0...
102 RepMode repeatMode
; // <0: C-Space just pressed; >0: just started; 0: normal
107 TtyEditor mPromptInput
; // input line for a prompt; lazy creation
109 char[128] mPromptPrompt
; // lol
112 final void promptDeactivate () {
114 mPromptActive
= false;
115 fullDirty(); // just in case
119 final void promptNoKillText () {
120 if (mPromptInput
!is null) mPromptInput
.killTextOnChar
= false;
123 final void promptActivate (const(char)[] prompt
=null, const(char)[] text
=null) {
124 if (mPromptInput
is null) {
125 mPromptInput
= new TtyEditor(0, 0, 10, 1, true); // single-lined
128 bool addDotDotDot
= false;
132 if (prompt
.length
> winw
-8) {
134 prompt
= prompt
[$-(winw
-8)..$];
137 if (prompt
.length
> mPromptPrompt
.length
) addDotDotDot
= true;
141 mPromptPrompt
[0..3] = '.';
142 if (prompt
.length
> mPromptPrompt
.length
-3) prompt
= prompt
[$-(mPromptPrompt
.length
-3)..$];
143 mPromptPrompt
[3..3+prompt
.length
] = prompt
[];
144 mPromptLen
= cast(int)prompt
.length
+3;
146 mPromptPrompt
[0..prompt
.length
] = prompt
[];
147 mPromptLen
= cast(int)prompt
.length
;
150 mPromptInput
.moveResize(winx
+mPromptLen
+2, winy
-1, winw
-mPromptLen
-2, 1);
151 mPromptInput
.clear();
153 mPromptInput
.doPutText(text
);
154 mPromptInput
.clearUndo();
157 mPromptInput
.killTextOnChar
= true;
158 mPromptInput
.clrBlock
= XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0xff), TtyRgb2Color
!(0x00, 0x5f, 0xff));
159 mPromptInput
.clrText
= XtColorFB
!(TtyRgb2Color
!(0x00, 0x00, 0x00), TtyRgb2Color
!(0xff, 0x7f, 0x00));
160 mPromptInput
.clrTextUnchanged
= XtColorFB
!(TtyRgb2Color
!(0x00, 0x00, 0x00), TtyRgb2Color
!(0xcf, 0x4f, 0x00));
162 mPromptInput
.utfuck
= utfuck
;
163 mPromptInput
.codepage
= codepage
;
164 mPromptActive
= true;
165 fullDirty(); // just in case
168 final bool promptProcessKey (TtyEvent key
, scope void delegate (EditorEngine ed
) onChange
=null) {
169 if (!mPromptActive
) return false;
170 auto lastCC
= mPromptInput
.bufferCC
;
171 auto res
= mPromptInput
.processKey(key
);
172 if (lastCC
!= mPromptInput
.bufferCC
&& onChange
!is null) onChange(mPromptInput
);
177 int incSearchDir
; // -1; 0; 1
178 char[] incSearchBuf
; // will be actively reused, don't expose
179 int incSearchHitPos
= -1;
180 int incSearchHitLen
= -1;
183 // autocompletion buffers
184 const(char)[][256] aclist
; // autocompletion tokens
185 uint acused
= 0; // number of found tokens
186 char[] acbuffer
; // will be actively reused, don't expose
190 string tempBlockFileName
;
192 // save this, so we can check on saving
194 long fileDiskSize
= -1; // <0: modtime is invalid
195 bool dontSetCursor
; // true: don't gotoxy to cursor position
196 // colors; valid for singleline control
197 uint clrBlock
, clrText
, clrTextUnchanged
;
199 SearchReplaceOptions srrOptions
;
200 bool hideStatus
= false;
201 bool hideSBar
= false; // hide scrollbar
202 FuiHistoryManager hisman
; // history manager for dialogs
205 this (int x0
, int y0
, int w
, int h
, bool asinglesine
=false) {
206 // prompt should be created only for multiline editors
207 super(x0
, y0
, w
, h
, null, asinglesine
);
208 srrOptions
.type
= SearchReplaceOptions
.Type
.Regex
;
209 srrOptions
.casesens
= true;
210 srrOptions
.nocomments
= true;
213 // call this after setting `fullFileName`
214 void setupHighlighter () {
215 auto ep
= fullFileName
.length
;
216 while (ep
> 0 && fullFileName
.ptr
[ep
-1] != '/' && fullFileName
.ptr
[ep
-1] != '.') --ep
;
217 if (ep
< 1 || fullFileName
.ptr
[ep
-1] != '.') {
218 attachHiglighter(getHiglighterObjectFor("", fullFileName
));
220 attachHiglighter(getHiglighterObjectFor(fullFileName
[ep
-1..$], fullFileName
));
224 final void toggleHighlighting () {
225 if (hl
is null) setupHighlighter(); else detachHighlighter();
228 final @property bool hasHighlighter () const pure nothrow @safe @nogc { pragma(inline
, true); return (hl
!is null); }
230 final void getDiskFileInfo () {
231 import std
.file
: getSize
, timeLastModified
;
232 if (fullFileName
.length
) {
233 fileDiskSize
= getSize(fullFileName
);
234 fileModTime
= timeLastModified(fullFileName
);
240 // return `true` if file was changed
241 final bool wasDiskFileChanged () {
242 import std
.file
: exists
, getSize
, timeLastModified
;
243 if (fullFileName
.length
&& fileDiskSize
>= 0) {
244 if (!fullFileName
.exists
) return false;
245 auto sz
= getSize(fullFileName
);
246 if (sz
!= fileDiskSize
) return true;
247 SysTime modtime
= timeLastModified(fullFileName
);
248 if (modtime
!= fileModTime
) return true;
253 override void loadFile (const(char)[] fname
) {
255 fullFileName
= normalizedAbsolutePath(fname
.idup
);
256 super.loadFile(VFile(fullFileName
));
260 // return `false` iff overwrite dialog was aborted
261 final bool saveFileChecked () {
262 if (fullFileName
.length
== 0) return true;
263 if (wasDiskFileChanged
) {
264 auto res
= dialogFileModified(fullFileName
, false, "Disk file was changed. Overwrite it?");
265 if (res
< 0) return false;
267 super.saveFile(fullFileName
);
271 super.saveFile(fullFileName
);
277 final void checkDiskAndReloadPrompt () {
278 if (fullFileName
.length
== 0) return;
279 if (wasDiskFileChanged
) {
280 auto fn
= fullFileName
;
281 if (fn
.length
> ttyw
-3) fn
= "* ..."~fn
[$-(ttyw
-4)..$];
282 auto res
= dialogFileModified(fullFileName
, false, "Disk file was changed. Reload it?");
283 if (res
!= 1) return;
284 int rx
= cx
, ry
= cy
;
286 super.loadFile(VFile(fullFileName
));
291 makeCurLineVisibleCentered();
295 override void saveFile (const(char)[] fname
=null) {
297 auto nfn
= normalizedAbsolutePath(fname
.idup
);
298 if (fname
== fullFileName
) { saveFileChecked(); return; }
299 super.saveFile(VFile(nfn
, "w"));
307 protected override void willBeDeleted (int pos
, int len
, int eolcount
) {
308 if (len
> 0) resetIncSearchPos();
309 super.willBeDeleted(pos
, len
, eolcount
);
312 protected override void willBeInserted (int pos
, int len
, int eolcount
) {
313 if (len
> 0) resetIncSearchPos();
314 super.willBeInserted(pos
, len
, eolcount
);
317 final void resetIncSearchPos () nothrow {
318 if (incSearchHitPos
>= 0) {
319 markLinesDirty(lc
.pos2line(incSearchHitPos
), 1);
320 incSearchHitPos
= -1;
321 incSearchHitLen
= -1;
325 final void doStartIncSearch (int sdir
=0) {
327 incInputActive
= true;
328 incSearchDir
= (sdir ? sdir
: incSearchDir ? incSearchDir
: 1);
329 promptActivate("incsearch", incSearchBuf
);
331 incSearchBuf.length = 0;
332 incSearchBuf.assumeSafeAppend;
336 final void doNextIncSearch (bool domove
=true) {
337 if (incSearchDir
== 0 || incSearchBuf
.length
== 0) {
342 //TODO: use `memr?chr()` here?
344 if (incSearchBuf
.ptr
[0] != '/' || incSearchBuf
.ptr
[0] == '\\') {
347 if (incSearchBuf
.ptr
[0] == '\\') {
348 if (incSearchBuf
.length
== 1) return;
351 int pos
= curpos
+(domove ? incSearchDir
: 0);
353 if (incSearchDir
< 0) {
354 mt
= findTextPlainBack(incSearchBuf
[isbofs
..$], 0, pos
, /*words:*/false, /*caseSens:*/true);
356 mt
= findTextPlain(incSearchBuf
[isbofs
..$], pos
, textsize
, /*words:*/false, /*caseSens:*/true);
359 incSearchHitPos
= mt
.s
;
360 incSearchHitLen
= mt
.e
-mt
.s
;
362 } else if (incSearchBuf
.length
> 2 && incSearchBuf
[$-1] == '/') {
364 import std
.utf
: byChar
;
365 auto re
= RegExp
.create(incSearchBuf
[1..$-1].byChar
, SRFlags
.Multiline
);
366 if (!re
.valid
) { ttyBeep
; return; }
367 Pike
.Capture
[2] caps
;
369 if (incSearchDir
> 0) {
370 found
= findTextRegExp(re
, curpos
+(domove ?
1 : 0), textsize
, caps
);
372 found
= findTextRegExpBack(re
, 0, curpos
+(domove ?
-1 : 0), caps
);
375 // something was found
376 incSearchHitPos
= caps
[0].s
;
377 incSearchHitLen
= caps
[0].e
-caps
[0].s
;
380 if (incSearchHitPos
>= 0 && incSearchHitLen
> 0) {
381 static if (EgEditorMovementUndo
) pushUndoCurPos();
382 lc
.pos2xy(incSearchHitPos
, cx
, cy
);
383 makeCurLineVisibleCentered();
384 markRangeDirty(incSearchHitPos
, incSearchHitLen
);
388 final void drawScrollBar () {
389 if (winx
== 0) return; // it won't be visible anyway
390 if (singleline || hideSBar
) return;
391 auto win
= XtWindow(winx
-1, winy
-(hideStatus ?
0 : 1), 1, winh
+(hideStatus ?
0 : 1));
392 if (win
.height
< 1) return; // it won't be visible anyway
393 win
.fg
= TtyRgb2Color
!(0x00, 0x00, 0x00);
394 win
.bg
= (termType
!= TermType
.linux ? TtyRgb2Color
!(0x00, 0x5f, 0xaf) : TtyRgb2Color
!(0x00, 0x5f, 0xcf));
396 int botline
= topline
+winh
-1;
397 if (botline
>= linecount
-1 || linecount
== 0) {
400 filled
= (win
.height
-1)*botline
/linecount
;
401 if (filled
== win
.height
-1 && mTopLine
+winh
< linecount
) --filled
;
403 foreach (immutable y
; 0..win
.height
) win
.writeCharsAt
!true(0, y
, 1, (y
<= filled ?
' ' : 'a'));
406 public override void drawCursor () {
407 if (dontSetCursor
) return;
408 // draw prompt if it is active
409 if (mPromptActive
) { mPromptInput
.drawCursor(); return; }
411 lc
.pos2xyVT(curpos
, rx
, ry
);
412 XtWindow(winx
, winy
, winw
, winh
).gotoXY(rx
-mXOfs
, ry
-topline
);
415 public override void drawStatus () {
416 if (singleline || hideStatus
) return;
417 auto win
= XtWindow(winx
, winy
-1, winw
, 1);
418 win
.fg
= TtyRgb2Color
!(0x00, 0x00, 0x00);
419 win
.bg
= TtyRgb2Color
!(0xb2, 0xb2, 0xb2);
420 win
.writeCharsAt(0, 0, win
.width
, ' ');
421 import core
.stdc
.stdio
: snprintf
;
423 char[512] buf
= void;
427 lc
.pos2xyVT(cp
, sx
, ry
);
429 // mod, (pos), topline, linecount
430 auto len
= snprintf(buf
.ptr
, buf
.length
, " %c[%04u:%05u : %5u : %5u] ",
431 (textChanged ?
'*' : ' '), sx
+0, cy
+1, topline
, linecount
);
432 // character code (hex, dec)
434 auto c
= cast(uint)gb
[cp
];
435 len
+= snprintf(buf
.ptr
+len
, buf
.length
-len
, "0x%02x %3u", c
, c
);
437 auto c
= cast(uint)dcharAt(cp
);
438 len
+= snprintf(buf
.ptr
+len
, buf
.length
-len
, "U%04x %5u", c
, c
);
440 // filesize, bufstart, bufend
441 len
+= snprintf(buf
.ptr
+len
, buf
.length
-len
, " [ %u : %d : %d]", gb
.textsize
, bstart
, bend
);
443 if (repeatCounter
>= 0) {
444 len
+= snprintf(buf
.ptr
+len
, buf
.length
-len
, " rep:%d", repeatCounter
);
445 if (repeatSP
> 0) len
+= snprintf(buf
.ptr
+len
, buf
.length
-len
, " (%d)", repeatSP
);
448 if (len
> winw
) len
= winw
;
449 win
.writeStrAt(0, 0, buf
[0..len
]);
453 if (readonly
) win
.writeCharsAt(0, 0, 1, '/'); else win
.writeCharsAt(0, 0, 1, 'U');
454 } else if (readonly
) {
456 win
.writeCharsAt(0, 0, 1, 'R');
460 // highlighting is done, other housekeeping is done, only draw
461 // lidx is always valid
462 // must repaint the whole line
463 // use `winXXX` vars to know window dimensions
464 //FIXME: clean this up!
465 public override void drawLine (int lidx
, int yofs
, int xskip
) {
466 immutable vt
= visualtabs
;
467 immutable tabsz
= lc
.tabsize
;
468 auto win
= XtWindow(winx
, winy
, winw
, winh
);
469 auto pos
= lc
.linestart(lidx
);
472 auto ts
= gb
.textsize
;
473 bool inBlock
= (bstart
< bend
&& pos
>= bstart
&& pos
< bend
);
474 bool bookmarked
= isLineBookmarked(lidx
);
475 bool hasHL
= (hl
!is null); // do we have highlighter (just a cache)
477 win
.color
= (clrText ? clrText
: TextColor
);
478 if (killTextOnChar
) win
.color
= (clrTextUnchanged ? clrTextUnchanged
: TextKillColor
);
479 if (inBlock
) win
.color
= (clrBlock ? clrBlock
: BlockColor
);
481 win
.color
= (hasHL ? TextColor
: TextColorNoHi
);
482 if (bookmarked
) win
.color
= BookmarkColor
; else if (inBlock
) win
.color
= BlockColor
;
484 // if we have no highlighter, check for trailing spaces explicitly
485 // look for trailing spaces even if we have a highlighter
486 int trspos
= lc
.lineend(lidx
); // this is where trailing spaces starts (will)
487 immutable lend
= trspos
;
488 if (!singleline
) while (trspos
> pos
&& gb
[trspos
-1] <= ' ') --trspos
;
489 bool utfucked
= utfuck
;
490 int bs
= bstart
, be
= bend
;
491 auto sltextClr
= (singleline ?
493 (clrTextUnchanged ? clrTextUnchanged
: TextKillColor
) :
494 (clrText ? clrText
: TextColor
)) :
495 (hasHL ? TextColor
: TextColorNoHi
));
496 auto blkClr
= (singleline ?
(clrBlock ? clrBlock
: BlockColor
) : BlockColor
);
497 while (pos
<= lend
) {
498 inBlock
= (bs
< be
&& pos
>= bs
&& pos
< be
);
501 if (!killTextOnChar
&& !singleline
) win
.color
= (hasHL ?
hiColor(gb
.hi(pos
-1)) : TextColorNoHi
); else win
.color
= sltextClr
;
506 if (pos
-1 >= trspos
) {
507 if (ch
!= '\t') ch
= '.';
508 if (!killTextOnChar
&& !singleline
) win
.color
= (ch
!= '\t' ? TrailSpaceColor
: VisualTabColor
); else win
.color
= sltextClr
;
509 } else if (ch
< ' ' || ch
== 127) {
510 if (!killTextOnChar
&& !singleline
) win
.color
= (ch
!= '\t' ? BadColor
: VisualTabColor
); else win
.color
= sltextClr
;
512 auto hs
= gb
.hi(pos
-1);
513 if (!killTextOnChar
&& !singleline
) win
.color
= hiColor(hs
); else win
.color
= sltextClr
;
517 if (pos
-1 >= trspos
) {
518 if (ch
!= '\t') ch
= '.';
519 if (!killTextOnChar
&& !singleline
) win
.color
= (ch
!= '\t' ? TrailSpaceColor
: VisualTabColor
); else win
.color
= sltextClr
;
520 } else if (ch
< ' ' || ch
== 127) {
521 if (!killTextOnChar
&& !singleline
) win
.color
= (ch
!= '\t' ? BadColor
: VisualTabColor
); else win
.color
= sltextClr
;
523 win
.color
= sltextClr
;
526 if (!killTextOnChar
&& !singleline
) {
527 if (bookmarked
) win
.color
= BookmarkColor
; else if (inBlock
) win
.color
= blkClr
;
529 if (inBlock
) win
.color
= blkClr
; else win
.color
= sltextClr
;
532 if (vt
&& ch
== '\t') {
533 int ex
= ((x
+tabsz
)/tabsz
)*tabsz
;
537 win
.writeCharsAt(x
, y
, 1, '<');
538 win
.writeCharsAt(x
+1, y
, sz
-2, '-');
539 win
.writeCharsAt(ex
-1, y
, 1, '>');
541 win
.writeCharsAt(x
, y
, 1, '\t');
544 x
= ex
-1; // compensate the following increment
545 } else if (!utfucked
) {
546 if (x
>= 0) win
.writeCharsAt(x
, y
, 1, recodeCharFrom(ch
));
551 dchar dch
= dcharAt(pos
);
552 if (dch
> dchar.max || dch
== 0xFFFD) {
554 scope(exit
) win
.color
= oc
;
555 if (!inBlock
) win
.color
= UtfuckedColor
;
556 win
.writeCharsAt
!true(x
, y
, 1, '\x7e'); // dot
558 win
.writeCharsAt(x
, y
, 1, uni2koi(dch
));
561 pos
+= gb
.utfuckLenAt(pos
);
563 if (++x
>= winw
) return;
566 if (x
>= winw
) return;
569 win
.color
= (hasHL || singleline ? TextColor
: TextColorNoHi
);
570 if (!killTextOnChar
&& !singleline
) {
571 if (bookmarked
) win
.color
= BookmarkColor
; else win
.color
= sltextClr
;
573 win
.color
= sltextClr
;
575 if (bs
< be
&& pos
>= bs
&& pos
< be
) win
.color
= blkClr
;
577 win
.writeCharsAt(x
, y
, winw
-x
, ' ');
581 // use `winXXX` vars to know window dimensions
582 public override void drawEmptyLine (int yofs
) {
583 auto win
= XtWindow(winx
, winy
, winw
, winh
);
585 win
.color
= (clrText ? clrText
: TextColor
);
586 if (killTextOnChar
) win
.color
= (clrTextUnchanged ? clrTextUnchanged
: TextKillColor
);
588 win
.color
= TextColor
;
590 win
.writeCharsAt(0, yofs
, winw
, ' ');
593 public override void drawPageBegin () {
596 // check if line has only spaces (or comments, use highlighter) to begin/end (determined by dir)
597 final bool lineHasOnlySpaces (int pos
, int dir
) {
598 if (dir
== 0) return false;
599 dir
= (dir
< 0 ?
-1 : 1);
601 if (ts
== 0) return true; // wow, rare case
602 if (pos
< 0) { if (dir
< 0) return true; pos
= 0; }
603 if (pos
>= ts
) { if (dir
> 0) return true; pos
= ts
-1; }
604 while (pos
>= 0 && pos
< ts
) {
606 if (ch
== '\n') break;
608 // nonspace, check highlighting, if any
610 auto lidx
= lc
.pos2line(pos
);
611 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
612 if (!hiIsComment(gb
.hi(pos
))) return false;
613 // ok, it is comment, it's the same as whitespace
623 // always starts at BOL
624 final int lineFindFirstNonSpace (int pos
) {
626 if (ts
== 0) return 0; // wow, rare case
627 pos
= lc
.linestart(lc
.pos2line(pos
));
630 if (ch
== '\n') break;
632 // nonspace, check highlighting, if any
634 auto lidx
= lc
.pos2line(pos
);
635 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
636 if (!hiIsComment(gb
.hi(pos
))) return pos
;
637 // ok, it is comment, it's the same as whitespace
647 final int drawHiBracket(bool dodraw
=true) (int pos
, int lidx
, char bch
, char ech
, int dir
, bool drawline
=false) {
648 enum LineScanPages
= 8;
650 auto ts
= gb
.textsize
;
652 int toplimit
= (drawline ? mTopLine
-winh
*(LineScanPages
-1) : mTopLine
);
653 int botlimit
= (drawline ? mTopLine
+winh
*LineScanPages
: mTopLine
+winh
);
655 while (pos
>= 0 && pos
< ts
) {
659 if (lidx
< toplimit || lidx
>= botlimit
) return -1;
663 if (isAnyTextChar(pos
, (dir
> 0))) {
666 } else if (ch
== ech
) {
669 lc
.pos2xyVT(pos
, rx
, ry
);
670 if (rx
>= mXOfs || rx
< mXOfs
+winw
) {
672 if (ry
>= mTopLine
&& ry
< mTopLine
+winh
) {
673 auto win
= XtWindow(winx
, winy
, winw
, winh
);
674 win
.color
= BracketColor
;
675 win
.writeCharsAt(rx
-mXOfs
, ry
-mTopLine
, 1, ech
);
676 markLinesDirty(ry
, 1);
678 // draw line with opening bracket if it is out of screen
679 if (drawline
&& dir
< 0 && ry
< mTopLine
) {
682 while (ls
> 0 && gb
[ls
-1] != '\n') --ls
;
683 // skip leading spaces
684 while (ls
< pos
&& gb
[ls
] <= ' ') ++ls
;
687 while (le
< ts
&& gb
[le
] != '\n') ++le
;
688 // remove trailing spaces
689 while (le
> pos
&& gb
[le
-1] <= ' ') --le
;
691 auto win
= XtWindow(winx
+1, winy
-1, winw
-1, 1);
692 win
.color
= XtColorFB
!(TtyRgb2Color
!(0x40, 0x40, 0x40), TtyRgb2Color
!(0xb2, 0xb2, 0xb2)); // 0,7
693 win
.writeCharsAt(0, 0, winw
, ' ');
695 while (x
< winw
&& ls
< le
) {
696 win
.writeCharsAt(x
, 0, 1, uni2koi(gb
.uniAt(ls
)));
697 ls
+= gb
.utfuckLenAt(ls
);
703 // draw vertical line
707 // has some text after the bracket on the starting line?
708 //if (!lineHasOnlySpaces(stpos+1, 1)) return; // has text, can't draw
709 // find first non-space at the starting line
710 ls
= lineFindFirstNonSpace(stpos
);
711 } else if (dir
< 0) {
713 lc
.pos2xyVT(stpos
, rx
, ry
);
714 ls
= lineFindFirstNonSpace(pos
);
716 lc
.pos2xyVT(ls
, stx
, sty
);
718 markLinesDirtySE(sty
+1, ry
-1);
723 auto win
= XtWindow(winx
, winy
, winw
, winh
);
724 win
.color
= VLineColor
;
725 win
.vline(stx
, sty
+1, ry
-sty
-1);
739 protected final void drawPartHighlight (int pos
, int count
, uint clr
) {
740 if (pos
>= 0 && count
> 0 && pos
< gb
.textsize
) {
742 lc
.pos2xyVT(pos
, rx
, ry
);
743 //auto oldclr = xtGetColor;
744 //scope(exit) win.color = oldclr;
745 if (ry
>= topline
&& ry
< topline
+winh
) {
747 auto win
= XtWindow(winx
, winy
, winw
, winh
);
749 if (count
> gb
.textsize
-pos
) count
= gb
.textsize
-pos
;
754 foreach (immutable _
; 0..count
) {
755 if (rx
>= 0 && rx
< winw
) win
.writeCharsAt(rx
, ry
, 1, recodeCharFrom(gb
[pos
++]));
761 if (rx
>= winw
) break;
763 dchar dch
= dcharAt(pos
);
764 if (dch
> dchar.max || dch
== 0xFFFD) {
765 //auto oc = xtGetColor();
766 //win.color = UtfuckedColor;
767 win
.writeCharsAt
!true(rx
, ry
, 1, '\x7e'); // dot
769 win
.writeCharsAt(rx
, ry
, 1, uni2koi(dch
));
773 pos
+= gb
.utfuckLenAt(pos
);
780 public override void drawPagePost () {
782 if (isAnyTextChar(pos
, false)) {
784 if (ch
== '(') drawHiBracket(pos
, cy
, ch
, ')', 1);
785 else if (ch
== '{') drawHiBracket(pos
, cy
, ch
, '}', 1, true);
786 else if (ch
== '[') drawHiBracket(pos
, cy
, ch
, ']', 1);
787 else if (ch
== ')') drawHiBracket(pos
, cy
, ch
, '(', -1);
788 else if (ch
== '}') drawHiBracket(pos
, cy
, ch
, '{', -1, true);
789 else if (ch
== ']') drawHiBracket(pos
, cy
, ch
, '[', -1);
792 if (incSearchHitPos
>= 0 && incSearchHitLen
> 0 && incSearchHitPos
< gb
.textsize
) {
793 drawPartHighlight(incSearchHitPos
, incSearchHitLen
, IncSearchColor
);
798 public override void drawPageEnd () {
800 auto win
= XtWindow(winx
, winy
-(singleline || hideStatus ?
0 : 1), winw
, 1);
801 win
.color
= mPromptInput
.clrText
;
802 win
.writeCharsAt(0, 0, winw
, ' ');
803 win
.writeStrAt(0, 0, mPromptPrompt
[0..mPromptLen
]);
804 win
.writeCharsAt(mPromptLen
, 0, 1, ':');
805 mPromptInput
.fullDirty(); // force redraw
806 mPromptInput
.drawPage();
811 //TODO: fix cx if current line was changed
812 final void doUntabify (int tabSize
=2) {
813 if (mReadOnly || gb
.textsize
== 0) return;
814 if (tabSize
< 1 || tabSize
> 255) return;
816 auto ts
= gb
.textsize
;
818 while (pos
< ts
&& gb
[pos
] != '\t') {
819 if (gb
[pos
] == '\n') curx
= 0; else ++curx
;
822 if (pos
>= ts
) return;
824 scope(exit
) undoGroupEnd();
825 char[255] spaces
= ' ';
829 assert(gb
[pos
] == '\t');
830 int spc
= tabSize
-(curx
%tabSize
);
831 replaceText
!"none"(pos
, 1, spaces
[0..spc
]);
834 while (pos
< ts
&& gb
[pos
] != '\t') {
835 if (gb
[pos
] == '\n') curx
= 0; else ++curx
;
842 //TODO: fix cx if current line was changed
843 final void doRemoveTailingSpaces () {
844 if (mReadOnly || gb
.textsize
== 0) return;
845 bool wasChanged
= false;
846 scope(exit
) if (wasChanged
) undoGroupEnd();
847 foreach (int lidx
; 0..linecount
) {
848 auto ls
= lc
.linestart(lidx
);
849 auto le
= lc
.lineend(lidx
); // points at '\n', or after text buffer
851 while (le
> ls
&& gb
[le
-1] <= ' ') { --le
; ++count
; }
852 if (count
== 0) continue;
853 if (!wasChanged
) { undoGroupStart(); wasChanged
= true; }
854 deleteText
!"none"(le
, count
);
859 // not string, not comment
860 // if `goingDown` is true, update highlighting
861 final bool isAnyTextChar (int pos
, bool goingDown
) {
862 if (hl
is null) return true;
863 if (pos
< 0 || pos
>= gb
.textsize
) return false;
864 // update highlighting
865 if (goingDown
&& hl
!is null) {
866 auto lidx
= lc
.pos2line(pos
);
867 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
868 // ok, it is comment, it's the same as whitespace
870 switch (gb
.hi(pos
).kwtype
) {
871 case HiCommentOneLine
:
873 case HiCommentDirective
:
877 case HiDQStringSpecial
:
879 case HiSQStringSpecial
:
888 final bool isInComment (int pos
) {
889 if (hl
is null || pos
< 0 || pos
>= gb
.textsize
) return false;
890 return hiIsComment(gb
.hi(pos
));
893 final bool isACGoodWordChar (int pos
) {
894 if (pos
< 0 || pos
>= gb
.textsize
) return false;
895 if (!isWordChar(gb
[pos
])) return false;
897 // don't autocomplete in strings
898 switch (gb
.hi(pos
).kwtype
) {
903 case HiDQStringSpecial
:
905 case HiSQStringSpecial
:
915 const(char)[] delegate (EditorEngine ed
, const(char)[] tk
, int tkpos
) completeToken
;
917 final void doAutoComplete () {
920 if (acbuffer
.length
> 0) {
922 acbuffer
.assumeSafeAppend
;
926 void addAcToken (const(char)[] tk
) {
927 if (tk
.length
== 0) return;
928 foreach (const(char)[] t
; aclist
[0..acused
]) if (t
== tk
) return;
929 if (acused
>= aclist
.length
) return;
931 auto pos
= acbuffer
.length
;
933 aclist
[acused
++] = acbuffer
[pos
..$];
936 import std
.ascii
: isAlphaNum
;
937 // get token to autocomplete
939 if (!isACGoodWordChar(pos
-1)) return;
940 //debug(egauto) { { import iv.vfs; auto fo = VFile("z00_ch.bin", "w"); fo.write(ch); } }
941 bool startedInComment
= isInComment(pos
-1);
943 int tkpos
= cast(int)tk
.length
;
944 while (pos
> 0 && isACGoodWordChar(pos
-1)) {
945 if (tkpos
== 0) return;
946 tk
.ptr
[--tkpos
] = gb
[--pos
];
948 int tklen
= cast(int)tk
.length
-tkpos
;
950 //HACK: try "std.algo"
951 if (gb
[pos
-1] == '.' && gb
[pos
-2] == 'd' && gb
[pos
-3] == 't' && gb
[pos
-4] == 's' && !isACGoodWordChar(pos
-5)) {
952 if (tk
[$-tklen
..$] == "algo") {
954 string ntx
= "rithm";
956 replaceText
!"end"(tkstpos
, 0, ntx
);
960 //HACK: try "arsd.sdpy"
961 if (gb
[pos
-1] == '.' && gb
[pos
-2] == 'd' && gb
[pos
-3] == 's' && gb
[pos
-4] == 'r' && gb
[pos
-5] == 'a' && !isACGoodWordChar(pos
-6)) {
962 if (tk
[$-tklen
..$] == "sdpy") {
963 string ntx
= "simpledisplay";
965 replaceText
!"end"(tkstpos
, tklen
, ntx
);
969 //debug(egauto) { { import iv.vfs; auto fo = VFile("z00_tk.bin", "w"); fo.write(tk[$-tklen..$]); } }
971 char[128] xtk
= void;
973 while (pos
> 0 && !isACGoodWordChar(pos
-1)) --pos
;
975 int xtp
= cast(int)xtk
.length
;
976 while (pos
> 0 && isACGoodWordChar(pos
-1)) {
978 xtk
.ptr
[--xtp
] = gb
[--pos
];
984 if (xtp
>= 0 && isInComment(pos
) == startedInComment
) {
985 int xlen
= cast(int)xtk
.length
-xtp
;
987 import core
.stdc
.string
: memcmp
;
988 if (memcmp(xtk
.ptr
+xtk
.length
-xlen
, tk
.ptr
+tk
.length
-tklen
, tklen
) == 0) {
989 const(char)[] tt
= xtk
[$-xlen
..$];
991 if (acused
>= 128) break;
996 debug(egauto
) { { import iv
.vfs
; auto fo
= VFile("z00_list.bin", "w"); fo
.writeln(list
[]); } }
998 const(char)[] getDefToken () /*nothrow @trusted @nogc*/ {
999 if (completeToken
!is null) return completeToken(this, tk
[$-tklen
..$], tkpos
);
1001 switch (tk[$-tklen..$]) {
1002 case "ab": return "abstract";
1003 case "br": return "break";
1004 case "ch": return "char";
1005 case "co": return "continue";
1006 case "de": return "delegate";
1007 case "fa": return "false";
1008 case "fi": return "final";
1009 case "for": return "foreach";
1010 case "fu": return "function";
1011 case "imm": return "immutable";
1012 case "not": return "nothrow";
1013 case "noth": return "nothrow";
1014 case "ov": return "override";
1015 case "pro": return "property";
1016 case "st": return "string";
1017 case "tru": return (tkpos == 0 || gb[tkpos-1] != '@' ? "true" : "trusted");
1018 case "ub": return "ubyte";
1019 case "ui": return "uint";
1020 case "ul": return "ulong";
1021 case "us": return "ushort";
1022 case "ush": return "ushort";
1023 case "vo": return "void";
1026 case "ra": return "raise";
1027 case "Ex": return "Exception";
1028 case "Cr": return "Create";
1029 case "Cre": return "Create";
1030 case "De": return "Destroy";
1031 case "ove": return "overload";
1032 case "ovr": return "override";
1033 case "inh": return "inherited";
1034 case "inhe": return "inherited";
1035 case "proc": return "procedure";
1036 case "fun": return "function";
1037 case "con": return "constructor";
1038 case "des": return "destructor";
1048 // try some common things
1049 acp
= getDefToken();
1050 if (acp
.length
== 0) return;
1051 //{ import iv.vfs; auto fo = VFile("z00_tk.bin", "w"); fo.writeln(tk[$-tklen..$]); fo.writeln(acp); }
1052 } else if (acused
== 1) {
1054 auto tkx
= getDefToken();
1055 if (tkx
.length
&& tkx
!= acp
) addAcToken(tkx
);
1059 lc
.pos2xyVT(tkstpos
, rx
, ry
);
1060 //int residx = promptSelect(aclist[0..acused], winx+(rx-mXOfs), winy+(ry-mTopLine)+1);
1061 int residx
= dialogSelectAC(aclist
[0..acused
], winx
+(rx
-mXOfs
), winy
+(ry
-mTopLine
)+1);
1062 if (residx
< 0) return;
1063 acp
= aclist
[residx
];
1065 if (mReadOnly
) return;
1066 replaceText
!"end"(tkstpos
, tklen
, acp
);
1069 final char[] buildHelpText(this ME
) () {
1071 void buildHelpFor(UDA
) () {
1072 foreach (string memn
; __traits(allMembers
, ME
)) {
1073 static if (is(typeof(__traits(getMember
, ME
, memn
)))) {
1074 foreach (const attr
; __traits(getAttributes
, __traits(getMember
, ME
, memn
))) {
1075 static if (is(typeof(attr
) == UDA
)) {
1076 static if (!attr
.hidden
&& attr
.help
.length
&& attr
.key
.length
) {
1078 bool goodMode
= true;
1079 foreach (const attrx
; __traits(getAttributes
, __traits(getMember
, ME
, memn
))) {
1080 static if (is(attrx
== TEDSingleOnly
)) { if (!singleline
) goodMode
= false; }
1081 else static if (is(attrx
== TEDMultiOnly
)) { if (singleline
) goodMode
= false; }
1082 else static if (is(attrx
== TEDEditOnly
)) { if (readonly
) goodMode
= false; }
1083 else static if (is(attrx
== TEDROOnly
)) { if (!readonly
) goodMode
= false; }
1088 foreach (immutable _
; attr
.key
.length
..12) res
~= ".";
1100 buildHelpFor
!TEDKey
;
1101 while (res
.length
&& res
[$-1] <= ' ') res
= res
[0..$-1];
1105 protected enum Ecc
{ None
, Eaten
, Combo
}
1109 // Combo: combo start
1110 // comboBuf should contain comboCount+1 keys!
1111 protected final Ecc
checkKeys (const(char)[] keys
) {
1113 // check if current combo prefix is ok
1114 foreach (const ref TtyEvent ck
; comboBuf
[0..comboCount
+1]) {
1115 keys
= TtyEvent
.parse(k
, keys
);
1116 if (k
.key
== TtyEvent
.Key
.Error || k
.key
== TtyEvent
.Key
.None || k
.key
== TtyEvent
.Key
.Unknown
) return Ecc
.None
;
1117 if (k
!= ck
) return Ecc
.None
;
1119 return (keys
.length
== 0 ? Ecc
.Eaten
: Ecc
.Combo
);
1122 // fuck! `(this ME)` trick doesn't work here
1123 protected final Ecc
doEditorCommandByUDA(ME
=typeof(this)) (TtyEvent key
) {
1125 if (key
.key
== TtyEvent
.Key
.None
) return Ecc
.None
;
1126 if (key
.key
== TtyEvent
.Key
.Error || key
.key
== TtyEvent
.Key
.Unknown
) { repeatCounter
= -1; comboCount
= 0; return Ecc
.None
; }
1127 // process repeat counter
1128 if (repeatCounter
>= 0) {
1130 if (repeatSP
< repeatStack
.length
) {
1131 repeatStack
[repeatSP
++] = repeatCounter
;
1133 foreach (immutable idx
; 1..repeatStack
.length
) repeatStack
[idx
-1] = repeatStack
[idx
];
1134 repeatStack
[$-1] = repeatCounter
;
1137 repeatMode
= RepMode
.JustStarted
;
1140 void doBinMath(string op
) () {
1141 if (repeatSP
< 1) { ttyBeep(); return; }
1142 static if (op
== "+") repeatCounter
= repeatStack
[repeatSP
-1]+repeatCounter
;
1143 else static if (op
== "-") repeatCounter
= repeatStack
[repeatSP
-1]-repeatCounter
;
1144 else static if (op
== "*") repeatCounter
= repeatStack
[repeatSP
-1]*repeatCounter
;
1145 else static if (op
== "/") { if (repeatCounter
== 0) { ttyBeep(); repeatCounter
= 0; } else repeatCounter
= repeatStack
[repeatSP
-1]/repeatCounter
; }
1146 else static if (op
== "%") { if (repeatCounter
== 0) { ttyBeep(); repeatCounter
= 0; } else repeatCounter
= repeatStack
[repeatSP
-1]%repeatCounter
; }
1147 else static assert(0, "wtf?!");
1148 repeatMode
= RepMode
.Normal
;
1149 --repeatSP
; // pop used value
1151 // cancel counter mode?
1152 if (key
== "C-C" || key
== "C-G") { repeatCounter
= -1; return Ecc
.Eaten
; }
1153 if (repeatMode
!= RepMode
.CSpace
) {
1154 if (key
== "C-Space") { repeatMode
= RepMode
.CSpace
; return Ecc
.None
; } // so C-Space can allow insert alot of numbers, for example
1156 if (key
.key
== TtyEvent
.Key
.Char
&& key
.ch
>= '0' && key
.ch
<= '9') {
1157 if (repeatMode
== RepMode
.JustStarted
) repeatCounter
= 0;
1158 repeatCounter
= repeatCounter
*10+key
.ch
-'0';
1159 repeatMode
= RepMode
.Normal
;
1163 if (key
.key
== TtyEvent
.Key
.Char
&& key
.ch
== '+') { doBinMath
!"+"(); return Ecc
.Combo
; }
1164 if (key
.key
== TtyEvent
.Key
.Char
&& key
.ch
== '-') { doBinMath
!"-"(); return Ecc
.Combo
; }
1165 if (key
.key
== TtyEvent
.Key
.Char
&& key
.ch
== '*') { doBinMath
!"*"(); return Ecc
.Combo
; }
1166 if (key
.key
== TtyEvent
.Key
.Char
&& key
.ch
== '/') { doBinMath
!"/"(); return Ecc
.Combo
; }
1167 if (key
.key
== TtyEvent
.Key
.Char
&& key
.ch
== '%') { doBinMath
!"%"(); return Ecc
.Combo
; }
1168 // pop or drop value
1169 if (key
== "Backspace" || key
== "M-Backspace") {
1170 if (repeatSP
== 0) { ttyBeep(); repeatCounter
= 1; repeatMode
= RepMode
.JustStarted
; return Ecc
.Combo
; }
1171 if (key
== "Backspace") repeatCounter
= repeatStack
[repeatSP
-1];
1176 if (key
== "=" || key
== "!") {
1181 repeatMode
= RepMode
.Normal
;
1184 // normal key processing
1185 bool possibleCombo
= false;
1186 // temporarily add current key to combo
1187 comboBuf
[comboCount
] = key
;
1188 // check all known combos
1189 foreach (string memn
; __traits(allMembers
, ME
)) {
1190 static if (is(typeof(&__traits(getMember
, ME
, memn
)))) {
1191 import std
.meta
: AliasSeq
;
1192 alias mx
= AliasSeq
!(__traits(getMember
, ME
, memn
))[0];
1193 static if (isCallable
!mx
&& hasUDA
!(mx
, TEDKey
)) {
1195 bool goodMode
= true;
1196 static if (hasUDA
!(mx
, TEDSingleOnly
)) { if (!singleline
) goodMode
= false; }
1197 static if (hasUDA
!(mx
, TEDMultiOnly
)) { if (singleline
) goodMode
= false; }
1198 static if (hasUDA
!(mx
, TEDEditOnly
)) { if (readonly
) goodMode
= false; }
1199 static if (hasUDA
!(mx
, TEDROOnly
)) { if (!readonly
) goodMode
= false; }
1201 foreach (const TEDKey attr
; getUDAs
!(mx
, TEDKey
)) {
1202 auto cc
= checkKeys(attr
.key
);
1203 if (cc
== Ecc
.Eaten
) {
1205 static if (is(ReturnType
!mx
== void)) {
1206 comboCount
= 0; // reset combo
1207 static if (hasUDA
!(mx
, TEDRepeated
)) {
1208 scope(exit
) { repeatCounter
= -1; repeatIteration
= 0; } // reset counter
1209 if (repeatCounter
< 0) repeatCounter
= 1; // no counter means 1
1210 foreach (immutable rc
; 0..repeatCounter
) { repeatIteration
= rc
; mx(); }
1212 static if (!hasUDA
!(mx
, TEDRepeatChar
)) repeatCounter
= -1;
1217 repeatIteration
= 0;
1218 static if (hasUDA
!(mx
, TEDRepeated
)) {
1219 if (repeatCounter
!= 0 && mx()) {
1220 scope(exit
) { repeatCounter
= -1; repeatIteration
= 0; } // reset counter
1221 if (repeatCounter
< 0) repeatCounter
= 1; // no counter means 1
1222 comboCount
= 0; // reset combo
1223 foreach (immutable rc
; 1..repeatCounter
) {
1224 repeatIteration
= rc
;
1230 static if (!hasUDA
!(mx
, TEDRepeatChar
)) repeatCounter
= -1;
1232 repeatCounter
= -1; // reset counter
1233 comboCount
= 0; // reset combo
1238 } else if (cc
== Ecc
.Combo
) {
1239 possibleCombo
= true;
1246 // check if we can start/continue combo
1247 // combo can't start with normal char, but can include normal chars
1248 if (possibleCombo
&& (comboCount
> 0 || key
.key
!= TtyEvent
.Key
.Char
)) {
1249 if (++comboCount
< comboBuf
.length
-1) return Ecc
.Combo
;
1251 // if we have combo prefix, eat key unconditionally
1252 if (comboCount
> 0) {
1253 repeatCounter
= -1; // reset counter
1254 comboCount
= 0; // reset combo, too long, or invalid, or none
1260 void doCounterMode () {
1261 if (waitingInF5
) { repeatCounter
= -1; return; }
1262 if (repeatCounter
< 0) {
1263 // start counter mode
1265 repeatMode
= RepMode
.JustStarted
; // just started
1267 repeatCounter
*= 2; // so ^P will multiply current counter
1271 char[] pasteCollector
;
1272 int pasteModeCounter
;
1273 bool pastePrevWasCR
;
1275 private void promptKeyProcessor (EditorEngine ed
) {
1276 // input buffer changed
1277 // check if it was *really* changed
1278 if (incSearchBuf
.length
== ed
.textsize
) {
1280 foreach (char ch
; ed
[]) { if (incSearchBuf
[pos
] != ch
) break; ++pos
; }
1281 if (pos
>= ed
.textsize
) return; // nothing was changed, so nothing to do
1284 incSearchBuf
.length
= 0;
1285 incSearchBuf
.assumeSafeAppend
;
1286 foreach (char ch
; ed
[]) incSearchBuf
~= ch
;
1287 doNextIncSearch(false); // don't move pointer
1290 void addToPasteCollector (TtyEvent key
) {
1291 void addChar (dchar dch
) {
1292 if (dch
< ' ') { if (dch
!= '\t' && dch
!= '\n') return; }
1293 if (dch
> 255) return;
1297 len
= utf8Encode(buf
[], koi2uni(cast(char)dch
));
1298 if (len
< 1) return;
1300 buf
[0] = cast(char)dch
;
1303 foreach (char pch
; buf
[0..len
]) {
1304 auto optr
= pasteCollector
.ptr
;
1305 pasteCollector
~= pch
;
1306 if (pasteCollector
.ptr
!is optr
) {
1307 import core
.memory
: GC
;
1308 if (pasteCollector
.ptr
is GC
.addrOf(pasteCollector
.ptr
)) GC
.setAttr(pasteCollector
.ptr
, GC
.BlkAttr
.NO_INTERIOR
);
1314 if (key
.key
== TtyEvent
.Key
.PasteStart
) {
1315 //VFile("z00.log", "w").writeln("PasteStart; pasteModeCounter before: ", pasteModeCounter);
1317 pastePrevWasCR
= false;
1320 if (key
.key
== TtyEvent
.Key
.PasteEnd
) {
1321 //VFile("z00.log", "w").writeln("PasteEnd; pasteModeCounter before: ", pasteModeCounter);
1322 pastePrevWasCR
= false;
1323 if (pasteModeCounter
< 1) { ttyBeep(); ttyBeep(); return; }
1324 if (--pasteModeCounter
== 0) {
1325 if (pasteCollector
.length
) {
1326 // insert text in "paste mode"
1327 if (incInputActive
) {
1328 foreach (char ch
; pasteCollector
) {
1330 k
.key
= TtyEvent
.Key
.Char
;
1332 promptProcessKey(k
, &promptKeyProcessor
);
1334 drawStatus(); // just in case
1337 scope(exit
) doPasteEnd();
1338 if (utfuck
) doPutTextUtf(pasteCollector
); else doPutText(pasteCollector
);
1340 pasteCollector
.length
= 0;
1341 pasteCollector
.assumeSafeAppend
;
1347 if (key
== "Enter") {
1348 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "{Enter}"); }
1349 if (!pastePrevWasCR
) addChar('\n');
1350 pastePrevWasCR
= false;
1353 if (key
.key
== TtyEvent
.Key
.ModChar
&& (key
.ch
== 13 || key
.ch
== 10 || key
.ch
== 12)) {
1354 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "{X:%d}", cast(int)key.ch); }
1355 if (key
.ch
!= 13 ||
!pastePrevWasCR
) addChar('\n');
1356 pastePrevWasCR
= (key
.ch
== 13);
1359 if (key
.key
== TtyEvent
.Key
.ModChar
) {
1360 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "{C:%d:%c}", cast(int)key.ch, cast(int)key.ch); }
1361 pastePrevWasCR
= false;
1364 pastePrevWasCR
= false;
1365 if (key
.key
== TtyEvent
.Key
.Char
) {
1366 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "%c", cast(int)key.ch); }
1367 addChar(cast(char)key
.ch
);
1372 bool processKey (TtyEvent key
) {
1373 //{ import core.stdc.stdio : stderr, fprintf; auto st = key.toString(); fprintf(stderr, "{%.*s}", cast(uint)st.length, st.ptr); }
1374 // hack it here, so it won't interfere with normal keyboard processing
1375 if (pasteModeCounter
) { addToPasteCollector(key
); return true; }
1376 if (key
.key
== TtyEvent
.Key
.PasteStart
) { /*doPasteStart();*/ addToPasteCollector(key
); return true; }
1377 if (key
.key
== TtyEvent
.Key
.PasteEnd
) { /*doPasteEnd();*/ addToPasteCollector(key
); return true; }
1380 waitingInF5
= false;
1381 if (key
== "enter") {
1382 if (tempBlockFileName
.length
) {
1383 try { doBlockRead(tempBlockFileName
); } catch (Exception
) {} // sorry
1389 if (key
.key
== TtyEvent
.Key
.Error || key
.key
== TtyEvent
.Key
.Unknown
) { repeatCounter
= -1; comboCount
= 0; return false; }
1391 if (incInputActive
) {
1392 if (key
.key
== TtyEvent
.Key
.ModChar
) {
1393 if (key
== "C-C") { incInputActive
= false; resetIncSearchPos(); promptNoKillText(); return true; }
1394 if (key
== "C-R") { incSearchDir
= 1; doNextIncSearch(); promptNoKillText(); return true; }
1395 if (key
== "C-V") { incSearchDir
= -1; doNextIncSearch(); promptNoKillText(); return true; }
1398 if (key
== "esc" || key
== "enter") {
1399 incInputActive
= false;
1401 resetIncSearchPos();
1404 if (mPromptInput
!is null) mPromptInput
.utfuck
= utfuck
;
1405 promptProcessKey(key
, &promptKeyProcessor
);
1406 drawStatus(); // just in case
1410 resetIncSearchPos();
1412 final switch (doEditorCommandByUDA(key
)) {
1413 case Ecc
.None
: break;
1419 if (key
.key
== TtyEvent
.Key
.Char
) {
1420 scope(exit
) repeatCounter
= -1; // reset counter
1421 if (readonly
) return false;
1422 if (repeatCounter
!= 0) {
1423 if (repeatCounter
< 0) repeatCounter
= 1;
1424 if (repeatCounter
> 1) {
1426 scope(exit
) undoGroupEnd();
1427 foreach (immutable _
; 0..repeatCounter
) doPutChar(cast(char)key
.ch
);
1429 doPutChar(cast(char)key
.ch
);
1438 bool processClick (int button
, int x
, int y
) {
1439 if (x
< 0 || y
< 0 || x
>= winw || y
>= winh
) return false;
1440 if (button
!= 0) return false;
1441 gotoXY(x
, topline
+y
);
1446 void pasteToX11 () {
1452 if (!hasMarkedBlock
) return;
1454 void doPaste (string cbkey
) {
1456 string
[string
] moreEnv
;
1457 moreEnv
["K8_SUBSHELL"] = "tan";
1458 auto pp
= std
.process
.pipeProcess(
1459 //["dmd", "-c", "-o-", "-verrors=64", "-vcolumns", fname],
1460 ["xsel", "-i", cbkey
],
1461 std
.process
.Redirect
.stderrToStdout|std
.process
.Redirect
.stdout|std
.process
.Redirect
.stdin
,
1463 std
.process
.Config
.none
,
1467 auto rng
= markedBlockRange
;
1468 foreach (char ch
; rng
) pp
.stdin
.write(ch
);
1472 } catch (Exception
) {}
1480 static struct PlainMatch
{
1482 @property bool empty () const pure nothrow @safe @nogc { return (s
< 0 || s
>= e
); }
1485 // epos is not included
1486 final PlainMatch
findTextPlain (const(char)[] pat
, int spos
, int epos
, bool words
, bool caseSens
) {
1488 immutable ts
= gb
.textsize
;
1489 if (pat
.length
== 0 || pat
.length
> ts
) return res
;
1490 if (epos
<= spos || spos
>= ts
) return res
;
1491 if (spos
< 0) spos
= 0;
1492 if (epos
< 0) epos
= 0;
1493 if (epos
> ts
) epos
= ts
;
1494 immutable bl
= cast(int)pat
.length
;
1495 //dialogMessage!"debug"("findTextPlain", "spos=%s; epos=%s; ts=%s; curpos=%s; pat:[%s]", spos, epos, ts, curpos, pat);
1496 while (ts
-spos
>= bl
) {
1497 if (caseSens ||
!pat
.ptr
[0].isalpha
) {
1498 spos
= gb
.fastFindChar(spos
, pat
.ptr
[0]);
1499 if (ts
-spos
< bl
) break;
1503 foreach (int p
; spos
..spos
+bl
) if (gb
[p
] != pat
.ptr
[p
-spos
]) { found
= false; break; }
1505 foreach (int p
; spos
..spos
+bl
) if (gb
[p
].tolower
!= pat
.ptr
[p
-spos
].tolower
) { found
= false; break; }
1507 // check word boundaries
1508 if (found
&& words
) {
1509 if (spos
> 0 && isWordChar(gb
[spos
-1])) found
= false;
1511 if (ep
< ts
&& isWordChar(gb
[ep
])) found
= false;
1513 //dialogMessage!"debug"("findTextPlain", "spos=%s; epos=%s; found=%s", spos, epos, found);
1524 // epos is not included
1525 final PlainMatch
findTextPlainBack (const(char)[] pat
, int spos
, int epos
, bool words
, bool caseSens
) {
1527 immutable ts
= gb
.textsize
;
1528 if (pat
.length
== 0 || pat
.length
> ts
) return res
;
1529 if (epos
<= spos || spos
>= ts
) return res
;
1530 if (spos
< 0) spos
= 0;
1531 if (epos
< 0) epos
= 0;
1532 if (epos
> ts
) epos
= ts
;
1533 immutable bl
= cast(int)pat
.length
;
1534 if (ts
-epos
< bl
) epos
= ts
-bl
;
1535 while (epos
>= spos
) {
1538 foreach (int p
; epos
..epos
+bl
) if (gb
[p
] != pat
.ptr
[p
-epos
]) { found
= false; break; }
1540 foreach (int p
; epos
..epos
+bl
) if (gb
[p
].tolower
!= pat
.ptr
[p
-epos
].tolower
) { found
= false; break; }
1542 if (found
&& words
) {
1543 if (epos
> 0 && isWordChar(gb
[epos
-1])) found
= false;
1545 if (ep
< ts
&& isWordChar(gb
[ep
])) found
= false;
1557 // epos is not included
1558 // caps are fixed so it can be used to index gap buffer
1559 final bool findTextRegExp (RegExp re
, int spos
, int epos
, Pike
.Capture
[] caps
) {
1560 Pike
.Capture
[1] tcaps
;
1561 if (epos
<= spos || spos
>= textsize
) return false;
1562 if (caps
.length
== 0) caps
= tcaps
;
1563 if (spos
< 0) spos
= 0;
1564 if (epos
< 0) epos
= 0;
1565 if (epos
> textsize
) epos
= textsize
;
1566 auto ctx
= Pike
.create(re
, caps
);
1567 if (!ctx
.valid
) return false;
1568 int res
= SRes
.Again
;
1569 foreach (const(char)[] buf
; gb
.bufparts(spos
)) {
1570 res
= ctx
.exec(buf
, false);
1572 if (res
!= SRes
.Again
) return false;
1577 if (res
< 0) return false;
1578 if (spos
+caps
[0].s
>= epos
) return false;
1579 if (spos
+caps
[0].e
> epos
) return false;
1580 foreach (ref cp
; caps
) if (cp
.s
< cp
.e
) { cp
.s
+= spos
; cp
.e
+= spos
; }
1585 public static struct FindResult
{
1589 // find all occurences of regexp, line by line (i.e. only first hit in line will be used)
1590 // returns "unsafe array" that cannot be modified
1591 final FindResult
[] findAllRegExp (const(char)[] restr
, bool* error
=null) {
1594 void addFindRect (int line
, int col
) {
1595 if (res
.length
> 0 && res
.ptr
[0].line
== line
) return; // skip duplicate lines
1596 auto optr
= res
.ptr
;
1597 res
~= FindResult(line
, col
);
1598 if (res
.ptr
!is optr
) {
1599 import core
.memory
: GC
;
1600 if (res
.ptr
is GC
.addrOf(res
.ptr
)) GC
.setAttr(res
.ptr
, GC
.BlkAttr
.NO_INTERIOR
);
1604 auto re
= RegExp
.create(restr
, SRFlags
.Multiline
);
1605 if (!re
.valid
) { if (error
!is null) *error
= true; return null; } // alas
1606 if (error
!is null) *error
= false;
1608 Pike
.Capture
[2] caps
;
1610 while (spos
< textsize
) {
1611 if (findTextRegExp(re
, spos
, textsize
, caps
[])) {
1613 lc
.pos2xy(caps
.ptr
[0].s
, cx
, cy
);
1614 addFindRect(cy
, cx
);
1625 // epos is not included
1626 // caps are fixed so it can be used to index gap buffer
1627 final bool findTextRegExpBack (RegExp re
, int spos
, int epos
, Pike
.Capture
[] caps
) {
1628 import core
.stdc
.string
: memcpy
;
1630 Pike
.Capture
* savedCaps
;
1631 Pike
.Capture
[1] tcaps
;
1632 if (epos
<= spos || spos
>= textsize
) return false;
1633 if (caps
.length
== 0) caps
= tcaps
;
1634 if (spos
< 0) spos
= 0;
1635 if (epos
< 0) epos
= 0;
1636 if (epos
> textsize
) epos
= textsize
;
1637 while (spos
< epos
) {
1638 auto ctx
= Pike
.create(re
, caps
);
1639 if (!ctx
.valid
) break;
1640 int res
= SRes
.Again
;
1641 foreach (const(char)[] buf
; gb
.bufparts(spos
)) {
1642 res
= ctx
.exec(buf
, false);
1644 if (res
!= SRes
.Again
) return false;
1649 if (spos
+caps
[0].s
>= epos
) break;
1650 //dialogMessage!"debug"("findTextRegexpBack", "spos=%s; epos=%s; found=%s", spos, epos, spos+caps[0].s);
1652 if (savedCaps
is null) {
1653 if (!csave
.active
) {
1654 csave
= MemPool
.create
;
1655 if (!csave
.active
) return false; // alas
1656 savedCaps
= csave
.alloc
!(typeof(caps
[0]))(cast(uint)(caps
[0].sizeof
*(caps
.length
-1)));
1657 if (savedCaps
is null) return false; // alas
1661 foreach (ref cp
; caps
) if (cp
.s
< cp
.e
) { cp
.s
+= spos
; cp
.e
+= spos
; }
1662 memcpy(savedCaps
, caps
.ptr
, caps
[0].sizeof
*caps
.length
);
1663 //FIXME: should we skip the whole found match?
1666 if (savedCaps
is null) return false;
1667 // restore latest match
1668 memcpy(caps
.ptr
, savedCaps
, caps
[0].sizeof
*caps
.length
);
1672 final void srrPlain (in ref SearchReplaceOptions srr
) {
1673 if (srr
.search
.length
== 0) return;
1674 if (srr
.inselection
&& !hasMarkedBlock
) return;
1675 scope(exit
) fullDirty(); // to remove highlighting
1676 bool closeGroup
= false;
1677 scope(exit
) if (closeGroup
) undoGroupEnd();
1679 // epos will be fixed on replacement
1681 if (srr
.inselection
) {
1685 if (!srr
.backwards
) {
1696 while (spos
< epos
) {
1698 if (srr
.backwards
) {
1699 mt
= findTextPlainBack(srr
.search
, spos
, epos
, srr
.wholeword
, srr
.casesens
);
1701 mt
= findTextPlain(srr
.search
, spos
, epos
, srr
.wholeword
, srr
.casesens
);
1703 if (mt
.empty
) break;
1704 // if i need to skip comments, do highlighting
1705 if (srr
.nocomments
&& hl
!is null) {
1706 auto lidx
= lc
.pos2line(mt
.s
);
1707 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
1708 if (hiIsComment(gb
.hi(mt
.s
))) {
1710 if (!srr
.backwards
) spos
= mt
.s
+1; else epos
= mt
.s
;
1716 bool doundo
= (mt
.s
!= curpos
);
1717 if (doundo
) gotoPos
!true(mt
.s
);
1720 drawPartHighlight(mt
.s
, mt
.e
-mt
.s
, IncSearchColor
);
1721 auto act
= dialogReplacePrompt(lc
.pos2line(mt
.s
)-topline
+winy
);
1722 //FIXME: do undo only if it was succesfully registered
1723 //doUndo(); // undo cursor movement
1724 if (act
== DialogRepPromptResult
.Cancel
) break;
1725 //if (doundo) doUndo();
1726 if (act
== DialogRepPromptResult
.Skip
) {
1727 if (!srr
.backwards
) spos
= mt
.s
+1; else epos
= mt
.s
;
1730 if (act
== DialogRepPromptResult
.All
) { doAll
= true; }
1732 if (doAll
&& !closeGroup
) { undoGroupStart(); closeGroup
= true; }
1733 replaceText
!"end"(mt
.s
, mt
.e
-mt
.s
, srr
.replace
);
1735 if (!srr
.backwards
) {
1737 spos
= mt
.s
+cast(int)srr
.replace
.length
;
1738 epos
-= (mt
.e
-mt
.s
)-cast(int)srr
.replace
.length
;
1747 dialogMessage
!"info"("Search and Replace", "%s replacement%s made", count
, (count
!= 1 ?
"s" : ""));
1751 final void srrRegExp (in ref SearchReplaceOptions srr
) {
1752 import std
.utf
: byChar
;
1753 if (srr
.search
.length
== 0) return;
1754 if (srr
.inselection
&& !hasMarkedBlock
) return;
1755 auto re
= RegExp
.create(srr
.search
.byChar
, (srr
.casesens ?
0 : SRFlags
.CaseInsensitive
)|SRFlags
.Multiline
);
1756 if (!re
.valid
) { ttyBeep
; return; }
1757 scope(exit
) fullDirty(); // to remove highlighting
1758 bool closeGroup
= false;
1759 scope(exit
) if (closeGroup
) undoGroupEnd();
1761 // epos will be fixed on replacement
1764 if (srr
.inselection
) {
1768 if (!srr
.backwards
) {
1778 Pike
.Capture
[64] caps
; // max captures
1780 char[] newtext
; // will be aggressively reused!
1781 scope(exit
) { delete newtext
; newtext
.length
= 0; }
1784 if (newtext
.length
) { newtext
.length
= 0; newtext
.assumeSafeAppend
; }
1785 auto reps
= srr
.replace
;
1787 mainloop
: while (spos
< reps
.length
) {
1788 if ((reps
[spos
] == '$' || reps
[spos
] == '\\') && reps
.length
-spos
> 1 && reps
[spos
+1].isdigit
) {
1789 int n
= reps
[spos
+1]-'0';
1791 if (!caps
[n
].empty
) foreach (char ch
; this[caps
[n
].s
..caps
[n
].e
]) newtext
~= ch
;
1792 } else if ((reps
[spos
] == '$' || reps
[spos
] == '\\') && reps
.length
-spos
> 2 && reps
[spos
+1] == '{' && reps
[spos
+2].isdigit
) {
1793 bool toupper
, tolower
, capitalize
, uncapitalize
;
1797 while (spos
< reps
.length
&& reps
[spos
].isdigit
) n
= n
*10+reps
[spos
++]-'0';
1798 while (spos
< reps
.length
&& reps
[spos
] != '}') {
1799 switch (reps
[spos
++]) {
1800 case 'u': case 'U': toupper
= true; tolower
= false; break;
1801 case 'l': case 'L': tolower
= true; toupper
= false; break;
1802 case 'C': capitalize
= true; break;
1803 case 'c': uncapitalize
= true; break;
1804 default: // ignore other flags
1807 if (spos
< reps
.length
&& reps
[spos
] == '}') ++spos
;
1808 if (n
< caps
.length
&& !caps
[n
].empty
) {
1809 int tp
= caps
[n
].s
, ep
= caps
[n
].e
;
1811 if (capitalize || toupper
) ch
= ch
.toupper
;
1812 else if (uncapitalize || tolower
) ch
= ch
.tolower
;
1816 if (toupper
) ch
= ch
.toupper
;
1817 else if (tolower
) ch
= ch
.tolower
;
1821 } else if (reps
[spos
] == '\\' && reps
.length
-spos
> 1) {
1823 switch (reps
[spos
-1]) {
1824 case 't': newtext
~= '\t'; break;
1825 case 'n': newtext
~= '\n'; break;
1826 case 'r': newtext
~= '\r'; break;
1827 case 'a': newtext
~= '\a'; break;
1828 case 'b': newtext
~= '\b'; break;
1829 case 'e': newtext
~= '\x1b'; break;
1831 if (reps
.length
-spos
< 1) break mainloop
;
1832 int n
= digitInBase(reps
[spos
], 16);
1835 if (reps
.length
-spos
> 0 && digitInBase(reps
[spos
], 16) >= 0) {
1836 n
= n
*16+digitInBase(reps
[spos
], 16);
1839 newtext
~= cast(char)n
;
1842 newtext
~= reps
[spos
-1];
1846 newtext
~= reps
[spos
++];
1849 replaceText
!"end"(caps
[0].s
, caps
[0].e
-caps
[0].s
, newtext
);
1850 return cast(int)newtext
.length
;
1854 while (spos
< epos
) {
1856 if (srr
.backwards
) {
1857 found
= findTextRegExpBack(re
, spos
, epos
, caps
[]);
1859 found
= findTextRegExp(re
, spos
, epos
, caps
[]);
1863 if (srr
.nocomments
&& hl
!is null) {
1864 auto lidx
= lc
.pos2line(caps
[0].s
);
1865 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
1866 if (hiIsComment(gb
.hi(caps
[0].s
))) {
1868 if (!srr
.backwards
) spos
= caps
[0].s
+1; else epos
= caps
[0].s
;
1873 bool doundo
= (caps
[0].s
!= curpos
);
1874 if (doundo
) gotoPos
!true(caps
[0].s
);
1877 drawPartHighlight(caps
[0].s
, caps
[0].e
-caps
[0].s
, IncSearchColor
);
1878 auto act
= dialogReplacePrompt(lc
.pos2line(caps
[0].s
)-topline
+winy
);
1879 //FIXME: do undo only if it was succesfully registered
1880 //doUndo(); // undo cursor movement
1881 if (act
== DialogRepPromptResult
.Cancel
) break;
1882 //if (doundo) doUndo();
1883 if (act
== DialogRepPromptResult
.Skip
) {
1884 if (!srr
.backwards
) spos
= caps
[0].s
+1; else epos
= caps
[0].s
;
1887 if (act
== DialogRepPromptResult
.All
) { doAll
= true; }
1889 if (doAll
&& !closeGroup
) { undoGroupStart(); closeGroup
= true; }
1890 int replen
= rereplace();
1892 if (!srr
.backwards
) {
1893 spos
= caps
[0].s
+(replen ? replen
: 1);
1894 epos
+= replen
-(caps
[0].e
-caps
[0].s
);
1903 dialogMessage
!"info"("Search and Replace", "%s replacement%s made", count
, (count
!= 1 ?
"s" : ""));
1908 void processWordWith (scope char delegate (char ch
) dg
) {
1909 if (dg
is null) return;
1910 bool undoAdded
= false;
1911 scope(exit
) if (undoAdded
) undoGroupEnd();
1913 while (pos
< gb
.textsize
&& gb
[pos
] <= ' ') ++pos
;
1914 if (!isWordChar(gb
[pos
])) return;
1916 while (pos
> 0 && isWordChar(gb
[pos
-1])) --pos
;
1917 while (pos
< gb
.textsize
) {
1919 if (!isWordChar(gb
[pos
])) break;
1922 if (!undoAdded
) { undoAdded
= true; undoGroupStart(); }
1923 replaceText
!"none"(pos
, 1, (&nc
)[0..1]);
1930 static bool isShitPPWordChar (char ch
) {
1931 // '$' should come in too, but meh...
1933 (ch
>= 'A' && ch
<= 'Z') ||
1934 (ch
>= 'a' && ch
<= 'z') ||
1935 (ch
>= '0' && ch
<= '9') ||
1939 // pos must be on ':', or this returns `false`
1940 bool isGoodDColonAt (int pos
) {
1941 if (pos
< 0 || pos
>= gb
.textsize
) return false;
1942 if (gb
[pos
] != ':') return false;
1943 // if we are at 2nd (or more) colon, move to the first one
1944 if (pos
> 0 && gb
[pos
-1] == ':') --pos
;
1945 // now we should be at the first ':' in '::', check for valid position
1946 if (pos
< 1 || pos
+2 >= gb
.textsize || gb
[pos
+1] != ':') return false; // not a '::', or invalid pos
1947 if (!isShitPPWordChar(gb
[pos
-1])) return false; // invalid boundaries
1948 if (!isShitPPWordChar(gb
[pos
+2]) && gb
[pos
+2] != '~') return false; // invalid boundaries (special '~' for dtors)
1952 int vchGotoLineStart (int pos
) {
1953 if (pos
<= 0) return 0;
1954 while (pos
> 0 && gb
[pos
-1] != '\n') --pos
;
1958 int vchGotoLineEnd (int pos
) {
1959 if (pos
<= 0) pos
= 0;
1960 while (pos
< gb
.textsize
&& gb
[pos
] != '\n') ++pos
;
1964 int vchGotoPrevLine (int pos
) {
1965 pos
= vchGotoLineStart(pos
);
1966 return vchGotoLineStart(pos
-1);
1969 // skip current line
1970 int vchGotoSkipLine (int pos
) {
1971 pos
= vchGotoLineEnd(pos
);
1973 if (pos
< gb
.textsize
) ++pos
;
1977 // is empty single-line comment?
1979 // -1 for "not a comment"
1980 // 0 for "comment with data"
1981 // 1 for empty single-line comment
1982 int isLineAtPosASingleLineVCHComment (int pos
) {
1983 pos
= vchGotoLineStart(pos
);
1985 while (pos
< gb
.textsize
&& gb
[pos
] != '\n' && gb
[pos
] <= ' ') ++pos
;
1986 // check for single-line comment
1987 if (pos
+2 > gb
.textsize
) return -1;
1988 if (gb
[pos
] != '/' || gb
[pos
+1] != '/') return -1;
1989 if (pos
+2 == gb
.textsize
) return 1;
1990 if (gb
[pos
+2] == '/') return -1;
1991 pos
+= 2; // skip comment
1992 // check if we have any non-blank chars in it
1993 while (pos
< gb
.textsize
&& gb
[pos
] != '\n' && gb
[pos
] <= ' ') ++pos
;
1994 return (pos
>= gb
.textsize || gb
[pos
] == '\n' ?
1 : 0);
1997 // good comment for Vavoom header?
1998 bool isLineAtPosAGoodVCHComment (int pos
) {
1999 pos
= vchGotoLineStart(pos
);
2001 while (pos
< gb
.textsize
&& gb
[pos
] != '\n' && gb
[pos
] <= ' ') ++pos
;
2002 // check for single-line comment
2003 if (pos
+2 > gb
.textsize
) return false;
2004 if (gb
[pos
] != '/' || gb
[pos
+1] != '/') return false;
2005 if (pos
+2 == gb
.textsize
) return true;
2006 if (gb
[pos
+2] == '/') return false;
2007 pos
+= 2; // skip comment
2008 // check if we have any non-blank chars in it
2009 while (pos
< gb
.textsize
&& gb
[pos
] != '\n' && gb
[pos
] <= ' ') ++pos
;
2010 if (pos
>= gb
.textsize
) return true; // dunno
2011 if (gb
[pos
] != '\n' || pos
== 2) return true; // non-empty
2012 // for empty comments, allow only one single line (so check the previous line)
2013 // if previous line is "comment with data", allow this empty line
2014 pos
= vchGotoPrevLine(pos
);
2015 return (isLineAtPosASingleLineVCHComment(pos
) == 0);
2018 // remove extra blanks (and insert required blanks) to good comment
2019 // WARNING! comment MUST be good
2020 // curpos must be at comment start, and will be on the next line
2021 void fixAGoodVCHCommentAndSkipIt () {
2023 // delete leading spaces
2024 while (gb
[pos
] != '/') ++pos
;
2025 if (pos
> curpos
) deleteText
!"start"(curpos
, pos
-curpos
);
2028 // we should have exactly two spaces after '//'
2029 if (gb
[pos
] > ' ' || gb
[pos
] == '\n') {
2031 if (gb
[pos
] != '\n') insertText
!("end", false)(pos
, " ");
2032 } else if (gb
[pos
+1] > ' ' || gb
[pos
+1] == '\n') {
2034 if (gb
[pos
+1] != '\n') {
2035 insertText
!("end", false)(pos
+1, " ");
2037 deleteText
!"start"(pos
, 1);
2039 } else if (gb
[pos
+2] <= ' ' && gb
[pos
+2] != '\n') {
2040 // have more remove extra
2043 while (gb
[epos
] <= ' ' && gb
[epos
] != '\n') ++epos
;
2044 deleteText
!"start"(pos
, epos
-pos
);
2048 while (gb
[pos
] != '\n') ++pos
;
2052 // BUG: char at EOT is invalid
2053 bool vchCheckWord (int pos
, string word
) {
2054 if (word
.length
== 0 || pos
>= gb
.textsize
) return false;
2055 if (isShitPPWordChar(gb
[pos
+cast(int)word
.length
])) return false;
2056 foreach (int f
; 0..cast(int)word
.length
) if (gb
[pos
+f
] != word
[f
]) return false;
2060 // position cursor at single-line comment group
2061 // basically, just inserts "//***"
2062 void doGenVavoomRegionCmt () {
2063 auto pos
= vchGotoLineStart(curpos
);
2064 if (!isLineAtPosAGoodVCHComment(pos
)) { ttyBeep(); return; } // invalid
2066 auto lastgoodpos
= pos
;
2067 while (pos
> 0 && isLineAtPosAGoodVCHComment(pos
)) {
2069 pos
= vchGotoLineStart(vchGotoPrevLine(pos
));
2072 bool insertSecond
= (isLineAtPosASingleLineVCHComment(pos
) == 0);
2073 // start undo group, so undo will remove the whole header at once
2075 scope(exit
) undoGroupEnd();
2078 scope(exit
) delete cutline
;
2079 cutline
.reserve(80);
2081 while (cutline
.length
< 76) cutline
~= '*';
2083 // insert starting cutline
2086 // insert second line
2087 if (insertSecond
) doPutText("//\n");
2088 // go to the end, and insert closing cutlines
2090 bool prevWasEmpty
= true;
2091 while (pos
< gb
.textsize
&& isLineAtPosAGoodVCHComment(pos
)) {
2092 prevWasEmpty
= (isLineAtPosASingleLineVCHComment(pos
) > 0);
2093 pos
= vchGotoSkipLine(pos
);
2096 if (!prevWasEmpty
) doPutText("//\n");
2098 // leave cursor here
2101 // position cursor at function/method name
2102 // this converts single-line comments
2103 void doGenVavoomCmt () {
2105 if (isLineAtPosAGoodVCHComment(pos
)) { ttyBeep(); return; } // invalid
2106 // find identifier start (including '::' for shitpp)
2108 if (gb
[pos
] == ':') {
2109 if (!isGoodDColonAt(pos
)) { ttyBeep(); return; } // invalid
2111 if (!isShitPPWordChar(gb
[pos
])) {
2112 // special for dtors
2113 if (gb
[pos
] == '~') {
2114 if (pos
< 3 || gb
[pos
-1] != ':' || gb
[pos
-2] != ':') break;
2115 pos
-= 1; // skip to '::' start (the following `--` will do the second move)
2116 } else if (pos
> 0 && gb
[pos
] == '.') {
2118 if (!isShitPPWordChar(gb
[pos
-1])) break;
2127 // find identifier end
2129 while (epos
< gb
.textsize
) {
2130 if (gb
[epos
] == ':') {
2131 if (!isGoodDColonAt(epos
)) { ttyBeep(); return; } // invalid
2133 if (!isShitPPWordChar(gb
[epos
])) {
2134 // special for dtors
2135 if (gb
[epos
] == '~') {
2136 if (epos
< 3 || gb
[epos
-1] != ':' || gb
[epos
-2] != ':') break;
2137 epos
+= 1; // skip to '::' end (the following `++` will do the second move)
2138 } else if (epos
+1 < gb
.textsize
&& gb
[epos
] == '.') {
2140 if (!isShitPPWordChar(gb
[epos
+1])) break;
2148 if (epos
== pos
) { ttyBeep(); return; } // invalid
2149 // [pos..epos) is our identifier; copy it into temp array
2151 scope(exit
) delete id
;
2152 id
.reserve(epos
-pos
+127);
2153 foreach (auto f
; pos
..epos
) id
~= gb
[f
];
2154 if (id
== "virtual" || id
== "override") { ttyBeep(); return; } // invalid
2155 // if this is `operator`, collect operator name too
2156 if (id
== "operator" || id
.endsWith("::operator")) {
2158 while (epos
< gb
.textsize
&& gb
[epos
] != '\n' && gb
[epos
] <= ' ') ++epos
;
2160 if (epos
+1 <= gb
.textsize
&& gb
[epos
] == '(' && gb
[epos
+1] == ')') {
2164 while (epos
< gb
.textsize
&& gb
[epos
] != '\n' && gb
[epos
] != '(') ++epos
;
2166 // remove trailing spaces
2167 while (epos
> xxpos
&& gb
[epos
-1] <= ' ') --epos
;
2168 // append collected chars
2169 while (xxpos
< epos
) id
~= gb
[xxpos
++];
2171 // start undo group, so undo will remove the whole header at once
2173 scope(exit
) undoGroupEnd();
2174 // move to line start (and save xpos)
2175 while (pos
> 0 && gb
[pos
-1] != '\n') --pos
;
2176 int xpos
= curpos
-pos
;
2180 scope(exit
) delete cutline
;
2181 cutline
.reserve(80);
2183 while (cutline
.length
< 76) cutline
~= '=';
2185 // skip good comments that we will add to header
2186 while (curpos
> 0 && isLineAtPosAGoodVCHComment(vchGotoPrevLine(curpos
))) {
2187 gotoPos(vchGotoPrevLine(curpos
));
2189 // insert cutline (no indent)
2191 // insert second line
2196 // finish third line, insert fourth line
2197 doPutText("\n//\n");
2198 // skip and normalize good comments
2199 if (isLineAtPosAGoodVCHComment(curpos
)) {
2200 while (isLineAtPosAGoodVCHComment(curpos
)) fixAGoodVCHCommentAndSkipIt();
2201 // and insert "nice line"
2204 // insert final line
2206 // back to identifier, so we can pretend that no cursor movement happened
2207 gotoPos(curpos
+xpos
);
2208 // now do some cosmetic: remove leading spaces, remove `virtual`
2209 pos
= vchGotoLineStart(curpos
);
2211 // it is safe to assume that we have non-blank chars here
2212 while (epos
< gb
.textsize
&& gb
[epos
] <= ' ') ++epos
;
2213 // check if we have "virtual"
2214 if (vchCheckWord(epos
, "virtual")) {
2217 // it is safe to assume that we have non-blank chars here
2218 while (epos
< gb
.textsize
&& gb
[epos
] <= ' ') ++epos
;
2221 gotoPos(curpos
-(epos
-pos
));
2222 deleteText
!"none"(pos
, epos
-pos
);
2224 // delete "override" at the end
2225 int stpos
= vchGotoLineStart(curpos
);
2226 int endpos
= vchGotoLineEnd(curpos
);
2227 // they're guaranteed to be different
2228 if (gb
[endpos
-1] == '{' || gb
[endpos
-1] == ';') --endpos
; /* this is how i usually doing it */
2229 const int realend
= endpos
;
2230 /* skip trailing spaces */
2231 while (endpos
> stpos
&& gb
[endpos
-1] <= ' ') --endpos
;
2232 /* check for the "override" word */
2233 immutable string ovword
= "override";
2234 usize wpos
= ovword
.length
;
2235 while (wpos
> 0 && endpos
> stpos
&& gb
[endpos
-1] == ovword
[wpos
-1]) {
2239 import std
.ascii
: isAlphaNum
;
2240 if (wpos
== 0 && endpos
> stpos
&& !isAlphaNum(gb
[endpos
-1])) {
2242 deleteText
!"none"(endpos
, realend
-endpos
);
2246 void doGenCmtTearLine () {
2248 // start undo group, so undo will remove the whole header at once
2250 scope(exit
) undoGroupEnd();
2251 // move to line start (and save xpos)
2252 while (pos
> 0 && gb
[pos
-1] != '\n') --pos
;
2253 int xpos
= curpos
-pos
;
2257 scope(exit
) delete tearline
;
2258 tearline
.reserve(90);
2259 while (tearline
.length
< 80) tearline
~= '/';
2261 tearline
[$-3] = ' ';
2264 doPutText(tearline
);
2265 // back to identifier, so we can pretend that no cursor movement happened
2266 gotoPos(curpos
+xpos
);
2269 int doJumpBracketLastDir
= 0; // for iterations
2271 void doJumpBracket () {
2272 if (repeatIteration
== 0) doJumpBracketLastDir
= 0;
2276 while (pos
>= 0 && pos
< gb
.textsize
) {
2278 //if (!isAnyTextChar(pos, false)) return;
2280 if (ch
== '(') { newpos
= drawHiBracket
!false(pos
, cy
, ch
, ')', 1); curdir
= 1; }
2281 else if (ch
== '{') { newpos
= drawHiBracket
!false(pos
, cy
, ch
, '}', 1, true); curdir
= 1; }
2282 else if (ch
== '[') { newpos
= drawHiBracket
!false(pos
, cy
, ch
, ']', 1); curdir
= 1; }
2283 else if (ch
== ')') { newpos
= drawHiBracket
!false(pos
, cy
, ch
, '(', -1); curdir
= -1; }
2284 else if (ch
== '}') { newpos
= drawHiBracket
!false(pos
, cy
, ch
, '{', -1, true); curdir
= -1; }
2285 else if (ch
== ']') { newpos
= drawHiBracket
!false(pos
, cy
, ch
, '[', -1); curdir
= -1; }
2287 if (doJumpBracketLastDir
== 0) return;
2288 pos
+= doJumpBracketLastDir
;
2291 if (doJumpBracketLastDir
!= 0 && curdir
== doJumpBracketLastDir
) break;
2292 if (doJumpBracketLastDir
== 0) { doJumpBracketLastDir
= curdir
; break; }
2293 pos
+= doJumpBracketLastDir
;
2295 if (newpos
>= 0) gotoPos(newpos
);
2300 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("Up", q
{ doUp(); });
2301 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-Up", q
{ doUp(true); });
2302 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-Up", q
{ doScrollUp(); });
2303 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-C-Up", q
{ doScrollUp(true); });
2304 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("Down", q
{ doDown(); });
2305 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-Down", q
{ doDown(true); });
2306 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-Down", q
{ doScrollDown(); });
2307 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-C-Down", q
{ doScrollDown(true); });
2309 @TEDRepeated mixin TEDImpl
!("C-]", q
{ doJumpBracket(); });
2311 @TEDRepeated mixin TEDImpl
!("Left", q
{ doLeft(); });
2312 @TEDRepeated mixin TEDImpl
!("S-Left", q
{ doLeft(true); });
2314 @TEDRepeated mixin TEDImpl
!("C-Left", q
{ doWordLeft(); });
2315 @TEDRepeated mixin TEDImpl
!("S-C-Left", q
{ doWordLeft(true); });
2316 @TEDRepeated mixin TEDImpl
!("Right", q
{ doRight(); });
2317 @TEDRepeated mixin TEDImpl
!("S-Right", q
{ doRight(true); });
2318 @TEDRepeated mixin TEDImpl
!("C-Right", q
{ doWordRight(); });
2319 @TEDRepeated mixin TEDImpl
!("S-C-Right", q
{ doWordRight(true); });
2321 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("PageUp", q
{ doPageUp(); });
2322 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-PageUp", q
{ doPageUp(true); });
2323 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-PageUp", q
{ doTextTop(); });
2324 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-C-PageUp", q
{ doTextTop(true); });
2325 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("PageDown", q
{ doPageDown(); });
2326 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-PageDown", q
{ doPageDown(true); });
2327 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-PageDown", q
{ doTextBottom(); });
2328 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-C-PageDown", q
{ doTextBottom(true); });
2329 @TEDRepeated mixin TEDImpl
!("Home", q
{ doHome(); });
2330 @TEDRepeated mixin TEDImpl
!("S-Home", q
{ doHome(true, true); });
2331 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-Home", q
{ doPageTop(); });
2332 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-C-Home", q
{ doPageTop(true); });
2333 @TEDRepeated mixin TEDImpl
!("End", q
{ doEnd(); });
2334 @TEDRepeated mixin TEDImpl
!("S-End", q
{ doEnd(true); });
2335 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-End", q
{ doPageBottom(); });
2336 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("S-C-End", q
{ doPageBottom(true); });
2338 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("Backspace", q
{ doBackspace(); });
2339 @TEDSingleOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("M-Backspace", "delete previous word", q
{ doDeleteWord(); });
2340 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("M-Backspace", "delete previous word or unindent", q
{ doBackByIndent(); });
2342 @TEDRepeated mixin TEDImpl
!("Delete", q
{ doDelete(); });
2343 mixin TEDImpl
!("C-Insert", "copy block to clipboard file, reset block mark", q
{ if (tempBlockFileName
.length
== 0) return; doBlockWrite(tempBlockFileName
); doBlockResetMark(); });
2345 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("Enter", q
{ doPutChar('\n'); });
2346 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("M-Enter", "split line without autoindenting", q
{ doLineSplit(false); });
2349 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("F2", "save file", q
{ saveFile(fullFileName
); });
2350 mixin TEDImpl
!("F3", "start/stop/reset block marking", q
{ doToggleBlockMarkMode(); });
2351 mixin TEDImpl
!("C-F3", "reset block mark", q
{ doBlockResetMark(); });
2352 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("F4", "search and relace text", q
{
2353 srrOptions
.inselenabled
= hasMarkedBlock
;
2354 srrOptions
.utfuck
= utfuck
;
2355 if (!dialogSearchReplace(hisman
, srrOptions
)) return;
2356 if (srrOptions
.type
== SearchReplaceOptions
.Type
.Normal
) { srrPlain(srrOptions
); return; }
2357 if (srrOptions
.type
== SearchReplaceOptions
.Type
.Regex
) { srrRegExp(srrOptions
); return; }
2358 dialogMessage
!"error"("Not Yet", "This S&R type is not supported yet!");
2360 @TEDEditOnly mixin TEDImpl
!("F5", "copy block", q
{ doBlockCopy(); });
2361 mixin TEDImpl
!("C-F5", "copy block to clipboard file", q
{ if (tempBlockFileName
.length
== 0) return; doBlockWrite(tempBlockFileName
); });
2362 @TEDEditOnly mixin TEDImpl
!("S-F5", "insert block from clipboard file", q
{ if (tempBlockFileName
.length
== 0) return; waitingInF5
= true; });
2363 @TEDEditOnly mixin TEDImpl
!("F6", "move block", q
{ doBlockMove(); });
2364 @TEDEditOnly mixin TEDImpl
!("F8", "delete block", q
{ doBlockDelete(); });
2366 mixin TEDImpl
!("C-A", "move to line start", q
{ doHome(); });
2367 mixin TEDImpl
!("C-E", "move to line end", q
{ doEnd(); });
2369 @TEDMultiOnly mixin TEDImpl
!("M-I", "jump to previous bookmark", q
{ doBookmarkJumpUp(); });
2370 @TEDMultiOnly mixin TEDImpl
!("M-J", "jump to next bookmark", q
{ doBookmarkJumpDown(); });
2371 @TEDMultiOnly mixin TEDImpl
!("M-K", "toggle bookmark", q
{ doBookmarkToggle(); });
2372 @TEDMultiOnly mixin TEDImpl
!("M-L", "goto line", q
{
2373 auto lnum
= dialogLineNumber(hisman
);
2374 if (lnum
> 0 && lnum
< linecount
) gotoXY
!true(curx
, lnum
-1); // vcenter
2377 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("M-C", "capitalize word", q
{
2379 processWordWith((char ch
) {
2380 if (first
) { first
= false; ch
= ch
.toupper
; }
2384 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("M-Q", "lowercase word", q
{ processWordWith((char ch
) => ch
.tolower
); });
2385 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("M-U", "uppercase word", q
{ processWordWith((char ch
) => ch
.toupper
); });
2387 @TEDMultiOnly mixin TEDImpl
!("M-S-L", "force center current line", q
{ makeCurLineVisibleCentered(true); });
2388 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-R", "continue incremental search, forward", q
{ incSearchDir
= 1; if (incSearchBuf
.length
== 0 && !incInputActive
) doStartIncSearch(1); else doNextIncSearch(); });
2389 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-U", "undo", q
{ doUndo(); });
2390 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("M-U", "undo", q
{ doUndo(); });
2391 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("M-S-U", "redo", q
{ doRedo(); });
2392 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-V", "continue incremental search, backward", q
{ incSearchDir
= -1; if (incSearchBuf
.length
== 0 && !incInputActive
) doStartIncSearch(-1); else doNextIncSearch(); });
2393 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-W", "remove previous word", q
{ doDeleteWord(); });
2394 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-Y", "remove current line", q
{ doKillLine(); });
2395 @TEDMultiOnly mixin TEDImpl
!("C-_", "start new incremental search, forward", q
{ doStartIncSearch(1); }); // ctrl+slash, actually
2396 @TEDMultiOnly mixin TEDImpl
!("C-\\", "start new incremental search, backward", q
{ doStartIncSearch(-1); });
2398 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("Tab", q
{ doPutText(" "); });
2399 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("M-Tab", "autocomplete word", q
{ doAutoComplete(); });
2400 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-Tab", "indent block", q
{ doIndentBlock(); });
2401 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-S-Tab", "unindent block", q
{ doUnindentBlock(); });
2403 mixin TEDImpl
!("M-S-c", "copy block to X11 selections (all three)", q
{ pasteToX11(); doBlockResetMark(); });
2405 @TEDMultiOnly mixin TEDImpl
!("M-E", "select codepage", q
{
2406 auto ncp
= dialogCodePage(utfuck ?
3 : codepage
);
2407 if (ncp
< 0) return;
2408 if (ncp
> CodePage
.max
) { utfuck
= true; return; }
2410 codepage
= cast(CodePage
)ncp
;
2414 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-K C-I", "indent block", q
{ doIndentBlock(); });
2415 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-K C-U", "unindent block", q
{ doUnindentBlock(); });
2416 @TEDEditOnly mixin TEDImpl
!("C-K C-E", "clear from cursor to EOL", q
{ doKillToEOL(); });
2417 @TEDMultiOnly @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-K Tab", "indent block", q
{ doIndentBlock(); });
2418 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-K M-Tab", "untabify", q
{ doUntabify(lc
.tabsize ? lc
.tabsize
: 2); }); // alt+tab: untabify
2419 @TEDEditOnly mixin TEDImpl
!("C-K C-space", "remove trailing spaces", q
{ doRemoveTailingSpaces(); });
2420 mixin TEDImpl
!("C-K C-T", /*"toggle \"visual tabs\" mode",*/ q
{ visualtabs
= !visualtabs
; });
2422 @TEDMultiOnly mixin TEDImpl
!("C-K C-B", q
{ doSetBlockStart(); });
2423 @TEDMultiOnly mixin TEDImpl
!("C-K C-K", q
{ doSetBlockEnd(); });
2425 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-K C-C", q
{ doBlockCopy(); });
2426 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-K C-M", q
{ doBlockMove(); });
2427 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-K C-Y", q
{ doBlockDelete(); });
2428 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-K C-H", q
{ doBlockResetMark(); });
2430 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-K Backspace", q
{ doBlockResetMark(); });
2432 mixin TEDImpl
!("C-K !", "toggle read-only mode", q
{ ttyBeep(); auto ro
= readonly
; readonly
= !ro
; fullDirty(); });
2434 @TEDEditOnly @TEDRepeated mixin TEDImpl
!("C-Q Tab", q
{ doPutChar('\t'); });
2435 mixin TEDImpl
!("C-Q C-U", "toggle utfuck mode", q
{ utfuck
= !utfuck
; }); // ^Q^U: switch utfuck mode
2436 mixin TEDImpl
!("C-Q 1", "switch to koi8", q
{ utfuck
= false; codepage
= CodePage
.koi8u
; fullDirty(); });
2437 mixin TEDImpl
!("C-Q 2", "switch to cp1251", q
{ utfuck
= false; codepage
= CodePage
.cp1251
; fullDirty(); });
2438 mixin TEDImpl
!("C-Q 3", "switch to cp866", q
{ utfuck
= false; codepage
= CodePage
.cp866
; fullDirty(); });
2439 mixin TEDImpl
!("C-Q C-B", "go to block start", q
{ if (hasMarkedBlock
) gotoPos
!true(bstart
); lastBGEnd
= false; });
2441 @TEDMultiOnly @TEDRepeated mixin TEDImpl
!("C-Q C-F", "incremental search current word", q
{
2443 if (!isWordChar(gb
[pos
])) return;
2444 // deactivate prompt
2445 if (incInputActive
) {
2446 incInputActive
= false;
2448 resetIncSearchPos();
2451 while (pos
> 0 && isWordChar(gb
[pos
-1])) --pos
;
2452 incSearchBuf
.length
= 0;
2453 incSearchBuf
.assumeSafeAppend
;
2454 while (pos
< gb
.textsize
&& isWordChar(gb
[pos
])) incSearchBuf
~= gb
[pos
++];
2460 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-Q C-H", "generate Vavoom function header comment", q
{ doGenVavoomCmt(); });
2461 // hack for idiotic terminal mapping
2462 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-Q Backspace", "generate Vavoom function header comment", q
{ doGenVavoomCmt(); });
2464 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-Q C-X C-H", "generate Vavoom region header comment", q
{ doGenVavoomRegionCmt(); });
2465 // hack for idiotic terminal mapping
2466 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-Q C-X Backspace", "generate Vavoom region header comment", q
{ doGenVavoomRegionCmt(); });
2468 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-Q C-M", "put comment tearline", q
{ doGenCmtTearLine(); });
2469 // hack for idiotic terminal mapping
2470 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-Q Enter", "put comment tearline", q
{ doGenCmtTearLine(); });
2472 mixin TEDImpl
!("C-Q C-K", "go to block end", q
{ if (hasMarkedBlock
) gotoPos
!true(bend
); lastBGEnd
= true; });
2473 mixin TEDImpl
!("C-Q C-T", "set tab size", q
{
2474 auto tsz
= dialogTabSize(hisman
, tabsize
);
2475 if (tsz
> 0 && tsz
<= 64) tabsize
= cast(ubyte)tsz
;
2478 @TEDMultiOnly mixin TEDImpl
!("C-Q C-X C-S", "toggle syntax highlighing", q
{ toggleHighlighting(); });
2480 @TEDMultiOnly @TEDROOnly @TEDRepeated mixin TEDImpl
!("Space", q
{ doPageDown(); });
2481 @TEDMultiOnly @TEDROOnly @TEDRepeated mixin TEDImpl
!("C-Space", q
{ doPageUp(); });
2483 @TEDRepeatChar mixin TEDImpl
!("C-P", "counter (*2 or direct number)", q
{ doCounterMode(); });
2484 @TEDRepeatChar mixin TEDImpl
!("C-]", "", q
{ doCounterMode(); });