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
.tuing
.ttyeditor
/*is aliced*/;
18 import std
.datetime
: SysTime
;
29 import iv
.egeditor
.editor
;
30 import iv
.egeditor
.highlighters
;
32 import iv
.tuing
.types
;
34 import iv
.tuing
.events
;
38 // ////////////////////////////////////////////////////////////////////////// //
39 private string
normalizedAbsolutePath (string path
) {
41 return buildNormalizedPath(absolutePath(path
));
45 // ////////////////////////////////////////////////////////////////////////// //
46 class TtyEditor
: EditorEngine
{
47 static struct SROptions
{
54 const(char)[] replace
;
61 // the following fields are relevant for "replacement continuation"
63 int mts
, mte
; // match start and end
72 bool closeGroup
= false; // close undo group?
73 Pike
.Capture
[64] caps
;
79 // ////////////////////////////////////////////////////////////////////////// //
80 public enum TextBG
= TtyRgb2Color
!(0x3a, 0x3a, 0x3a); // 237
82 public enum TextColor
= XtColorFB
!(TtyRgb2Color
!(0xd0, 0xd0, 0xd0), TextBG
); // 252,237
83 public enum TextKillColor
= XtColorFB
!(TtyRgb2Color
!(0xe0, 0xe0, 0xe0), TextBG
); // 252,237
84 public enum BadColor
= XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0x54), TtyRgb2Color
!(0xb2, 0x18, 0x18)); // 11,1
85 //public enum TrailSpaceColor = XtColorFB!(TtyRgb2Color!(0xff, 0xff, 0x00), TtyRgb2Color!(0x00, 0x00, 0x87)); // 226,18
86 public enum TrailSpaceColor
= XtColorFB
!(TtyRgb2Color
!(0x6c, 0x6c, 0x6c), TtyRgb2Color
!(0x26, 0x26, 0x26)); // 242,235
87 public enum BlockColor
= XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0xff), TtyRgb2Color
!(0x00, 0x5f, 0xff)); // 15,27
88 public enum BookmarkColor
= XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0xff), TtyRgb2Color
!(0x87, 0x00, 0xd7)); // 15,92
89 public enum BracketColor
= XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0x54), TtyRgb2Color
!(0x00, 0x00, 0x00)); // 11,0
90 public enum IncSearchColor
= XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0x00), TtyRgb2Color
!(0xd7, 0x00, 0x00)); // 226,160
92 public enum UtfuckedColor
= XtColorFB
!(TtyRgb2Color
!(0x6c, 0x6c, 0x6c), TtyRgb2Color
!(0x26, 0x26, 0x26)); // 242,235
94 public enum VLineColor
= XtColorFB
!(TtyRgb2Color
!(0x60, 0x60, 0x60), TextBG
); // 252,237
96 //public enum TabColor = XtColorFB!(TtyRgb2Color!(0x00, 0x00, 0x80), TextBG);
99 // ////////////////////////////////////////////////////////////////////////// //
100 public uint hiColor() (in auto ref GapBuffer
.HighState hs
) nothrow @safe @nogc {
102 case HiNone
: return XtColorFB
!(TtyRgb2Color
!(0xb2, 0xb2, 0xb2), TtyRgb2Color
!(0x00, 0x00, 0x00)); // 7,0
103 case HiText
: return TextColor
;
105 case HiCommentOneLine
:
107 return XtColorFB
!(TtyRgb2Color
!(0xb2, 0x68, 0x18), TextBG
); // 3,237
110 return XtColorFB
!(TtyRgb2Color
!(0x18, 0xb2, 0x18), TextBG
); // 2,237
113 return XtColorFB
!(TtyRgb2Color
!(0x54, 0xff, 0xff), TextBG
); // 14,237
115 return XtColorFB
!(TtyRgb2Color
!(0x54, 0xff, 0x54), TextBG
); // 10,237; green
116 //return XtColorFB!(TtyRgb2Color!(0x18, 0xb2, 0x18), TextBG); // 2,237
117 //return XtColorFB!(TtyRgb2Color!(0x54, 0xff, 0xff), TextBG); // 14,237
124 return XtColorFB
!(TtyRgb2Color
!(0x18, 0xb2, 0xb2), TextBG
); // 6,237
125 case HiStringSpecial
:
126 case HiSQStringSpecial
:
127 return XtColorFB
!(TtyRgb2Color
!(0x54, 0xff, 0xff), TextBG
); // 14,237
128 //return XtColorFB!(TtyRgb2Color!(0x18, 0xb2, 0x18), TextBG); // 2,237
130 case HiKeyword
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0x54), TextBG
); // 11,237
131 case HiKeywordHi
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0xff), TextBG
); // 202,237
132 case HiBuiltin
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0x5f, 0x00), TextBG
); // 202,237
133 case HiType
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0xaf, 0x00), TextBG
); // 214,237
134 case HiSpecial
: return XtColorFB
!(TtyRgb2Color
!(0x54, 0xff, 0x54), TextBG
); // 10,237; green
135 case HiInternal
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0x54, 0x54), TextBG
); // 9,237; red
136 case HiPunct
: return XtColorFB
!(TtyRgb2Color
!(0x54, 0xff, 0xff), TextBG
); // 14,237
137 case HiSemi
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0x00, 0xff), TextBG
); // 201,237
138 case HiUDA
: return XtColorFB
!(TtyRgb2Color
!(0x00, 0x87, 0xff), TextBG
); // 33,237
139 case HiAliced
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0x5f, 0x00), TextBG
); // 202,237
140 case HiPreprocessor
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0x54, 0x54), TextBG
); // 9,237; red
142 case HiRegExp
: return XtColorFB
!(TtyRgb2Color
!(0xff, 0x5f, 0x00), TextBG
); // 202,237
144 case HiToDoOpen
: // [.]
145 return XtColorFB
!(TtyRgb2Color
!(0xff, 0x00, 0xff), TextBG
);
146 case HiToDoUnsure
: // [?]
147 return XtColorFB
!(TtyRgb2Color
!(0xc0, 0x00, 0xc0), TextBG
);
148 case HiToDoUrgent
: // [!]
149 return XtColorFB
!(TtyRgb2Color
!(0xff, 0x00, 0x00), TextBG
);
150 case HiToDoSemi
: // [+]
151 return XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0x00), TextBG
);
152 case HiToDoDone
: // [*]
153 return XtColorFB
!(TtyRgb2Color
!(0x00, 0xa0, 0x00), TextBG
);
154 case HiToDoDont
: // [-]
155 return XtColorFB
!(TtyRgb2Color
!(0x90, 0x90, 0x00), TextBG
);
157 default: assert(0, "wtf?!");
161 // ////////////////////////////////////////////////////////////////////////// //
162 // new higlighter instance for the file with the given extension
163 public __gshared EditorHL
getHiglighterFor (const(char)[] ext
, const(char)[] fullname
) {
164 if (ext
.strEquCI(".d")) {
165 __gshared EdHiTokensD toksd
;
166 if (toksd
is null) toksd
= new EdHiTokensD();
167 return new EditorHLExt(toksd
);
169 if (ext
.strEquCI(".js") || ext
.strEquCI(".jsm")) {
170 __gshared EdHiTokensJS toksjs
;
171 if (toksjs
is null) toksjs
= new EdHiTokensJS();
172 return new EditorHLExt(toksjs
);
174 if (ext
.strEquCI(".c") || ext
.strEquCI(".cpp") || ext
.strEquCI(".h") || ext
.strEquCI(".hpp")) {
175 __gshared EdHiTokensC toksc
;
176 if (toksc
is null) toksc
= new EdHiTokensC();
177 return new EditorHLExt(toksc
);
179 if (ext
.strEquCI(".sh") || ext
.strEquCI(".profile")) {
180 __gshared EdHiTokensShell tokssh
;
181 if (tokssh
is null) tokssh
= new EdHiTokensShell();
182 return new EditorHLExt(tokssh
);
184 auto bnpos
= fullname
.length
;
185 while (bnpos
> 0 && fullname
.ptr
[bnpos
-1] != '/') --bnpos
;
186 auto name
= fullname
[bnpos
..$];
187 if (name
== "TODO") return new EditorHLTODO();
188 if (name
== "COMMIT_EDITMSG") return new EditorHLGitCommit();
193 enum TEDSingleOnly
; // only for single-line mode
194 enum TEDMultiOnly
; // only for multiline mode
195 enum TEDEditOnly
; // only for non-readonly mode
196 enum TEDROOnly
; // only for readonly mode
198 static struct TEDKey
{ string key
; string help
; } // UDA
200 static string
TEDImplX(string key
, string help
, string code
, usize ln
) () {
201 static assert(key
.length
> 0, "wtf?!");
202 static assert(code
.length
> 0, "wtf?!");
203 string res
= "@TEDKey("~key
.stringof
~", "~help
.stringof
~") void _ted_";
205 while (pos
< key
.length
) {
206 char ch
= key
[pos
++];
207 if (key
.length
-pos
> 0 && key
[pos
] == '-') {
208 if (ch
== 'C' || ch
== 'c') { ++pos
; res
~= "Ctrl"; continue; }
209 if (ch
== 'M' || ch
== 'm') { ++pos
; res
~= "Alt"; continue; }
210 if (ch
== 'S' || ch
== 's') { ++pos
; res
~= "Shift"; continue; }
212 if (ch
== '^') { res
~= "Ctrl"; continue; }
213 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
214 if ((ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') || ch
== '_') res
~= ch
; else res
~= '_';
217 res
~= " () {"~code
~"}";
221 mixin template TEDImpl(string key
, string help
, string code
, usize ln
=__LINE__
) {
222 mixin(TEDImplX
!(key
, help
, code
, ln
));
225 mixin template TEDImpl(string key
, string code
, usize ln
=__LINE__
) {
226 mixin(TEDImplX
!(key
, "", code
, ln
));
230 static struct TextHighlight
{
233 @property bool valid () const pure nothrow @safe @nogc { return (count
> 0); }
235 TextHighlight texthi
;
237 TtyEvent
[32] comboBuf
;
238 int comboCount
; // number of items in `comboBuf`
243 TtyEditor mPromptInput
; // input line for a prompt; lazy creation
245 char[128] mPromptPrompt
; // lol
248 final void promptDeactivate () {
250 mPromptActive
= false;
251 fullDirty(); // just in case
255 final void promptNoKillText () {
256 if (mPromptInput
!is null) mPromptInput
.killTextOnChar
= false;
259 final void promptActivate (const(char)[] prompt
=null, const(char)[] text
=null) {
260 if (mPromptInput
is null) {
261 mPromptInput
= new TtyEditor(0, 0, 10, 1, true); // single-lined
264 bool addDotDotDot
= false;
268 if (prompt
.length
> winw
-8) {
270 prompt
= prompt
[$-(winw
-8)..$];
273 if (prompt
.length
> mPromptPrompt
.length
) addDotDotDot
= true;
277 mPromptPrompt
[0..3] = '.';
278 if (prompt
.length
> mPromptPrompt
.length
-3) prompt
= prompt
[$-(mPromptPrompt
.length
-3)..$];
279 mPromptPrompt
[3..3+prompt
.length
] = prompt
[];
280 mPromptLen
= cast(int)prompt
.length
+3;
282 mPromptPrompt
[0..prompt
.length
] = prompt
[];
283 mPromptLen
= cast(int)prompt
.length
;
286 mPromptInput
.moveResize(winx
+mPromptLen
+2, winy
-1, winw
-mPromptLen
-2, 1);
287 mPromptInput
.clear();
289 mPromptInput
.doPutText(text
);
290 mPromptInput
.clearUndo();
293 mPromptInput
.killTextOnChar
= true;
294 mPromptInput
.clrBlock
= XtColorFB
!(TtyRgb2Color
!(0xff, 0xff, 0xff), TtyRgb2Color
!(0x00, 0x5f, 0xff));
295 mPromptInput
.clrText
= XtColorFB
!(TtyRgb2Color
!(0x00, 0x00, 0x00), TtyRgb2Color
!(0xff, 0x7f, 0x00));
296 mPromptInput
.clrTextUnchanged
= XtColorFB
!(TtyRgb2Color
!(0x00, 0x00, 0x00), TtyRgb2Color
!(0xcf, 0x4f, 0x00));
298 mPromptInput
.utfuck
= utfuck
;
299 mPromptInput
.codepage
= codepage
;
300 mPromptActive
= true;
301 fullDirty(); // just in case
304 final bool promptProcessKey (TtyEvent key
, scope void delegate (EditorEngine ed
) onChange
=null) {
305 if (!mPromptActive
) return false;
306 auto lastCC
= mPromptInput
.bufferCC
;
307 auto res
= mPromptInput
.processKey(key
);
308 if (lastCC
!= mPromptInput
.bufferCC
&& onChange
!is null) onChange(mPromptInput
);
313 int incSearchDir
; // -1; 0; 1
314 char[] incSearchBuf
; // will be actively reused, don't expose
315 int incSearchHitPos
= -1;
316 int incSearchHitLen
= -1;
319 // autocompletion buffers
320 const(char)[][256] aclist
; // autocompletion tokens
321 uint acused
= 0; // number of found tokens
322 char[] acbuffer
; // will be actively reused, don't expose
326 string tempBlockFileName
;
328 // save this, so we can check on saving
330 long fileDiskSize
= -1; // <0: modtime is invalid
331 bool dontSetCursor
; // true: don't gotoxy to cursor position
332 // colors; valid for singleline control
333 uint clrBlock
, clrText
, clrTextUnchanged
;
335 SROptions srrOptions
;
336 bool hideStatus
= false;
337 bool hideSBar
= false; // hide scrollbar
338 bool editorlocked
= false; // when editor send some message that needs action reply, it locks itself
341 this (int x0
, int y0
, int w
, int h
, bool asinglesine
=false) {
342 // prompt should be created only for multiline editors
343 super(x0
, y0
, w
, h
, null, asinglesine
);
344 //srrOptions.type = SROptions.Type.Regex;
345 srrOptions
.casesens
= true;
346 addEventListener(this, (EventEditorReplyGotoLine evt
) {
347 if (evt
.line
> 0 && evt
.line
<= linecount
) gotoXY
!true(curx
, evt
.line
-1); // vcenter
349 addEventListener(this, (EventEditorReplyCodePage evt
) {
352 if (ncp
> CodePage
.max
) { utfuck
= true; return; }
354 codepage
= cast(CodePage
)ncp
;
356 //(new EventEditorMessage(this, "codepage set")).post;
358 addEventListener(this, (EventEditorReplyTabSize evt
) {
359 auto tsz
= evt
.tabsize
;
360 if (tsz
> 0 && tsz
<= 64) tabsize
= cast(ubyte)tsz
;
361 //import std.conv : to;
362 //(new EventEditorMessage(this, "tabsize set to "~tabsize.to!string)).post;
366 // call this after setting `fullFileName`
367 void setupHighlighter () {
368 auto ep
= fullFileName
.length
;
369 while (ep
> 0 && fullFileName
.ptr
[ep
-1] != '/' && fullFileName
.ptr
[ep
-1] != '.') --ep
;
370 if (ep
< 1 || fullFileName
.ptr
[ep
-1] != '.') {
371 attachHiglighter(getHiglighterFor("", fullFileName
));
373 attachHiglighter(getHiglighterFor(fullFileName
[ep
-1..$], fullFileName
));
377 final void getDiskFileInfo () {
378 import std
.file
: getSize
, timeLastModified
;
379 if (fullFileName
.length
) {
380 fileDiskSize
= getSize(fullFileName
);
381 fileModTime
= timeLastModified(fullFileName
);
387 // return `true` if file was changed
388 final bool wasDiskFileChanged () {
389 import std
.file
: exists
, getSize
, timeLastModified
;
390 if (fullFileName
.length
&& fileDiskSize
>= 0) {
391 if (!fullFileName
.exists
) return false;
392 auto sz
= getSize(fullFileName
);
393 if (sz
!= fileDiskSize
) return true;
394 SysTime modtime
= timeLastModified(fullFileName
);
395 if (modtime
!= fileModTime
) return true;
400 override void loadFile (const(char)[] fname
) {
402 fullFileName
= normalizedAbsolutePath(fname
.idup
);
403 super.loadFile(VFile(fullFileName
));
407 final void checkDiskAndReloadPrompt () {
408 if (fullFileName
.length
== 0) return;
409 if (wasDiskFileChanged
) {
410 auto oldlk
= editorlocked
;
411 addEventListener(this, (EventEditorReplyReloadModified evt
) {
412 editorlocked
= oldlk
;
414 int rx
= cx
, ry
= cy
;
416 super.loadFile(VFile(fullFileName
));
421 makeCurLineVisibleCentered();
425 (new EventEditorQueryReloadModified(this)).post
;
429 final void forceSaveFile () {
430 if (fullFileName
.length
== 0) return;
431 super.saveFile(fullFileName
);
435 override void saveFile (const(char)[] fname
=null) {
437 auto nfn
= normalizedAbsolutePath(fname
.idup
);
438 if (fname
!= fullFileName
) {
439 super.saveFile(VFile(nfn
, "w"));
445 if (fullFileName
.length
== 0) return;
446 if (wasDiskFileChanged
) {
447 auto oldlk
= editorlocked
;
448 addEventListener(this, (EventEditorReplyOverwriteModified evt
) {
449 editorlocked
= oldlk
;
451 super.saveFile(fullFileName
);
456 (new EventEditorQueryOverwriteModified(this)).post
;
460 protected override void willBeDeleted (int pos
, int len
, int eolcount
) {
461 if (len
> 0) { resetIncSearchPos(); resetHighlight(); }
462 super.willBeDeleted(pos
, len
, eolcount
);
465 protected override void willBeInserted (int pos
, int len
, int eolcount
) {
466 if (len
> 0) { resetIncSearchPos(); resetHighlight(); }
467 super.willBeInserted(pos
, len
, eolcount
);
470 final void resetIncSearchPos (bool resethi
=true) nothrow @safe @nogc {
471 if (incSearchHitPos
>= 0) {
472 markLinesDirty(gb
.pos2line(incSearchHitPos
), 1);
473 incSearchHitPos
= -1;
474 incSearchHitLen
= -1;
475 if (resethi
) resetHighlight();
479 final void doStartIncSearch (int sdir
=0) {
482 incInputActive
= true;
483 incSearchDir
= (sdir ? sdir
: incSearchDir ? incSearchDir
: 1);
484 promptActivate("incsearch", incSearchBuf
);
486 incSearchBuf.length = 0;
487 incSearchBuf.assumeSafeAppend;
491 final void doNextIncSearch (bool domove
=true) {
492 if (incSearchDir
== 0 || incSearchBuf
.length
== 0) {
498 //TODO: use `memr?chr()` here?
501 if (incSearchBuf
.ptr
[0] != '/') {
503 int pos
= curpos
+(domove ? incSearchDir
: 0);
505 if (incSearchDir
< 0) {
506 mt
= findTextPlainBack(incSearchBuf
, 0, pos
, /*words:*/false, /*caseSens:*/true);
508 mt
= findTextPlain(incSearchBuf
, pos
, textsize
, /*words:*/false, /*caseSens:*/true);
511 incSearchHitPos
= mt
.s
;
512 incSearchHitLen
= mt
.e
-mt
.s
;
513 texthi
.pos
= incSearchHitPos
;
514 texthi
.count
= incSearchHitLen
;
515 texthi
.clr
= IncSearchColor
;
517 } else if (incSearchBuf
.length
> 2 && incSearchBuf
[$-1] == '/') {
519 import std
.utf
: byChar
;
520 auto re
= RegExp
.create(incSearchBuf
[1..$-1].byChar
, SRFlags
.Multiline
);
521 if (!re
.valid
) { ttyBeep
; return; }
522 Pike
.Capture
[2] caps
;
524 if (incSearchDir
> 0) {
525 found
= findTextRegExp(re
, curpos
+(domove ?
1 : 0), textsize
, caps
);
527 found
= findTextRegExpBack(re
, 0, curpos
+(domove ?
-1 : 0), caps
);
530 // something was found
531 incSearchHitPos
= caps
[0].s
;
532 incSearchHitLen
= caps
[0].e
-caps
[0].s
;
533 texthi
.pos
= incSearchHitPos
;
534 texthi
.count
= incSearchHitLen
;
535 texthi
.clr
= IncSearchColor
;
538 if (incSearchHitPos
>= 0 && incSearchHitLen
> 0) {
540 gb
.pos2xy(incSearchHitPos
, cx
, cy
);
541 makeCurLineVisibleCentered();
542 markRangeDirty(incSearchHitPos
, incSearchHitLen
);
546 final void drawScrollBar () {
547 if (winx
== 0) return; // it won't be visible anyway
548 if (singleline || hideSBar
) return;
549 auto win
= XtWindow(winx
-1, winy
-(hideStatus ?
0 : 1), 1, winh
+(hideStatus ?
0 : 1));
550 if (win
.height
< 1) return; // it won't be visible anyway
551 win
.fg
= TtyRgb2Color
!(0x00, 0x00, 0x00);
552 win
.bg
= (termType
!= TermType
.linux ? TtyRgb2Color
!(0x00, 0x5f, 0xaf) : TtyRgb2Color
!(0x00, 0x5f, 0xcf));
554 int botline
= topline
+winh
-1;
555 if (botline
>= linecount
-1 || linecount
== 0) {
558 filled
= (win
.height
-1)*botline
/linecount
;
559 if (filled
== win
.height
-1 && mTopLine
+winh
< linecount
) --filled
;
561 foreach (immutable y
; 0..win
.height
) win
.writeCharsAt
!true(0, y
, 1, (y
<= filled ?
' ' : 'a'));
564 public override void drawCursor () {
565 if (dontSetCursor
) return;
566 // draw prompt if it is active
567 if (mPromptActive
) { mPromptInput
.drawCursor(); return; }
569 gb
.pos2xyVT(curpos
, rx
, ry
);
570 XtWindow(winx
, winy
, winw
, winh
).gotoXY(rx
-mXOfs
, ry
-topline
);
573 public override void drawStatus () {
574 if (singleline || hideStatus
) return;
575 auto win
= XtWindow(winx
, winy
-1, winw
, 1);
576 win
.fg
= TtyRgb2Color
!(0x00, 0x00, 0x00);
577 win
.bg
= TtyRgb2Color
!(0xb2, 0xb2, 0xb2);
578 win
.writeCharsAt(0, 0, win
.width
, ' ');
579 import core
.stdc
.stdio
: snprintf
;
581 auto c
= cast(uint)gb
[cp
];
582 char[512] buf
= void;
586 gb
.pos2xyVT(cp
, sx
, ry
);
589 auto len
= snprintf(buf
.ptr
, buf
.length
, " %c[%04u:%04u : 0x%08x : %u : %u] [ 0x%08x : 0x%08x ] 0x%02x %3u",
590 (textChanged ?
'*' : ' '), sx
+0, cy
+1, cp
, topline
, mXOfs
, bstart
, bend
, c
, c
);
591 if (len
> winw
) len
= winw
;
592 win
.writeStrAt(0, 0, buf
[0..len
]);
594 dchar dch
= dcharAt(cp
);
595 if (dch
> dchar.max
) dch
= 0;
596 auto len
= snprintf(buf
.ptr
, buf
.length
, " %c[%04u:%04u : 0x%08x : %u : %u] [ 0x%08x : 0x%08x ] 0x%02x %3u U%04X",
597 (textChanged ?
'*' : ' '), sx
+0, cy
+1, cp
, topline
, mXOfs
, bstart
, bend
, c
, c
, cast(uint)dch
);
598 if (len
> winw
) len
= winw
;
599 win
.writeStrAt(0, 0, buf
[0..len
]);
603 if (readonly
) win
.writeCharsAt(0, 0, 1, '/'); else win
.writeCharsAt(0, 0, 1, 'U');
604 } else if (readonly
) {
606 win
.writeCharsAt(0, 0, 1, 'R');
610 // highlighting is done, other housekeeping is done, only draw
611 // lidx is always valid
612 // must repaint the whole line
613 // use `winXXX` vars to know window dimensions
614 //FIXME: clean this up!
615 public override void drawLine (int lidx
, int yofs
, int xskip
) {
616 immutable vt
= visualtabs
;
617 immutable tabsz
= gb
.tabsize
;
618 auto win
= XtWindow(winx
, winy
, winw
, winh
);
619 auto pos
= gb
.line2pos(lidx
);
622 auto ts
= gb
.textsize
;
623 bool inBlock
= (bstart
< bend
&& pos
>= bstart
&& pos
< bend
);
624 bool bookmarked
= isLineBookmarked(lidx
);
626 win
.color
= (clrText ? clrText
: TextColor
);
627 if (killTextOnChar
) win
.color
= (clrTextUnchanged ? clrTextUnchanged
: TextKillColor
);
628 if (inBlock
) win
.color
= (clrBlock ? clrBlock
: BlockColor
);
630 win
.color
= TextColor
;
631 if (bookmarked
) win
.color
= BookmarkColor
; else if (inBlock
) win
.color
= BlockColor
;
633 // if we have no highlighter, check for trailing spaces explicitly
634 bool hasHL
= (hl
!is null); // do we have highlighter (just a cache)
635 // look for trailing spaces even if we have a highlighter
636 int trspos
= gb
.line2pos(lidx
+1); // this is where trailing spaces starts (will)
637 if (!singleline
) while (trspos
> pos
&& gb
[trspos
-1] <= ' ') --trspos
;
638 bool utfucked
= utfuck
;
639 int bs
= bstart
, be
= bend
;
640 auto sltextClr
= (singleline ?
642 (clrTextUnchanged ? clrTextUnchanged
: TextKillColor
) :
643 (clrText ? clrText
: TextColor
)) :
645 auto blkClr
= (singleline ?
(clrBlock ? clrBlock
: BlockColor
) : BlockColor
);
647 inBlock
= (bs
< be
&& pos
>= bs
&& pos
< be
);
650 if (!killTextOnChar
&& !singleline
) win
.color
= (hasHL ?
hiColor(gb
.hi(pos
-1)) : TextColor
); else win
.color
= sltextClr
;
653 if (pos
-1 >= trspos
) {
654 if (ch
!= '\t') ch
= '.';
655 if (!killTextOnChar
&& !singleline
) win
.color
= TrailSpaceColor
; else win
.color
= sltextClr
;
656 } else if (ch
< ' ' || ch
== 127) {
657 if (!killTextOnChar
&& !singleline
) win
.color
= BadColor
; else win
.color
= sltextClr
;
659 auto hs
= gb
.hi(pos
-1);
660 if (!killTextOnChar
&& !singleline
) win
.color
= hiColor(hs
); else win
.color
= sltextClr
;
664 if (pos
-1 >= trspos
) {
665 if (ch
!= '\t') ch
= '.';
666 if (!killTextOnChar
&& !singleline
) win
.color
= TrailSpaceColor
; else win
.color
= sltextClr
;
667 } else if (ch
< ' ' || ch
== 127) {
668 if (!killTextOnChar
&& !singleline
) win
.color
= BadColor
; else win
.color
= sltextClr
;
670 win
.color
= sltextClr
;
673 if (!killTextOnChar
&& !singleline
) {
674 if (bookmarked
) win
.color
= BookmarkColor
; else if (inBlock
) win
.color
= blkClr
;
676 if (inBlock
) win
.color
= blkClr
; else win
.color
= sltextClr
;
678 if (ch
== '\n') break;
680 if (vt
&& ch
== '\t') {
681 int ex
= ((x
+tabsz
)/tabsz
)*tabsz
;
685 win
.writeCharsAt(x
, y
, 1, '<');
686 win
.writeCharsAt(x
+1, y
, sz
-2, '-');
687 win
.writeCharsAt(ex
-1, y
, 1, '>');
689 win
.writeCharsAt(x
, y
, 1, '\t');
692 x
= ex
-1; // compensate the following increment
693 } else if (!utfucked
) {
694 if (x
>= 0) win
.writeCharsAt(x
, y
, 1, recodeCharFrom(ch
));
699 dchar dch
= dcharAt(pos
);
700 if (dch
> dchar.max || dch
== 0xFFFD) {
702 scope(exit
) win
.color
= oc
;
703 if (!inBlock
) win
.color
= UtfuckedColor
;
704 win
.writeCharsAt
!true(x
, y
, 1, '\x7e'); // dot
706 win
.writeCharsAt(x
, y
, 1, uni2koi(dch
));
709 pos
+= gb
.utfuckLenAt(pos
);
711 if (++x
>= winw
) return;
714 if (x
>= winw
) return;
717 win
.color
= TextColor
;
718 if (!killTextOnChar
&& !singleline
) {
719 if (bookmarked
) win
.color
= BookmarkColor
; else win
.color
= sltextClr
;
721 win
.color
= sltextClr
;
724 win
.writeCharsAt(x
, y
, winw
-x
, ' ');
728 // use `winXXX` vars to know window dimensions
729 public override void drawEmptyLine (int yofs
) {
730 auto win
= XtWindow(winx
, winy
, winw
, winh
);
732 win
.color
= (clrText ? clrText
: TextColor
);
733 if (killTextOnChar
) win
.color
= (clrTextUnchanged ? clrTextUnchanged
: TextKillColor
);
735 win
.color
= TextColor
;
737 win
.writeCharsAt(0, yofs
, winw
, ' ');
740 public override void drawPageBegin () {
743 // check if line has only spaces (or comments, use highlighter) to begin/end (determined by dir)
744 final bool lineHasOnlySpaces (int pos
, int dir
) {
745 if (dir
== 0) return false;
746 dir
= (dir
< 0 ?
-1 : 1);
748 if (ts
== 0) return true; // wow, rare case
749 if (pos
< 0) { if (dir
< 0) return true; pos
= 0; }
750 if (pos
>= ts
) { if (dir
> 0) return true; pos
= ts
-1; }
751 while (pos
>= 0 && pos
< ts
) {
753 if (ch
== '\n') break;
755 // nonspace, check highlighting, if any
757 auto lidx
= gb
.pos2line(pos
);
758 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
759 if (!hiIsComment(gb
.hi(pos
))) return false;
760 // ok, it is comment, it's the same as whitespace
770 // always starts at BOL
771 final int lineFindFirstNonSpace (int pos
) {
773 if (ts
== 0) return 0; // wow, rare case
774 pos
= gb
.line2pos(gb
.pos2line(pos
));
777 if (ch
== '\n') break;
779 // nonspace, check highlighting, if any
781 auto lidx
= gb
.pos2line(pos
);
782 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
783 if (!hiIsComment(gb
.hi(pos
))) return pos
;
784 // ok, it is comment, it's the same as whitespace
794 final void drawHiBracket (int pos
, int lidx
, char bch
, char ech
, int dir
, bool drawline
=false) {
795 enum LineScanPages
= 8;
797 auto ts
= gb
.textsize
;
799 int toplimit
= (drawline ? mTopLine
-winh
*(LineScanPages
-1) : mTopLine
);
800 int botlimit
= (drawline ? mTopLine
+winh
*LineScanPages
: mTopLine
+winh
);
802 while (pos
>= 0 && pos
< ts
) {
806 if (lidx
< toplimit || lidx
>= botlimit
) return;
810 if (isAnyTextChar(pos
, (dir
> 0))) {
813 } else if (ch
== ech
) {
816 gb
.pos2xyVT(pos
, rx
, ry
);
817 if (rx
>= mXOfs || rx
< mXOfs
+winw
) {
818 if (ry
>= mTopLine
&& ry
< mTopLine
+winh
) {
819 auto win
= XtWindow(winx
, winy
, winw
, winh
);
820 win
.color
= BracketColor
;
821 win
.writeCharsAt(rx
-mXOfs
, ry
-mTopLine
, 1, ech
);
822 markLinesDirty(ry
, 1);
824 // draw line with opening bracket if it is out of screen
825 if (drawline
&& dir
< 0 && ry
< mTopLine
) {
828 while (ls
> 0 && gb
[ls
-1] != '\n') --ls
;
829 // skip leading spaces
830 while (ls
< pos
&& gb
[ls
] <= ' ') ++ls
;
833 while (le
< ts
&& gb
[le
] != '\n') ++le
;
834 // remove trailing spaces
835 while (le
> pos
&& gb
[le
-1] <= ' ') --le
;
837 auto win
= XtWindow(winx
+1, winy
-1, winw
-1, 1);
838 win
.color
= XtColorFB
!(TtyRgb2Color
!(0x40, 0x40, 0x40), TtyRgb2Color
!(0xb2, 0xb2, 0xb2)); // 0,7
839 win
.writeCharsAt(0, 0, winw
, ' ');
841 while (x
< winw
&& ls
< le
) {
842 win
.writeCharsAt(x
, 0, 1, gb
.utfuckAt(ls
));
843 ls
+= gb
.utfuckLenAt(ls
);
849 // draw vertical line
853 // has some text after the bracket on the starting line?
854 //if (!lineHasOnlySpaces(stpos+1, 1)) return; // has text, can't draw
855 // find first non-space at the starting line
856 ls
= lineFindFirstNonSpace(stpos
);
857 } else if (dir
< 0) {
859 gb
.pos2xyVT(stpos
, rx
, ry
);
860 ls
= lineFindFirstNonSpace(pos
);
862 gb
.pos2xyVT(ls
, stx
, sty
);
864 markLinesDirtySE(sty
+1, ry
-1);
869 auto win
= XtWindow(winx
, winy
, winw
, winh
);
870 win
.color
= VLineColor
;
871 win
.vline(stx
, sty
+1, ry
-sty
-1);
883 // `fullreset` is false: keep insearch highlighting
884 protected final void resetHighlight (bool fullreset
=true) nothrow @safe @nogc {
890 texthi
.pos
= incSearchHitPos
;
891 texthi
.count
= incSearchHitLen
;
892 texthi
.clr
= IncSearchColor
;
896 protected final void drawPartHighlight (int pos
, int count
, uint clr
) {
897 if (pos
>= 0 && count
> 0 && pos
< gb
.textsize
) {
899 gb
.pos2xyVT(pos
, rx
, ry
);
900 //auto oldclr = xtGetColor;
901 //scope(exit) win.color = oldclr;
902 if (ry
>= topline
&& ry
< topline
+winh
) {
904 auto win
= XtWindow(winx
, winy
, winw
, winh
);
906 if (count
> gb
.textsize
-pos
) count
= gb
.textsize
-pos
;
911 foreach (immutable _
; 0..count
) {
912 if (rx
>= 0 && rx
< winw
) win
.writeCharsAt(rx
, ry
, 1, recodeCharFrom(gb
[pos
++]));
918 if (rx
>= winw
) break;
920 dchar dch
= dcharAt(pos
);
921 if (dch
> dchar.max || dch
== 0xFFFD) {
922 //auto oc = xtGetColor();
923 //win.color = UtfuckedColor;
924 win
.writeCharsAt
!true(rx
, ry
, 1, '\x7e'); // dot
926 win
.writeCharsAt(rx
, ry
, 1, uni2koi(dch
));
930 pos
+= gb
.utfuckLenAt(pos
);
937 public override void drawPageMisc () {
939 if (isAnyTextChar(pos
, false)) {
941 if (ch
== '(') drawHiBracket(pos
, cy
, ch
, ')', 1);
942 else if (ch
== '{') drawHiBracket(pos
, cy
, ch
, '}', 1, true);
943 else if (ch
== '[') drawHiBracket(pos
, cy
, ch
, ']', 1);
944 else if (ch
== ')') drawHiBracket(pos
, cy
, ch
, '(', -1);
945 else if (ch
== '}') drawHiBracket(pos
, cy
, ch
, '{', -1, true);
946 else if (ch
== ']') drawHiBracket(pos
, cy
, ch
, '[', -1);
948 if (texthi
.valid
) drawPartHighlight(texthi
.pos
, texthi
.count
, texthi
.clr
);
952 public override void drawPageEnd () {
954 auto win
= XtWindow(winx
, winy
-(singleline || hideStatus ?
0 : 1), winw
, 1);
955 win
.color
= mPromptInput
.clrText
;
956 win
.writeCharsAt(0, 0, winw
, ' ');
957 win
.writeStrAt(0, 0, mPromptPrompt
[0..mPromptLen
]);
958 win
.writeCharsAt(mPromptLen
, 0, 1, ':');
959 mPromptInput
.fullDirty(); // force redraw
960 mPromptInput
.drawPage();
965 //TODO: fix cx if current line was changed
966 final void doUntabify (int tabSize
=2) {
967 if (mReadOnly || gb
.textsize
== 0) return;
968 if (tabSize
< 1 || tabSize
> 255) return;
970 auto ts
= gb
.textsize
;
972 while (pos
< ts
&& gb
[pos
] != '\t') {
973 if (gb
[pos
] == '\n') curx
= 0; else ++curx
;
976 if (pos
>= ts
) return;
978 scope(exit
) undoGroupEnd();
979 char[255] spaces
= ' ';
983 assert(gb
[pos
] == '\t');
984 int spc
= tabSize
-(curx
%tabSize
);
985 replaceText
!"none"(pos
, 1, spaces
[0..spc
]);
988 while (pos
< ts
&& gb
[pos
] != '\t') {
989 if (gb
[pos
] == '\n') curx
= 0; else ++curx
;
996 //TODO: fix cx if current line was changed
997 final void doRemoveTailingSpaces () {
998 if (mReadOnly || gb
.textsize
== 0) return;
999 bool wasChanged
= false;
1000 scope(exit
) if (wasChanged
) undoGroupEnd();
1001 foreach (int lidx
; 0..linecount
) {
1002 auto ls
= gb
.line2pos(lidx
);
1003 auto le
= gb
.lineend(lidx
); // points at '\n'
1004 if (gb
[le
] != '\n') {
1007 } else if (le
-ls
< 1) {
1011 while (le
> ls
&& gb
[le
-1] <= ' ') { --le
; ++count
; }
1012 if (count
== 0) continue;
1013 if (!wasChanged
) { undoGroupStart(); wasChanged
= true; }
1014 deleteText
!"none"(le
, count
);
1019 // not string, not comment
1020 // if `goingDown` is true, update highlighting
1021 final bool isAnyTextChar (int pos
, bool goingDown
) {
1022 if (hl
is null) return true;
1023 if (pos
< 0 || pos
>= gb
.textsize
) return false;
1024 // update highlighting
1025 if (goingDown
&& hl
!is null) {
1026 auto lidx
= gb
.pos2line(pos
);
1027 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
1028 // ok, it is comment, it's the same as whitespace
1030 switch (gb
.hi(pos
).kwtype
) {
1031 case HiCommentOneLine
:
1032 case HiCommentMulti
:
1036 case HiStringSpecial
:
1038 case HiSQStringSpecial
:
1047 final bool isInComment (int pos
) {
1048 if (hl
is null || pos
< 0 || pos
>= gb
.textsize
) return false;
1049 return hiIsComment(gb
.hi(pos
));
1052 final bool isACGoodWordChar (int pos
) {
1053 if (pos
< 0 || pos
>= gb
.textsize
) return false;
1054 if (!isWordChar(gb
[pos
])) return false;
1056 // don't autocomplete in strings
1057 switch (gb
.hi(pos
).kwtype
) {
1062 case HiStringSpecial
:
1064 case HiSQStringSpecial
:
1074 final void doAutoComplete () {
1075 if (mReadOnly
) return;
1079 if (acbuffer
.length
> 0) {
1080 acbuffer
.length
= 0;
1081 acbuffer
.assumeSafeAppend
;
1085 void addAcToken (const(char)[] tk
) {
1086 if (tk
.length
== 0) return;
1087 foreach (const(char)[] t
; aclist
[0..acused
]) if (t
== tk
) return;
1088 if (acused
>= aclist
.length
) return;
1090 auto pos
= acbuffer
.length
;
1092 aclist
[acused
++] = acbuffer
[pos
..$];
1095 import std
.ascii
: isAlphaNum
;
1096 // get token to autocomplete
1098 if (!isACGoodWordChar(pos
-1)) return;
1099 //debug(egauto) { { import iv.vfs; auto fo = VFile("z00_ch.bin", "w"); fo.write(ch); } }
1100 bool startedInComment
= isInComment(pos
-1);
1101 char[128] tk
= void;
1102 int tkpos
= cast(int)tk
.length
;
1103 while (pos
> 0 && isACGoodWordChar(pos
-1)) {
1104 if (tkpos
== 0) return;
1105 tk
.ptr
[--tkpos
] = gb
[--pos
];
1107 int tklen
= cast(int)tk
.length
-tkpos
;
1109 //HACK: try "std.algo"
1110 if (gb
[pos
-1] == '.' && gb
[pos
-2] == 'd' && gb
[pos
-3] == 't' && gb
[pos
-4] == 's' && !isACGoodWordChar(pos
-5)) {
1111 if (tk
[$-tklen
..$] == "algo") {
1113 string ntx
= "rithm";
1115 replaceText
!"end"(tkstpos
, 0, ntx
);
1119 //debug(egauto) { { import iv.vfs; auto fo = VFile("z00_tk.bin", "w"); fo.write(tk[$-tklen..$]); } }
1121 char[128] xtk
= void;
1123 while (pos
> 0 && !isACGoodWordChar(pos
-1)) --pos
;
1124 if (pos
<= 0) break;
1125 int xtp
= cast(int)xtk
.length
;
1126 while (pos
> 0 && isACGoodWordChar(pos
-1)) {
1128 xtk
.ptr
[--xtp
] = gb
[--pos
];
1134 if (xtp
>= 0 && isInComment(pos
) == startedInComment
) {
1135 int xlen
= cast(int)xtk
.length
-xtp
;
1137 import core
.stdc
.string
: memcmp
;
1138 if (memcmp(xtk
.ptr
+xtk
.length
-xlen
, tk
.ptr
+tk
.length
-tklen
, tklen
) == 0) {
1139 const(char)[] tt
= xtk
[$-xlen
..$];
1141 if (acused
>= 128) break;
1146 debug(egauto
) { { import iv
.vfs
; auto fo
= VFile("z00_list.bin", "w"); fo
.writeln(list
[]); } }
1148 if (acused
== 0) return;
1151 if (mReadOnly
) return;
1152 replaceText
!"end"(tkstpos
, tklen
, acp
);
1154 auto oldlk
= editorlocked
;
1155 addEventListener(this, (EventEditorReplyAutocompletion evt
) {
1156 editorlocked
= oldlk
;
1157 if (evt
.res
.length
) replaceText
!"end"(evt
.pos
, evt
.len
, evt
.res
);
1158 }, true); // oneshot
1159 editorlocked
= true;
1161 gb
.pos2xyVT(tkstpos
, rx
, ry
);
1162 (new EventEditorQueryAutocompletion(this, tkstpos
, tklen
, FuiPoint((rx
-mXOfs
), (ry
-mTopLine
)+1), aclist
[0..acused
])).post
;
1166 final char[] buildHelpText(this ME
) () {
1168 void buildHelpFor(UDA
) () {
1169 foreach (string memn
; __traits(allMembers
, ME
)) {
1170 static if (is(typeof(__traits(getMember
, ME
, memn
)))) {
1171 foreach (const attr
; __traits(getAttributes
, __traits(getMember
, ME
, memn
))) {
1172 static if (is(typeof(attr
) == UDA
)) {
1173 static if (attr
.help
.length
&& attr
.key
.length
) {
1175 bool goodMode
= true;
1176 foreach (const attrx
; __traits(getAttributes
, __traits(getMember
, ME
, memn
))) {
1177 static if (is(attrx
== TEDSingleOnly
)) { if (!singleline
) goodMode
= false; }
1178 else static if (is(attrx
== TEDMultiOnly
)) { if (singleline
) goodMode
= false; }
1179 else static if (is(attrx
== TEDEditOnly
)) { if (readonly
) goodMode
= false; }
1180 else static if (is(attrx
== TEDROOnly
)) { if (!readonly
) goodMode
= false; }
1185 foreach (immutable _
; attr
.key
.length
..12) res
~= ".";
1197 buildHelpFor
!TEDKey
;
1198 while (res
.length
&& res
[$-1] <= ' ') res
= res
[0..$-1];
1202 protected enum Ecc
{ None
, Eaten
, Combo
}
1206 // Combo: combo start
1207 // comboBuf should contain comboCount+1 keys!
1208 protected final Ecc
checkKeys (const(char)[] keys
) {
1210 // check if current combo prefix is ok
1211 foreach (const ref TtyEvent ck
; comboBuf
[0..comboCount
+1]) {
1212 keys
= TtyEvent
.parse(k
, keys
);
1213 if (k
.key
== TtyEvent
.Key
.Error || k
.key
== TtyEvent
.Key
.None || k
.key
== TtyEvent
.Key
.Unknown
) return Ecc
.None
;
1214 if (k
!= ck
) return Ecc
.None
;
1216 return (keys
.length
== 0 ? Ecc
.Eaten
: Ecc
.Combo
);
1219 // fuck! `(this ME)` trick doesn't work here
1220 protected final Ecc
doEditorCommandByUDA(ME
=typeof(this)) (TtyEvent key
) {
1222 if (key
.key
== TtyEvent
.Key
.None
) return Ecc
.None
;
1223 if (key
.key
== TtyEvent
.Key
.Error || key
.key
== TtyEvent
.Key
.Unknown
) { comboCount
= 0; return Ecc
.None
; }
1224 bool possibleCombo
= false;
1225 // temporarily add current key to combo
1226 comboBuf
[comboCount
] = key
;
1227 // check all known combos
1228 foreach (string memn
; __traits(allMembers
, ME
)) {
1229 static if (is(typeof(&__traits(getMember
, ME
, memn
)))) {
1230 import std
.meta
: AliasSeq
;
1231 alias mx
= AliasSeq
!(__traits(getMember
, ME
, memn
))[0];
1232 static if (isCallable
!mx
&& hasUDA
!(mx
, TEDKey
)) {
1234 bool goodMode
= true;
1235 static if (hasUDA
!(mx
, TEDSingleOnly
)) { if (!singleline
) goodMode
= false; }
1236 static if (hasUDA
!(mx
, TEDMultiOnly
)) { if (singleline
) goodMode
= false; }
1237 static if (hasUDA
!(mx
, TEDEditOnly
)) { if (readonly
) goodMode
= false; }
1238 static if (hasUDA
!(mx
, TEDROOnly
)) { if (!readonly
) goodMode
= false; }
1240 foreach (const TEDKey attr
; getUDAs
!(mx
, TEDKey
)) {
1241 auto cc
= checkKeys(attr
.key
);
1242 if (cc
== Ecc
.Eaten
) {
1244 static if (is(ReturnType
!mx
== void)) {
1245 comboCount
= 0; // reset combo
1250 comboCount
= 0; // reset combo
1254 } else if (cc
== Ecc
.Combo
) {
1255 possibleCombo
= true;
1262 // check if we can start/continue combo
1263 // combo can't start with normal char, but can include normal chars
1264 if (possibleCombo
&& (comboCount
> 0 || key
.key
!= TtyEvent
.Key
.Char
)) {
1265 if (++comboCount
< comboBuf
.length
-1) return Ecc
.Combo
;
1267 // if we have combo prefix, eat key unconditionally
1268 if (comboCount
> 0) {
1269 comboCount
= 0; // reset combo, too long, or invalid, or none
1275 bool processKey (TtyEvent key
) {
1276 // hack it here, so it won't interfere with normal keyboard processing
1277 if (key
.key
== TtyEvent
.Key
.PasteStart
) { doPasteStart(); return true; }
1278 if (key
.key
== TtyEvent
.Key
.PasteEnd
) { doPasteEnd(); return true; }
1279 if (editorlocked
) return false;
1282 waitingInF5
= false;
1283 if (key
== "enter") {
1284 if (tempBlockFileName
.length
) {
1285 try { doBlockRead(tempBlockFileName
); } catch (Exception
) {} // sorry
1291 if (key
.key
== TtyEvent
.Key
.Error || key
.key
== TtyEvent
.Key
.Unknown
) { comboCount
= 0; return false; }
1293 if (incInputActive
) {
1294 if (key
.key
== TtyEvent
.Key
.ModChar
) {
1295 if (key
== "^C") { incInputActive
= false; resetIncSearchPos(); resetHighlight(); promptNoKillText(); return true; }
1296 if (key
== "^R") { incSearchDir
= 1; doNextIncSearch(); promptNoKillText(); return true; }
1297 if (key
== "^V") { incSearchDir
= -1; doNextIncSearch(); promptNoKillText(); return true; }
1300 if (key
== "esc" || key
== "enter") {
1301 incInputActive
= false;
1303 resetIncSearchPos(false);
1307 if (mPromptInput
!is null) mPromptInput
.utfuck
= utfuck
;
1308 promptProcessKey(key
, delegate (ed
) {
1309 // input buffer changed
1310 // check if it was *really* changed
1311 if (incSearchBuf
.length
== ed
.textsize
) {
1313 foreach (char ch
; ed
[]) { if (incSearchBuf
[pos
] != ch
) break; ++pos
; }
1314 if (pos
>= ed
.textsize
) return; // nothing was changed, so nothing to do
1317 incSearchBuf
.length
= 0;
1318 incSearchBuf
.assumeSafeAppend
;
1319 foreach (char ch
; ed
[]) incSearchBuf
~= ch
;
1320 doNextIncSearch(false); // don't move pointer
1322 drawStatus(); // just in case
1326 //resetIncSearchPos();
1328 final switch (doEditorCommandByUDA(key
)) {
1329 case Ecc
.None
: break;
1332 resetHighlight(false);
1336 if (key
.key
== TtyEvent
.Key
.Char
) {
1337 if (readonly
) return false;
1339 doPutChar(cast(char)key
.ch
);
1346 bool processClick (int button
, int x
, int y
) {
1347 if (x
< 0 || y
< 0 || x
>= winw || y
>= winh
) return false;
1348 if (button
!= 0) return false;
1349 gotoXY(x
, topline
+y
);
1354 void pasteToX11 () {
1360 if (!hasMarkedBlock
) return;
1362 void doPaste (string cbkey
) {
1364 string
[string
] moreEnv
;
1365 moreEnv
["K8_SUBSHELL"] = "tan";
1366 auto pp
= std
.process
.pipeProcess(
1367 //["dmd", "-c", "-o-", "-verrors=64", "-vcolumns", fname],
1368 ["xsel", "-i", cbkey
],
1369 std
.process
.Redirect
.stderrToStdout|std
.process
.Redirect
.stdout|std
.process
.Redirect
.stdin
,
1371 std
.process
.Config
.none
,
1375 auto rng
= markedBlockRange
;
1376 foreach (char ch
; rng
) pp
.stdin
.write(ch
);
1380 } catch (Exception
) {}
1388 static struct PlainMatch
{
1390 @property bool empty () const pure nothrow @safe @nogc { return (s
< 0 || s
>= e
); }
1393 // epos is not included
1394 final PlainMatch
findTextPlain (const(char)[] pat
, int spos
, int epos
, bool words
, bool caseSens
) {
1396 immutable ts
= gb
.textsize
;
1397 if (pat
.length
== 0 || pat
.length
> ts
) return res
;
1398 if (epos
<= spos || spos
>= ts
) return res
;
1399 if (spos
< 0) spos
= 0;
1400 if (epos
< 0) epos
= 0;
1401 if (epos
> ts
) epos
= ts
;
1402 immutable bl
= cast(int)pat
.length
;
1403 //dialogMessage!"debug"("findTextPlain", "spos=%s; epos=%s; ts=%s; curpos=%s; pat:[%s]", spos, epos, ts, curpos, pat);
1404 while (ts
-spos
>= bl
) {
1405 if (caseSens ||
!pat
.ptr
[0].isalpha
) {
1406 spos
= gb
.fastFindChar(spos
, pat
.ptr
[0]);
1407 if (ts
-spos
< bl
) break;
1411 foreach (int p
; spos
..spos
+bl
) if (gb
[p
] != pat
.ptr
[p
-spos
]) { found
= false; break; }
1413 foreach (int p
; spos
..spos
+bl
) if (gb
[p
].tolower
!= pat
.ptr
[p
-spos
].tolower
) { found
= false; break; }
1415 // check word boundaries
1416 if (found
&& words
) {
1417 if (spos
> 0 && isWordChar(gb
[spos
-1])) found
= false;
1419 if (ep
< ts
&& isWordChar(gb
[ep
])) found
= false;
1421 //dialogMessage!"debug"("findTextPlain", "spos=%s; epos=%s; found=%s", spos, epos, found);
1432 // epos is not included
1433 final PlainMatch
findTextPlainBack (const(char)[] pat
, int spos
, int epos
, bool words
, bool caseSens
) {
1435 immutable ts
= gb
.textsize
;
1436 if (pat
.length
== 0 || pat
.length
> ts
) return res
;
1437 if (epos
<= spos || spos
>= ts
) return res
;
1438 if (spos
< 0) spos
= 0;
1439 if (epos
< 0) epos
= 0;
1440 if (epos
> ts
) epos
= ts
;
1441 immutable bl
= cast(int)pat
.length
;
1442 if (ts
-epos
< bl
) epos
= ts
-bl
;
1443 while (epos
>= spos
) {
1446 foreach (int p
; epos
..epos
+bl
) if (gb
[p
] != pat
.ptr
[p
-epos
]) { found
= false; break; }
1448 foreach (int p
; epos
..epos
+bl
) if (gb
[p
].tolower
!= pat
.ptr
[p
-epos
].tolower
) { found
= false; break; }
1450 if (found
&& words
) {
1451 if (epos
> 0 && isWordChar(gb
[epos
-1])) found
= false;
1453 if (ep
< ts
&& isWordChar(gb
[ep
])) found
= false;
1465 // epos is not included
1466 // caps are fixed so it can be used to index gap buffer
1467 final bool findTextRegExp (RegExp re
, int spos
, int epos
, Pike
.Capture
[] caps
) {
1468 Pike
.Capture
[1] tcaps
;
1469 if (epos
<= spos || spos
>= textsize
) return false;
1470 if (caps
.length
== 0) caps
= tcaps
;
1471 if (spos
< 0) spos
= 0;
1472 if (epos
< 0) epos
= 0;
1473 if (epos
> textsize
) epos
= textsize
;
1474 auto ctx
= Pike
.create(re
, caps
);
1475 if (!ctx
.valid
) return false;
1476 int res
= SRes
.Again
;
1477 foreach (const(char)[] buf
; gb
.bufparts(spos
)) {
1478 res
= ctx
.exec(buf
, false);
1480 if (res
!= SRes
.Again
) return false;
1485 if (res
< 0) return false;
1486 if (spos
+caps
[0].s
>= epos
) return false;
1487 if (spos
+caps
[0].e
> epos
) return false;
1488 foreach (ref cp
; caps
) if (cp
.s
< cp
.e
) { cp
.s
+= spos
; cp
.e
+= spos
; }
1492 // epos is not included
1493 // caps are fixed so it can be used to index gap buffer
1494 final bool findTextRegExpBack (RegExp re
, int spos
, int epos
, Pike
.Capture
[] caps
) {
1495 import core
.stdc
.string
: memcpy
;
1497 Pike
.Capture
* savedCaps
;
1498 Pike
.Capture
[1] tcaps
;
1499 if (epos
<= spos || spos
>= textsize
) return false;
1500 if (caps
.length
== 0) caps
= tcaps
;
1501 if (spos
< 0) spos
= 0;
1502 if (epos
< 0) epos
= 0;
1503 if (epos
> textsize
) epos
= textsize
;
1504 while (spos
< epos
) {
1505 auto ctx
= Pike
.create(re
, caps
);
1506 if (!ctx
.valid
) break;
1507 int res
= SRes
.Again
;
1508 foreach (const(char)[] buf
; gb
.bufparts(spos
)) {
1509 res
= ctx
.exec(buf
, false);
1511 if (res
!= SRes
.Again
) return false;
1516 if (spos
+caps
[0].s
>= epos
) break;
1517 //dialogMessage!"debug"("findTextRegexpBack", "spos=%s; epos=%s; found=%s", spos, epos, spos+caps[0].s);
1519 if (savedCaps
is null) {
1520 if (!csave
.active
) {
1521 csave
= MemPool
.create
;
1522 if (!csave
.active
) return false; // alas
1523 savedCaps
= csave
.alloc
!(typeof(caps
[0]))(cast(uint)(caps
[0].sizeof
*(caps
.length
-1)));
1524 if (savedCaps
is null) return false; // alas
1528 foreach (ref cp
; caps
) if (cp
.s
< cp
.e
) { cp
.s
+= spos
; cp
.e
+= spos
; }
1529 memcpy(savedCaps
, caps
.ptr
, caps
[0].sizeof
*caps
.length
);
1530 //FIXME: should we skip the whole found match?
1533 if (savedCaps
is null) return false;
1534 // restore latest match
1535 memcpy(caps
.ptr
, savedCaps
, caps
[0].sizeof
*caps
.length
);
1539 final void srrPlainStart (ref SROptions srr
) {
1540 if (srr
.search
.length
== 0) { srr
.ed
= null; return; }
1541 if (srr
.inselection
&& !hasMarkedBlock
) { srr
.ed
= null; return; }
1542 //scope(exit) fullDirty(); // to remove highlighting
1543 //bool closeGroup = false;
1544 //scope(exit) if (closeGroup) undoGroupEnd();
1546 if (srr
.inselection
) {
1550 if (!srr
.backwards
) {
1563 srr
.closeGroup
= false;
1564 srr
.cont
= SROptions
.Cont
.Yes
;
1568 final void srrPlainDoSkip (ref SROptions srr
) {
1569 if (!srr
.backwards
) srr
.spos
= srr
.mts
+1; else srr
.epos
= srr
.mts
;
1572 final void srrPlainDoReplace (ref SROptions srr
) {
1574 replaceText
!"end"(srr
.mts
, srr
.mte
-srr
.mts
, srr
.replace
);
1576 if (!srr
.backwards
) {
1578 srr
.spos
= srr
.mts
+cast(int)srr
.replace
.length
;
1579 srr
.epos
-= (srr
.mte
-srr
.mts
)-cast(int)srr
.replace
.length
;
1585 //TODO: write event-based code
1586 final void srrPlainStep (ref SROptions srr
) {
1587 while (srr
.spos
< srr
.epos
) {
1589 if (srr
.backwards
) {
1590 mt
= findTextPlainBack(srr
.search
, srr
.spos
, srr
.epos
, srr
.wholeword
, srr
.casesens
);
1592 mt
= findTextPlain(srr
.search
, srr
.spos
, srr
.epos
, srr
.wholeword
, srr
.casesens
);
1594 if (mt
.empty
) break;
1598 if (srr
.nocomments
&& hl
!is null) {
1599 auto lidx
= gb
.pos2line(mt
.s
);
1600 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
1601 if (hiIsComment(gb
.hi(mt
.s
))) {
1602 srrPlainDoSkip(srr
);
1606 if (srr
.cont
!= SROptions
.Cont
.All
) {
1607 bool doundo
= (mt
.s
!= curpos
);
1608 if (doundo
) gotoPos
!true(mt
.s
);
1612 texthi
.count
= mt
.e
-mt
.s
;
1613 texthi
.clr
= IncSearchColor
;
1614 drawPartHighlight(texthi
.pos
, texthi
.count
, texthi
.clr
);
1615 auto oldlk
= editorlocked
;
1616 addEventListener(this, (EventEditorReplyReplacement evt
) {
1617 editorlocked
= oldlk
;
1618 if (evt
.opt
is null) return;
1619 srr
= *cast(SROptions
*)evt
.opt
;
1620 assert(srr
.ed
is this);
1621 final switch (srr
.cont
) {
1622 case SROptions
.Cont
.Cancel
:
1623 assert(srr
.closeGroup
== false);
1626 case SROptions
.Cont
.No
:
1627 srrPlainDoSkip(srr
);
1629 case SROptions
.Cont
.All
:
1630 if (!srr
.closeGroup
) { undoGroupStart(); srr
.closeGroup
= true; }
1631 goto case SROptions
.Cont
.Yes
;
1632 case SROptions
.Cont
.Yes
:
1633 srrPlainDoReplace(srr
);
1639 editorlocked
= true;
1640 (new EventEditorQueryReplacement(this, &srr
)).post
;
1644 srrPlainDoReplace(srr
);
1647 if (srr
.cont
== SROptions
.Cont
.All
) {
1648 import std
.string
: format
;
1649 if (srr
.closeGroup
) { srr
.closeGroup
= false; undoGroupEnd(); }
1652 (new EventEditorMessage(this, "%s replacement%s made".format(srr
.repcount
, (srr
.repcount
!= 1 ?
"s" : "")))).post
;
1654 assert(srr
.closeGroup
== false);
1658 final void srrRegexStart (ref SROptions srr
) {
1659 import std
.utf
: byChar
;
1660 if (srr
.search
.length
== 0) { srr
.ed
= null; return; }
1661 if (srr
.inselection
&& !hasMarkedBlock
) { srr
.ed
= null; return; }
1662 srr
.re
= RegExp
.create(srr
.search
.byChar
, (srr
.casesens ?
0 : SRFlags
.CaseInsensitive
)|SRFlags
.Multiline
);
1663 if (!srr
.re
.valid
) { ttyBeep
; srr
.re
= RegExp
.init
; srr
.ed
= null; return; }
1665 if (srr
.inselection
) {
1669 if (!srr
.backwards
) {
1682 srr
.closeGroup
= false;
1683 srr
.cont
= SROptions
.Cont
.Yes
;
1687 final void srrRegExpDoSkip (ref SROptions srr
) {
1688 if (!srr
.backwards
) srr
.spos
= srr
.caps
[0].s
+1; else srr
.epos
= srr
.caps
[0].s
;
1691 final void srrRegExpDoReplace (ref SROptions srr
) {
1693 if (srr
.newtext
.length
) { srr
.newtext
.length
= 0; srr
.newtext
.assumeSafeAppend
; }
1694 auto reps
= srr
.replace
;
1696 mainloop
: while (spos
< reps
.length
) {
1697 if ((reps
[spos
] == '$' || reps
[spos
] == '\\') && reps
.length
-spos
> 1 && reps
[spos
+1].isdigit
) {
1698 int n
= reps
[spos
+1]-'0';
1700 if (!srr
.caps
[n
].empty
) foreach (char ch
; this[srr
.caps
[n
].s
..srr
.caps
[n
].e
]) srr
.newtext
~= ch
;
1701 } else if ((reps
[spos
] == '$' || reps
[spos
] == '\\') && reps
.length
-spos
> 2 && reps
[spos
+1] == '{' && reps
[spos
+2].isdigit
) {
1702 bool toupper
, tolower
, capitalize
, uncapitalize
;
1706 while (spos
< reps
.length
&& reps
[spos
].isdigit
) n
= n
*10+reps
[spos
++]-'0';
1707 while (spos
< reps
.length
&& reps
[spos
] != '}') {
1708 switch (reps
[spos
++]) {
1709 case 'u': case 'U': toupper
= true; tolower
= false; break;
1710 case 'l': case 'L': tolower
= true; toupper
= false; break;
1711 case 'C': capitalize
= true; break;
1712 case 'c': uncapitalize
= true; break;
1713 default: // ignore other flags
1716 if (spos
< reps
.length
&& reps
[spos
] == '}') ++spos
;
1717 if (n
< srr
.caps
.length
&& !srr
.caps
[n
].empty
) {
1718 int tp
= srr
.caps
[n
].s
, ep
= srr
.caps
[n
].e
;
1720 if (capitalize || toupper
) ch
= ch
.toupper
;
1721 else if (uncapitalize || tolower
) ch
= ch
.tolower
;
1725 if (toupper
) ch
= ch
.toupper
;
1726 else if (tolower
) ch
= ch
.tolower
;
1730 } else if (reps
[spos
] == '\\' && reps
.length
-spos
> 1) {
1732 switch (reps
[spos
-1]) {
1733 case 't': srr
.newtext
~= '\t'; break;
1734 case 'n': srr
.newtext
~= '\n'; break;
1735 case 'r': srr
.newtext
~= '\r'; break;
1736 case 'a': srr
.newtext
~= '\a'; break;
1737 case 'b': srr
.newtext
~= '\b'; break;
1738 case 'e': srr
.newtext
~= '\x1b'; break;
1740 if (reps
.length
-spos
< 1) break mainloop
;
1741 int n
= digitInBase(reps
[spos
], 16);
1744 if (reps
.length
-spos
> 0 && digitInBase(reps
[spos
], 16) >= 0) {
1745 n
= n
*16+digitInBase(reps
[spos
], 16);
1748 srr
.newtext
~= cast(char)n
;
1751 srr
.newtext
~= reps
[spos
-1];
1755 srr
.newtext
~= reps
[spos
++];
1758 replaceText
!"end"(srr
.caps
[0].s
, srr
.caps
[0].e
-srr
.caps
[0].s
, srr
.newtext
);
1759 return cast(int)srr
.newtext
.length
;
1763 int replen
= rereplace();
1765 if (!srr
.backwards
) {
1766 srr
.spos
= srr
.caps
[0].s
+(replen ? replen
: 1);
1767 srr
.epos
+= replen
-(srr
.caps
[0].e
-srr
.caps
[0].s
);
1769 srr
.epos
= srr
.caps
[0].s
;
1773 final void srrRegExpStep (ref SROptions srr
) {
1774 while (srr
.spos
< srr
.epos
) {
1776 if (srr
.backwards
) {
1777 found
= findTextRegExpBack(srr
.re
, srr
.spos
, srr
.epos
, srr
.caps
[]);
1779 found
= findTextRegExp(srr
.re
, srr
.spos
, srr
.epos
, srr
.caps
[]);
1783 if (srr
.nocomments
&& hl
!is null) {
1784 auto lidx
= gb
.pos2line(srr
.caps
[0].s
);
1785 if (hl
.fixLine(lidx
)) markLinesDirty(lidx
, 1); // so it won't lost dirty flag in redraw
1786 if (hiIsComment(gb
.hi(srr
.caps
[0].s
))) {
1787 srrRegExpDoSkip(srr
);
1791 if (srr
.cont
!= SROptions
.Cont
.All
) {
1792 bool doundo
= (srr
.caps
[0].s
!= curpos
);
1793 if (doundo
) gotoPos
!true(srr
.caps
[0].s
);
1796 texthi
.pos
= srr
.caps
[0].s
;
1797 texthi
.count
= srr
.caps
[0].e
-srr
.caps
[0].s
;
1798 texthi
.clr
= IncSearchColor
;
1799 drawPartHighlight(texthi
.pos
, texthi
.count
, texthi
.clr
);
1800 auto oldlk
= editorlocked
;
1801 addEventListener(this, (EventEditorReplyReplacement evt
) {
1802 editorlocked
= oldlk
;
1803 if (evt
.opt
is null) return;
1804 srr
= *cast(SROptions
*)evt
.opt
;
1805 assert(srr
.ed
is this);
1806 final switch (srr
.cont
) {
1807 case SROptions
.Cont
.Cancel
:
1808 assert(srr
.closeGroup
== false);
1811 case SROptions
.Cont
.No
:
1812 srrRegExpDoSkip(srr
);
1814 case SROptions
.Cont
.All
:
1815 if (!srr
.closeGroup
) { undoGroupStart(); srr
.closeGroup
= true; }
1816 goto case SROptions
.Cont
.Yes
;
1817 case SROptions
.Cont
.Yes
:
1818 srrRegExpDoReplace(srr
);
1824 editorlocked
= true;
1825 (new EventEditorQueryReplacement(this, &srr
)).post
;
1829 srrRegExpDoReplace(srr
);
1832 if (srr
.cont
== SROptions
.Cont
.All
) {
1833 import std
.string
: format
;
1834 if (srr
.closeGroup
) { srr
.closeGroup
= false; undoGroupEnd(); }
1837 (new EventEditorMessage(this, "%s replacement%s made".format(srr
.repcount
, (srr
.repcount
!= 1 ?
"s" : "")))).post
;
1839 assert(srr
.closeGroup
== false);
1844 void processWordWith (scope char delegate (char ch
) dg
) {
1845 if (dg
is null) return;
1846 bool undoAdded
= false;
1847 scope(exit
) if (undoAdded
) undoGroupEnd();
1849 if (!isWordChar(gb
[pos
])) return;
1851 while (pos
> 0 && isWordChar(gb
[pos
-1])) --pos
;
1852 while (pos
< gb
.textsize
) {
1854 if (!isWordChar(gb
[pos
])) break;
1857 if (!undoAdded
) { undoAdded
= true; undoGroupStart(); }
1858 replaceText
!"none"(pos
, 1, (&nc
)[0..1]);
1866 @TEDMultiOnly mixin TEDImpl
!("Up", q
{ doUp(); });
1867 @TEDMultiOnly mixin TEDImpl
!("S-Up", q
{ doUp(true); });
1868 @TEDMultiOnly mixin TEDImpl
!("^Up", q
{ doScrollUp(); });
1869 @TEDMultiOnly mixin TEDImpl
!("S-^Up", q
{ doScrollUp(true); });
1870 @TEDMultiOnly mixin TEDImpl
!("Down", q
{ doDown(); });
1871 @TEDMultiOnly mixin TEDImpl
!("S-Down", q
{ doDown(true); });
1872 @TEDMultiOnly mixin TEDImpl
!("^Down", q
{ doScrollDown(); });
1873 @TEDMultiOnly mixin TEDImpl
!("S-^Down", q
{ doScrollDown(true); });
1875 mixin TEDImpl
!("Left", q
{ doLeft(); });
1876 mixin TEDImpl
!("S-Left", q
{ doLeft(true); });
1878 mixin TEDImpl
!("^Left", q
{ doWordLeft(); });
1879 mixin TEDImpl
!("S-^Left", q
{ doWordLeft(true); });
1880 mixin TEDImpl
!("Right", q
{ doRight(); });
1881 mixin TEDImpl
!("S-Right", q
{ doRight(true); });
1882 mixin TEDImpl
!("^Right", q
{ doWordRight(); });
1883 mixin TEDImpl
!("S-^Right", q
{ doWordRight(true); });
1885 @TEDMultiOnly mixin TEDImpl
!("PageUp", q
{ doPageUp(); });
1886 @TEDMultiOnly mixin TEDImpl
!("S-PageUp", q
{ doPageUp(true); });
1887 @TEDMultiOnly mixin TEDImpl
!("^PageUp", q
{ doTextTop(); });
1888 @TEDMultiOnly mixin TEDImpl
!("S-^PageUp", q
{ doTextTop(true); });
1889 @TEDMultiOnly mixin TEDImpl
!("PageDown", q
{ doPageDown(); });
1890 @TEDMultiOnly mixin TEDImpl
!("S-PageDown", q
{ doPageDown(true); });
1891 @TEDMultiOnly mixin TEDImpl
!("^PageDown", q
{ doTextBottom(); });
1892 @TEDMultiOnly mixin TEDImpl
!("S-^PageDown", q
{ doTextBottom(true); });
1893 mixin TEDImpl
!("Home", q
{ doHome(); });
1894 mixin TEDImpl
!("S-Home", q
{ doHome(true, true); });
1895 @TEDMultiOnly mixin TEDImpl
!("^Home", q
{ doPageTop(); });
1896 @TEDMultiOnly mixin TEDImpl
!("S-^Home", q
{ doPageTop(true); });
1897 mixin TEDImpl
!("End", q
{ doEnd(); });
1898 mixin TEDImpl
!("S-End", q
{ doEnd(true); });
1899 @TEDMultiOnly mixin TEDImpl
!("^End", q
{ doPageBottom(); });
1900 @TEDMultiOnly mixin TEDImpl
!("S-^End", q
{ doPageBottom(true); });
1902 @TEDEditOnly mixin TEDImpl
!("Backspace", q
{ doBackspace(); });
1903 @TEDSingleOnly @TEDEditOnly mixin TEDImpl
!("M-Backspace", "delete previous word", q
{ doDeleteWord(); });
1904 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("M-Backspace", "delete previous word or unindent", q
{ doBackByIndent(); });
1906 mixin TEDImpl
!("Delete", q
{ doDelete(); });
1907 mixin TEDImpl
!("^Insert", "copy block to clipboard file, reset block mark", q
{ if (tempBlockFileName
.length
== 0) return; doBlockWrite(tempBlockFileName
); doBlockResetMark(); });
1909 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("Enter", q
{ doPutChar('\n'); });
1910 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("M-Enter", "split line without autoindenting", q
{ doLineSplit(false); });
1912 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("F2", "save file", q
{ saveFile(fullFileName
); });
1913 mixin TEDImpl
!("F3", "start/stop/reset block marking", q
{ doBlockMark(); });
1914 mixin TEDImpl
!("^F3", "reset block mark", q
{ doBlockResetMark(); });
1915 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("F4", "search and relace text", q
{
1916 if (srrOptions
.ed
!is null) return; // in progress
1917 srrOptions
.ed
= this;
1918 auto oldlk
= editorlocked
;
1919 addEventListener(this, (EventEditorReplySR evt
) {
1920 editorlocked
= oldlk
;
1921 if (!evt
.proceed
) { srrOptions
.ed
= null; return; }
1922 assert(evt
.opt
!is null);
1923 srrOptions
= *cast(SROptions
*)evt
.opt
;
1924 if (srrOptions
.type
== SROptions
.Type
.Normal
) srrPlainStart(srrOptions
);
1925 if (srrOptions
.type
== SROptions
.Type
.Regex
) srrRegexStart(srrOptions
);
1927 editorlocked
= true;
1928 (new EventEditorQuerySR(this, &srrOptions
)).post
;
1930 @TEDEditOnly mixin TEDImpl
!("F5", "copy block", q
{ doBlockCopy(); });
1931 mixin TEDImpl
!("^F5", "copy block to clipboard file", q
{ if (tempBlockFileName
.length
== 0) return; doBlockWrite(tempBlockFileName
); });
1932 @TEDEditOnly mixin TEDImpl
!("S-F5", "insert block from clipboard file", q
{ if (tempBlockFileName
.length
== 0) return; waitingInF5
= true; });
1933 @TEDEditOnly mixin TEDImpl
!("F6", "move block", q
{ doBlockMove(); });
1934 @TEDEditOnly mixin TEDImpl
!("F8", "delete block", q
{ doBlockDelete(); });
1936 mixin TEDImpl
!("^A", "move to line start", q
{ doHome(); });
1937 mixin TEDImpl
!("^E", "move to line end", q
{ doEnd(); });
1939 @TEDMultiOnly mixin TEDImpl
!("M-I", "jump to previous bookmark", q
{ doBookmarkJumpUp(); });
1940 @TEDMultiOnly mixin TEDImpl
!("M-J", "jump to next bookmark", q
{ doBookmarkJumpDown(); });
1941 @TEDMultiOnly mixin TEDImpl
!("M-K", "toggle bookmark", q
{ doBookmarkToggle(); });
1942 @TEDMultiOnly mixin TEDImpl
!("M-L", "goto line", q
{ (new EventEditorQueryGotoLine(this)).post
; });
1944 @TEDEditOnly mixin TEDImpl
!("M-C", "capitalize word", q
{
1946 processWordWith((char ch
) {
1947 if (first
) { first
= false; ch
= ch
.toupper
; }
1951 @TEDEditOnly mixin TEDImpl
!("M-Q", "lowercase word", q
{ processWordWith((char ch
) => ch
.tolower
); });
1952 @TEDEditOnly mixin TEDImpl
!("M-U", "uppercase word", q
{ processWordWith((char ch
) => ch
.toupper
); });
1954 @TEDMultiOnly mixin TEDImpl
!("M-S-L", "force center current line", q
{ makeCurLineVisibleCentered(true); });
1955 @TEDMultiOnly mixin TEDImpl
!("^R", "continue incremental search, forward", q
{ incSearchDir
= 1; if (incSearchBuf
.length
== 0 && !incInputActive
) doStartIncSearch(1); else doNextIncSearch(); });
1956 @TEDEditOnly mixin TEDImpl
!("^U", "undo", q
{ doUndo(); });
1957 @TEDEditOnly mixin TEDImpl
!("M-S-U", "redo", q
{ doRedo(); });
1958 @TEDMultiOnly mixin TEDImpl
!("^V", "continue incremental search, backward", q
{ incSearchDir
= -1; if (incSearchBuf
.length
== 0 && !incInputActive
) doStartIncSearch(-1); else doNextIncSearch(); });
1959 @TEDEditOnly mixin TEDImpl
!("^W", "remove previous word", q
{ doDeleteWord(); });
1960 @TEDEditOnly mixin TEDImpl
!("^Y", "remove current line", q
{ doKillLine(); });
1961 @TEDMultiOnly mixin TEDImpl
!("^_", "start new incremental search, forward", q
{ doStartIncSearch(1); }); // ctrl+slash, actually
1962 @TEDMultiOnly mixin TEDImpl
!("^\\", "start new incremental search, backward", q
{ doStartIncSearch(-1); });
1964 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("Tab", q
{ doPutText(" "); });
1965 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("M-Tab", "autocomplete word", q
{ doAutoComplete(); });
1966 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-Tab", "indent block", q
{ doIndentBlock(); });
1967 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("C-S-Tab", "unindent block", q
{ doUnindentBlock(); });
1969 mixin TEDImpl
!("M-S-c", "copy block to X11 selections (all three)", q
{ pasteToX11(); doBlockResetMark(); });
1971 mixin TEDImpl
!("M-E", "select codepage", q
{ (new EventEditorQueryCodePage(this, (utfuck ?
3 : codepage
))).post
; });
1973 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K ^I", "indent block", q
{ doIndentBlock(); });
1974 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K ^U", "unindent block", q
{ doUnindentBlock(); });
1975 @TEDEditOnly mixin TEDImpl
!("^K ^E", "clear from cursor to EOL", q
{ doKillToEOL(); });
1976 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K Tab", "indent block", q
{ doIndentBlock(); });
1977 @TEDEditOnly mixin TEDImpl
!("^K M-Tab", "untabify", q
{ doUntabify(gb
.tabsize ? gb
.tabsize
: 2); }); // alt+tab: untabify
1978 @TEDEditOnly mixin TEDImpl
!("^K C-space", "remove trailing spaces", q
{ doRemoveTailingSpaces(); });
1979 mixin TEDImpl
!("^K ^T", /*"toggle \"visual tabs\" mode",*/ q
{ visualtabs
= !visualtabs
; });
1981 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K ^B", q
{ doSetBlockStart(); });
1982 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K ^K", q
{ doSetBlockEnd(); });
1984 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K ^C", q
{ doBlockCopy(); });
1985 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K ^M", q
{ doBlockMove(); });
1986 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K ^Y", q
{ doBlockDelete(); });
1987 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K ^H", q
{ doBlockResetMark(); });
1989 @TEDMultiOnly @TEDEditOnly mixin TEDImpl
!("^K Backspace", q
{ doBlockResetMark(); });
1991 @TEDEditOnly mixin TEDImpl
!("^Q Tab", q
{ doPutChar('\t'); });
1992 mixin TEDImpl
!("^Q ^U", "toggle utfuck mode", q
{ utfuck
= !utfuck
; }); // ^Q^U: switch utfuck mode
1993 mixin TEDImpl
!("^Q 1", "switch to koi8", q
{ utfuck
= false; codepage
= CodePage
.koi8u
; fullDirty(); });
1994 mixin TEDImpl
!("^Q 2", "switch to cp1251", q
{ utfuck
= false; codepage
= CodePage
.cp1251
; fullDirty(); });
1995 mixin TEDImpl
!("^Q 3", "switch to cp866", q
{ utfuck
= false; codepage
= CodePage
.cp866
; fullDirty(); });
1996 mixin TEDImpl
!("^Q ^B", "go to block start", q
{ if (hasMarkedBlock
) gotoPos
!true(bstart
); lastBGEnd
= false; });
1998 @TEDMultiOnly mixin TEDImpl
!("^Q ^F", "incremental search current word", q
{
2000 if (!isWordChar(gb
[pos
])) return;
2001 // deactivate prompt
2002 if (incInputActive
) {
2003 incInputActive
= false;
2005 resetIncSearchPos();
2009 while (pos
> 0 && isWordChar(gb
[pos
-1])) --pos
;
2010 incSearchBuf
.length
= 0;
2011 incSearchBuf
.assumeSafeAppend
;
2012 while (pos
< gb
.textsize
&& isWordChar(gb
[pos
])) incSearchBuf
~= gb
[pos
++];
2018 mixin TEDImpl
!("^Q ^K", "go to block end", q
{ if (hasMarkedBlock
) gotoPos
!true(bend
); lastBGEnd
= true; });
2019 mixin TEDImpl
!("^Q ^T", "set tab size", q
{ (new EventEditorQueryTabSize(this, tabsize
)).post
; });
2021 @TEDMultiOnly @TEDROOnly mixin TEDImpl
!("Space", q
{ doPageDown(); });
2022 @TEDMultiOnly @TEDROOnly mixin TEDImpl
!("^Space", q
{ doPageUp(); });