mirror of https://github.com/adamdruppe/arsd.git
reverse history search in line getter
This commit is contained in:
parent
b1f57a3ef1
commit
ec11df3114
378
terminal.d
378
terminal.d
|
@ -4671,6 +4671,8 @@ class LineGetter {
|
||||||
|
|
||||||
if(cursorPosition >= horizontalScrollPosition + availableLineLength())
|
if(cursorPosition >= horizontalScrollPosition + availableLineLength())
|
||||||
horizontalScrollPosition++;
|
horizontalScrollPosition++;
|
||||||
|
|
||||||
|
lineChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// .
|
/// .
|
||||||
|
@ -4690,8 +4692,11 @@ class LineGetter {
|
||||||
line[i] = line[i + 1];
|
line[i] = line[i + 1];
|
||||||
line = line[0 .. $-1];
|
line = line[0 .. $-1];
|
||||||
line.assumeSafeAppend();
|
line.assumeSafeAppend();
|
||||||
|
lineChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected bool lineChanged;
|
||||||
|
|
||||||
private void killText(dchar[] text) {
|
private void killText(dchar[] text) {
|
||||||
if(!text.length)
|
if(!text.length)
|
||||||
return;
|
return;
|
||||||
|
@ -4774,8 +4779,109 @@ class LineGetter {
|
||||||
return terminal.width - startOfLineX - promptLength - 1;
|
return terminal.width - startOfLineX - promptLength - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static struct Drawer {
|
||||||
|
LineGetter lg;
|
||||||
|
|
||||||
|
this(LineGetter lg) {
|
||||||
|
this.lg = lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
int written;
|
||||||
|
int lineLength;
|
||||||
|
|
||||||
|
void specialChar(char c) {
|
||||||
|
lg.terminal.color(lg.regularForeground, lg.specialCharBackground);
|
||||||
|
lg.terminal.write(c);
|
||||||
|
lg.terminal.color(lg.regularForeground, lg.background);
|
||||||
|
|
||||||
|
written++;
|
||||||
|
lineLength--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void regularChar(dchar ch) {
|
||||||
|
import std.utf;
|
||||||
|
char[4] buffer;
|
||||||
|
auto l = encode(buffer, ch);
|
||||||
|
// note the Terminal buffers it so meh
|
||||||
|
lg.terminal.write(buffer[0 .. l]);
|
||||||
|
|
||||||
|
written++;
|
||||||
|
lineLength--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawContent(T)(T towrite, int highlightBegin = 0, int highlightEnd = 0) {
|
||||||
|
// FIXME: if there is a color at the end of the line it messes up as you scroll
|
||||||
|
// FIXME: need a way to go to multi-line editing
|
||||||
|
|
||||||
|
bool highlightOn = false;
|
||||||
|
void highlightOff() {
|
||||||
|
lg.terminal.color(lg.regularForeground, lg.background);
|
||||||
|
highlightOn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(idx, dchar ch; towrite) {
|
||||||
|
if(lineLength <= 0)
|
||||||
|
break;
|
||||||
|
switch(ch) {
|
||||||
|
case '\n': specialChar('n'); break;
|
||||||
|
case '\r': specialChar('r'); break;
|
||||||
|
case '\a': specialChar('a'); break;
|
||||||
|
case '\t': specialChar('t'); break;
|
||||||
|
case '\b': specialChar('b'); break;
|
||||||
|
case '\033': specialChar('e'); break;
|
||||||
|
default:
|
||||||
|
if(highlightEnd) {
|
||||||
|
if(idx == highlightBegin) {
|
||||||
|
lg.terminal.color(lg.regularForeground, Color.yellow);
|
||||||
|
highlightOn = true;
|
||||||
|
}
|
||||||
|
if(idx == highlightEnd) {
|
||||||
|
highlightOff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
regularChar(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(highlightOn)
|
||||||
|
highlightOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private int lastDrawLength = 0;
|
private int lastDrawLength = 0;
|
||||||
void redraw() {
|
void redraw() {
|
||||||
|
finalizeRedraw(coreRedraw());
|
||||||
|
}
|
||||||
|
|
||||||
|
void finalizeRedraw(CoreRedrawInfo cdi) {
|
||||||
|
if(!cdi.populated)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(UseVtSequences) {
|
||||||
|
terminal.writeStringRaw("\033[K");
|
||||||
|
} else {
|
||||||
|
// FIXME: graphemes
|
||||||
|
if(cdi.written < lastDrawLength)
|
||||||
|
foreach(i; cdi.written .. lastDrawLength)
|
||||||
|
terminal.write(" ");
|
||||||
|
lastDrawLength = cdi.written;
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.moveTo(startOfLineX + cdi.cursorPositionToDrawX + promptLength, startOfLineY + cdi.cursorPositionToDrawY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct CoreRedrawInfo {
|
||||||
|
bool populated;
|
||||||
|
int written;
|
||||||
|
int cursorPositionToDrawX;
|
||||||
|
int cursorPositionToDrawY;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CoreRedrawInfo coreRedraw() {
|
||||||
|
if(supplementalGetter)
|
||||||
|
return CoreRedrawInfo.init; // the supplementalGetter will be drawing instead...
|
||||||
terminal.hideCursor();
|
terminal.hideCursor();
|
||||||
scope(exit) {
|
scope(exit) {
|
||||||
version(Win32Console) {
|
version(Win32Console) {
|
||||||
|
@ -4793,8 +4899,10 @@ class LineGetter {
|
||||||
}
|
}
|
||||||
terminal.moveTo(startOfLineX, startOfLineY);
|
terminal.moveTo(startOfLineX, startOfLineY);
|
||||||
|
|
||||||
auto lineLength = availableLineLength();
|
Drawer drawer = Drawer(this);
|
||||||
if(lineLength < 0)
|
|
||||||
|
drawer.lineLength = availableLineLength();
|
||||||
|
if(drawer.lineLength < 0)
|
||||||
throw new Exception("too narrow terminal to draw");
|
throw new Exception("too narrow terminal to draw");
|
||||||
|
|
||||||
terminal.color(promptColor, background);
|
terminal.color(promptColor, background);
|
||||||
|
@ -4805,69 +4913,30 @@ class LineGetter {
|
||||||
auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition;
|
auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition;
|
||||||
auto cursorPositionToDrawY = 0;
|
auto cursorPositionToDrawY = 0;
|
||||||
|
|
||||||
int written = promptLength;
|
drawer.drawContent(towrite);
|
||||||
|
|
||||||
void specialChar(char c) {
|
|
||||||
terminal.color(regularForeground, specialCharBackground);
|
|
||||||
terminal.write(c);
|
|
||||||
terminal.color(regularForeground, background);
|
|
||||||
|
|
||||||
written++;
|
|
||||||
lineLength--;
|
|
||||||
}
|
|
||||||
|
|
||||||
void regularChar(dchar ch) {
|
|
||||||
import std.utf;
|
|
||||||
char[4] buffer;
|
|
||||||
auto l = encode(buffer, ch);
|
|
||||||
// note the Terminal buffers it so meh
|
|
||||||
terminal.write(buffer[0 .. l]);
|
|
||||||
|
|
||||||
written++;
|
|
||||||
lineLength--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: if there is a color at the end of the line it messes up as you scroll
|
|
||||||
// FIXME: need a way to go to multi-line editing
|
|
||||||
|
|
||||||
foreach(dchar ch; towrite) {
|
|
||||||
if(lineLength == 0)
|
|
||||||
break;
|
|
||||||
switch(ch) {
|
|
||||||
case '\n': specialChar('n'); break;
|
|
||||||
case '\r': specialChar('r'); break;
|
|
||||||
case '\a': specialChar('a'); break;
|
|
||||||
case '\t': specialChar('t'); break;
|
|
||||||
case '\b': specialChar('b'); break;
|
|
||||||
case '\033': specialChar('e'); break;
|
|
||||||
default:
|
|
||||||
regularChar(ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string suggestion;
|
string suggestion;
|
||||||
|
|
||||||
if(lineLength >= 0) {
|
if(drawer.lineLength >= 0) {
|
||||||
suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null;
|
suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null;
|
||||||
if(suggestion.length) {
|
if(suggestion.length) {
|
||||||
terminal.color(suggestionForeground, background);
|
terminal.color(suggestionForeground, background);
|
||||||
foreach(dchar ch; suggestion) {
|
foreach(dchar ch; suggestion) {
|
||||||
if(lineLength == 0)
|
if(drawer.lineLength == 0)
|
||||||
break;
|
break;
|
||||||
regularChar(ch);
|
drawer.regularChar(ch);
|
||||||
}
|
}
|
||||||
terminal.color(regularForeground, background);
|
terminal.color(regularForeground, background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: graphemes
|
CoreRedrawInfo cri;
|
||||||
|
cri.populated = true;
|
||||||
|
cri.written = drawer.written;
|
||||||
|
cri.cursorPositionToDrawX = cursorPositionToDrawX;
|
||||||
|
cri.cursorPositionToDrawY = cursorPositionToDrawY;
|
||||||
|
|
||||||
if(written < lastDrawLength)
|
return cri;
|
||||||
foreach(i; written .. lastDrawLength)
|
|
||||||
terminal.write(" ");
|
|
||||||
lastDrawLength = written;
|
|
||||||
|
|
||||||
terminal.moveTo(startOfLineX + cursorPositionToDrawX + promptLength, startOfLineY + cursorPositionToDrawY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
|
/// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
|
||||||
|
@ -4920,7 +4989,7 @@ class LineGetter {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeWithSize(bool firstEver = false) {
|
protected void initializeWithSize(bool firstEver = false) {
|
||||||
auto x = startOfLineX;
|
auto x = startOfLineX;
|
||||||
|
|
||||||
updateCursorPosition();
|
updateCursorPosition();
|
||||||
|
@ -4937,7 +5006,7 @@ class LineGetter {
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCursorPosition() {
|
protected void updateCursorPosition() {
|
||||||
terminal.flush();
|
terminal.flush();
|
||||||
|
|
||||||
// then get the current cursor position to start fresh
|
// then get the current cursor position to start fresh
|
||||||
|
@ -5100,6 +5169,8 @@ class LineGetter {
|
||||||
|
|
||||||
private bool maintainBuffer;
|
private bool maintainBuffer;
|
||||||
|
|
||||||
|
private LineGetter supplementalGetter;
|
||||||
|
|
||||||
/++
|
/++
|
||||||
for integrating into another event loop
|
for integrating into another event loop
|
||||||
you can pass individual events to this and
|
you can pass individual events to this and
|
||||||
|
@ -5113,6 +5184,18 @@ class LineGetter {
|
||||||
where the event came from.
|
where the event came from.
|
||||||
+/
|
+/
|
||||||
bool workOnLine(InputEvent e, RealTimeConsoleInput* rtti = null) {
|
bool workOnLine(InputEvent e, RealTimeConsoleInput* rtti = null) {
|
||||||
|
if(supplementalGetter) {
|
||||||
|
if(!supplementalGetter.workOnLine(e, rtti)) {
|
||||||
|
auto got = supplementalGetter.finishGettingLine();
|
||||||
|
// the supplementalGetter will poke our own state directly
|
||||||
|
// so i can ignore the return value here...
|
||||||
|
|
||||||
|
supplementalGetter = null;
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
switch(e.type) {
|
switch(e.type) {
|
||||||
case InputEvent.Type.EndOfFileEvent:
|
case InputEvent.Type.EndOfFileEvent:
|
||||||
justHitTab = false;
|
justHitTab = false;
|
||||||
|
@ -5182,10 +5265,12 @@ class LineGetter {
|
||||||
case '\b':
|
case '\b':
|
||||||
justHitTab = false;
|
justHitTab = false;
|
||||||
if(ev.modifierState & ModifierState.control) {
|
if(ev.modifierState & ModifierState.control) {
|
||||||
|
lineChanged = true;
|
||||||
killWord();
|
killWord();
|
||||||
justKilled = true;
|
justKilled = true;
|
||||||
redraw();
|
redraw();
|
||||||
} else if(cursorPosition) {
|
} else if(cursorPosition) {
|
||||||
|
lineChanged = true;
|
||||||
justKilled = false;
|
justKilled = false;
|
||||||
cursorPosition--;
|
cursorPosition--;
|
||||||
for(int i = cursorPosition; i < line.length - 1; i++)
|
for(int i = cursorPosition; i < line.length - 1; i++)
|
||||||
|
@ -5225,7 +5310,7 @@ class LineGetter {
|
||||||
positionCursor();
|
positionCursor();
|
||||||
redraw();
|
redraw();
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r', 18:
|
||||||
if(!(ev.modifierState & ModifierState.control))
|
if(!(ev.modifierState & ModifierState.control))
|
||||||
goto default;
|
goto default;
|
||||||
goto case;
|
goto case;
|
||||||
|
@ -5233,6 +5318,9 @@ class LineGetter {
|
||||||
justHitTab = justKilled = false;
|
justHitTab = justKilled = false;
|
||||||
// search in history
|
// search in history
|
||||||
// FIXME: what about search in completion too?
|
// FIXME: what about search in completion too?
|
||||||
|
supplementalGetter = new HistorySearchLineGetter(this);
|
||||||
|
supplementalGetter.startGettingLine();
|
||||||
|
supplementalGetter.redraw();
|
||||||
break;
|
break;
|
||||||
case 'u', 21:
|
case 'u', 21:
|
||||||
if(!(ev.modifierState & ModifierState.control))
|
if(!(ev.modifierState & ModifierState.control))
|
||||||
|
@ -5473,6 +5561,43 @@ class LineGetter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Replaces the line currently being edited with the given line and positions the cursor inside it.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added November 27, 2020.
|
||||||
|
+/
|
||||||
|
void replaceLine(const scope dchar[] line) {
|
||||||
|
if(this.line.length < line.length)
|
||||||
|
this.line.length = line.length;
|
||||||
|
else
|
||||||
|
this.line = this.line[0 .. line.length];
|
||||||
|
this.line.assumeSafeAppend();
|
||||||
|
this.line[] = line[];
|
||||||
|
if(cursorPosition > line.length)
|
||||||
|
cursorPosition = cast(int) line.length;
|
||||||
|
if(horizontalScrollPosition > line.length)
|
||||||
|
horizontalScrollPosition = cast(int) line.length;
|
||||||
|
positionCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void replaceLine(const scope char[] line) {
|
||||||
|
if(line.length >= 255) {
|
||||||
|
import std.conv;
|
||||||
|
replaceLine(to!dstring(line));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dchar[255] tmp;
|
||||||
|
size_t idx;
|
||||||
|
foreach(dchar c; line) {
|
||||||
|
tmp[idx++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceLine(tmp[0 .. idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
string finishGettingLine() {
|
string finishGettingLine() {
|
||||||
import std.conv;
|
import std.conv;
|
||||||
auto f = to!string(line);
|
auto f = to!string(line);
|
||||||
|
@ -5485,6 +5610,151 @@ class LineGetter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HistorySearchLineGetter : LineGetter {
|
||||||
|
LineGetter basedOn;
|
||||||
|
string sideDisplay;
|
||||||
|
this(LineGetter basedOn) {
|
||||||
|
this.basedOn = basedOn;
|
||||||
|
super(basedOn.terminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
override void updateCursorPosition() {
|
||||||
|
super.updateCursorPosition();
|
||||||
|
startOfLineX = basedOn.startOfLineX;
|
||||||
|
startOfLineY = basedOn.startOfLineY;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void initializeWithSize(bool firstEver = false) {
|
||||||
|
if(terminal.width > 60)
|
||||||
|
this.prompt = "(history search): \"";
|
||||||
|
else
|
||||||
|
this.prompt = "(hs): \"";
|
||||||
|
super.initializeWithSize(firstEver);
|
||||||
|
}
|
||||||
|
|
||||||
|
override int availableLineLength() {
|
||||||
|
return terminal.width / 2 - startOfLineX - promptLength - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void loadFromHistory(int howFarBack) {
|
||||||
|
currentHistoryViewPosition = howFarBack;
|
||||||
|
reloadSideDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
int highlightBegin;
|
||||||
|
int highlightEnd;
|
||||||
|
|
||||||
|
void reloadSideDisplay() {
|
||||||
|
import std.string;
|
||||||
|
import std.range;
|
||||||
|
int counter = currentHistoryViewPosition;
|
||||||
|
|
||||||
|
string lastHit;
|
||||||
|
int hb, he;
|
||||||
|
if(line.length)
|
||||||
|
foreach_reverse(item; basedOn.history) {
|
||||||
|
auto idx = item.indexOf(line);
|
||||||
|
if(idx != -1) {
|
||||||
|
hb = cast(int) idx;
|
||||||
|
he = cast(int) (idx + line.walkLength);
|
||||||
|
lastHit = item;
|
||||||
|
if(counter)
|
||||||
|
counter--;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sideDisplay = lastHit;
|
||||||
|
highlightBegin = hb;
|
||||||
|
highlightEnd = he;
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool redrawQueued = false;
|
||||||
|
override void redraw() {
|
||||||
|
redrawQueued = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void actualRedraw() {
|
||||||
|
auto cri = coreRedraw();
|
||||||
|
terminal.write("\" ");
|
||||||
|
|
||||||
|
int available = terminal.width / 2 - 1;
|
||||||
|
auto used = prompt.length + cri.written + 3 /* the write above plus a space */;
|
||||||
|
if(used < available)
|
||||||
|
available += available - used;
|
||||||
|
|
||||||
|
//terminal.moveTo(terminal.width / 2, startOfLineY);
|
||||||
|
Drawer drawer = Drawer(this);
|
||||||
|
drawer.lineLength = available;
|
||||||
|
drawer.drawContent(sideDisplay, highlightBegin, highlightEnd);
|
||||||
|
|
||||||
|
cri.written += drawer.written;
|
||||||
|
|
||||||
|
finalizeRedraw(cri);
|
||||||
|
}
|
||||||
|
|
||||||
|
override bool workOnLine(InputEvent e, RealTimeConsoleInput* rtti = null) {
|
||||||
|
scope(exit) {
|
||||||
|
if(redrawQueued) {
|
||||||
|
actualRedraw();
|
||||||
|
redrawQueued = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(e.type == InputEvent.Type.KeyboardEvent) {
|
||||||
|
auto ev = e.keyboardEvent;
|
||||||
|
if(ev.pressed == false)
|
||||||
|
return true;
|
||||||
|
/* Insert the character (unless it is backspace, tab, or some other control char) */
|
||||||
|
auto ch = ev.which;
|
||||||
|
switch(ch) {
|
||||||
|
// modification being the search through history commands
|
||||||
|
// should just keep searching, not endlessly nest.
|
||||||
|
case 'r', 18:
|
||||||
|
if(!(ev.modifierState & ModifierState.control))
|
||||||
|
goto default;
|
||||||
|
goto case;
|
||||||
|
case KeyboardEvent.Key.F3:
|
||||||
|
e.keyboardEvent.which = KeyboardEvent.Key.UpArrow;
|
||||||
|
break;
|
||||||
|
case KeyboardEvent.Key.escape:
|
||||||
|
sideDisplay = null;
|
||||||
|
return false; // cancel
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(super.workOnLine(e, rtti)) {
|
||||||
|
if(lineChanged) {
|
||||||
|
currentHistoryViewPosition = 0;
|
||||||
|
reloadSideDisplay();
|
||||||
|
lineChanged = false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void startGettingLine() {
|
||||||
|
super.startGettingLine();
|
||||||
|
this.line = basedOn.line.dup;
|
||||||
|
cursorPosition = cast(int) this.line.length;
|
||||||
|
startOfLineX = basedOn.startOfLineX;
|
||||||
|
startOfLineY = basedOn.startOfLineY;
|
||||||
|
positionCursor();
|
||||||
|
reloadSideDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
override string finishGettingLine() {
|
||||||
|
auto got = super.finishGettingLine();
|
||||||
|
|
||||||
|
if(sideDisplay.length)
|
||||||
|
basedOn.replaceLine(sideDisplay);
|
||||||
|
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds default constructors that just forward to the superclass
|
/// Adds default constructors that just forward to the superclass
|
||||||
mixin template LineGetterConstructors() {
|
mixin template LineGetterConstructors() {
|
||||||
this(Terminal* tty, string historyFilename = null) {
|
this(Terminal* tty, string historyFilename = null) {
|
||||||
|
|
Loading…
Reference in New Issue