mirror of https://github.com/adamdruppe/arsd.git
horizontal scroll on getline
This commit is contained in:
parent
8237be0d8b
commit
7d7b37273d
143
terminal.d
143
terminal.d
|
@ -1133,6 +1133,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
|
||||||
// it technically might, I'm updating the pointer before using it just in case.
|
// it technically might, I'm updating the pointer before using it just in case.
|
||||||
lineGetter.terminal = &this;
|
lineGetter.terminal = &this;
|
||||||
|
|
||||||
|
if(prompt !is null)
|
||||||
lineGetter.prompt = prompt;
|
lineGetter.prompt = prompt;
|
||||||
|
|
||||||
auto line = lineGetter.getline();
|
auto line = lineGetter.getline();
|
||||||
|
@ -2297,14 +2298,15 @@ void main() {
|
||||||
//terminal.color(Color.DEFAULT, Color.DEFAULT);
|
//terminal.color(Color.DEFAULT, Color.DEFAULT);
|
||||||
|
|
||||||
//
|
//
|
||||||
/*
|
///*
|
||||||
auto getter = new LineGetter(&terminal, "test");
|
auto getter = new FileLineGetter(&terminal, "test");
|
||||||
getter.prompt = "> ";
|
getter.prompt = "> ";
|
||||||
|
getter.history = ["abcdefghijklmnopqrstuvwzyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"];
|
||||||
terminal.writeln("\n" ~ getter.getline());
|
terminal.writeln("\n" ~ getter.getline());
|
||||||
terminal.writeln("\n" ~ getter.getline());
|
terminal.writeln("\n" ~ getter.getline());
|
||||||
terminal.writeln("\n" ~ getter.getline());
|
terminal.writeln("\n" ~ getter.getline());
|
||||||
getter.dispose();
|
getter.dispose();
|
||||||
*/
|
//*/
|
||||||
|
|
||||||
terminal.writeln(terminal.getline());
|
terminal.writeln(terminal.getline());
|
||||||
terminal.writeln(terminal.getline());
|
terminal.writeln(terminal.getline());
|
||||||
|
@ -2472,7 +2474,7 @@ class LineGetter {
|
||||||
/// Override this to change the directory where history files are stored
|
/// Override this to change the directory where history files are stored
|
||||||
///
|
///
|
||||||
/// Default is $HOME/.arsd-getline on linux and %APPDATA%/arsd-getline/ on Windows.
|
/// Default is $HOME/.arsd-getline on linux and %APPDATA%/arsd-getline/ on Windows.
|
||||||
string historyFileDirectory() {
|
/* virtual */ string historyFileDirectory() {
|
||||||
version(Windows) {
|
version(Windows) {
|
||||||
char[1024] path;
|
char[1024] path;
|
||||||
// FIXME: this doesn't link because the crappy dmd lib doesn't have it
|
// FIXME: this doesn't link because the crappy dmd lib doesn't have it
|
||||||
|
@ -2508,12 +2510,12 @@ class LineGetter {
|
||||||
|
|
||||||
/// Override this if you don't want all lines added to the history.
|
/// Override this if you don't want all lines added to the history.
|
||||||
/// You can return null to not add it at all, or you can transform it.
|
/// You can return null to not add it at all, or you can transform it.
|
||||||
string historyFilter(string candidate) {
|
/* virtual */ string historyFilter(string candidate) {
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You may override this to do nothing
|
/// You may override this to do nothing
|
||||||
void saveSettingsAndHistoryToFile() {
|
/* virtual */ void saveSettingsAndHistoryToFile() {
|
||||||
import std.file;
|
import std.file;
|
||||||
if(!exists(historyFileDirectory))
|
if(!exists(historyFileDirectory))
|
||||||
mkdir(historyFileDirectory);
|
mkdir(historyFileDirectory);
|
||||||
|
@ -2531,7 +2533,7 @@ class LineGetter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You may override this to do nothing
|
/// You may override this to do nothing
|
||||||
void loadSettingsAndHistoryFromFile() {
|
/* virtual */ void loadSettingsAndHistoryFromFile() {
|
||||||
import std.file;
|
import std.file;
|
||||||
history = null;
|
history = null;
|
||||||
auto fn = historyPath();
|
auto fn = historyPath();
|
||||||
|
@ -2553,7 +2555,7 @@ class LineGetter {
|
||||||
|
|
||||||
Default is to provide recent command history as autocomplete.
|
Default is to provide recent command history as autocomplete.
|
||||||
*/
|
*/
|
||||||
protected string[] tabComplete(in dchar[] candidate) {
|
/* virtual */ protected string[] tabComplete(in dchar[] candidate) {
|
||||||
return history.length > 20 ? history[0 .. 20] : history;
|
return history.length > 20 ? history[0 .. 20] : history;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2578,6 +2580,7 @@ class LineGetter {
|
||||||
if(list.length) {
|
if(list.length) {
|
||||||
// FIXME: allow mouse clicking of an item, that would be cool
|
// FIXME: allow mouse clicking of an item, that would be cool
|
||||||
|
|
||||||
|
// FIXME: scroll
|
||||||
//if(terminal.type == ConsoleOutputType.linear) {
|
//if(terminal.type == ConsoleOutputType.linear) {
|
||||||
terminal.writeln();
|
terminal.writeln();
|
||||||
foreach(item; list) {
|
foreach(item; list) {
|
||||||
|
@ -2642,18 +2645,34 @@ class LineGetter {
|
||||||
}
|
}
|
||||||
|
|
||||||
cursorPosition = cast(int) line.length;
|
cursorPosition = cast(int) line.length;
|
||||||
|
scrollToEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool insertMode = true;
|
bool insertMode = true;
|
||||||
|
bool multiLineMode = false;
|
||||||
|
|
||||||
private dchar[] line;
|
private dchar[] line;
|
||||||
private int cursorPosition = 0;
|
private int cursorPosition = 0;
|
||||||
|
private int horizontalScrollPosition = 0;
|
||||||
|
|
||||||
|
private void scrollToEnd() {
|
||||||
|
horizontalScrollPosition = (cast(int) line.length);
|
||||||
|
horizontalScrollPosition -= availableLineLength();
|
||||||
|
if(horizontalScrollPosition < 0)
|
||||||
|
horizontalScrollPosition = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// used for redrawing the line in the right place
|
// used for redrawing the line in the right place
|
||||||
// and detecting mouse events on our line.
|
// and detecting mouse events on our line.
|
||||||
private int startOfLineX;
|
private int startOfLineX;
|
||||||
private int startOfLineY;
|
private int startOfLineY;
|
||||||
|
|
||||||
|
// private string[] cachedCompletionList;
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
// /// Note that this assumes the tab complete list won't change between actual
|
||||||
|
// /// presses of tab by the user. If you pass it a list, it will use it, but
|
||||||
|
// /// otherwise it will keep track of the last one to avoid calls to tabComplete.
|
||||||
private string suggestion(string[] list = null) {
|
private string suggestion(string[] list = null) {
|
||||||
import std.algorithm, std.utf;
|
import std.algorithm, std.utf;
|
||||||
auto relevantLineSection = line[0 .. cursorPosition];
|
auto relevantLineSection = line[0 .. cursorPosition];
|
||||||
|
@ -2691,6 +2710,9 @@ class LineGetter {
|
||||||
line[cursorPosition] = ch;
|
line[cursorPosition] = ch;
|
||||||
}
|
}
|
||||||
cursorPosition++;
|
cursorPosition++;
|
||||||
|
|
||||||
|
if(cursorPosition >= horizontalScrollPosition + availableLineLength())
|
||||||
|
horizontalScrollPosition++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// .
|
/// .
|
||||||
|
@ -2712,26 +2734,50 @@ class LineGetter {
|
||||||
line.assumeSafeAppend();
|
line.assumeSafeAppend();
|
||||||
}
|
}
|
||||||
|
|
||||||
int lastDrawLength = 0;
|
int availableLineLength() {
|
||||||
|
return terminal.width - startOfLineX - cast(int) prompt.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lastDrawLength = 0;
|
||||||
void redraw() {
|
void redraw() {
|
||||||
terminal.moveTo(startOfLineX, startOfLineY);
|
terminal.moveTo(startOfLineX, startOfLineY);
|
||||||
|
|
||||||
|
auto lineLength = availableLineLength();
|
||||||
|
if(lineLength < 0)
|
||||||
|
throw new Exception("too narrow terminal to draw");
|
||||||
|
|
||||||
terminal.write(prompt);
|
terminal.write(prompt);
|
||||||
|
|
||||||
terminal.write(line);
|
auto towrite = line[horizontalScrollPosition .. $];
|
||||||
auto suggestion = ((cursorPosition == line.length) && autoSuggest) ? this.suggestion() : null;
|
auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition;
|
||||||
|
auto cursorPositionToDrawY = 0;
|
||||||
|
|
||||||
|
if(towrite.length > lineLength) {
|
||||||
|
towrite = towrite[0 .. lineLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.write(towrite);
|
||||||
|
|
||||||
|
lineLength -= towrite.length;
|
||||||
|
|
||||||
|
if(lineLength >= 0) {
|
||||||
|
auto suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null;
|
||||||
if(suggestion.length) {
|
if(suggestion.length) {
|
||||||
terminal.color(suggestionForeground, background);
|
terminal.color(suggestionForeground, background);
|
||||||
terminal.write(suggestion);
|
terminal.write(suggestion);
|
||||||
terminal.color(regularForeground, background);
|
terminal.color(regularForeground, background);
|
||||||
}
|
}
|
||||||
if(line.length < lastDrawLength)
|
}
|
||||||
foreach(i; line.length + suggestion.length + prompt.length .. lastDrawLength)
|
|
||||||
terminal.write(" ");
|
|
||||||
lastDrawLength = cast(int) (line.length + suggestion.length + prompt.length); // FIXME: graphemes and utf-8 on suggestion/prompt
|
|
||||||
|
|
||||||
// FIXME: wrapping
|
// FIXME: graphemes and utf-8 on suggestion/prompt
|
||||||
terminal.moveTo(startOfLineX + cursorPosition + cast(int) prompt.length, startOfLineY);
|
auto written = cast(int) (towrite.length + suggestion.length + prompt.length);
|
||||||
|
|
||||||
|
if(written < lastDrawLength)
|
||||||
|
foreach(i; written .. lastDrawLength)
|
||||||
|
terminal.write(" ");
|
||||||
|
lastDrawLength = written;
|
||||||
|
|
||||||
|
terminal.moveTo(startOfLineX + cursorPositionToDrawX + cast(int) prompt.length, startOfLineY + cursorPositionToDrawY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
|
/// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
|
||||||
|
@ -2741,7 +2787,7 @@ class LineGetter {
|
||||||
void startGettingLine() {
|
void startGettingLine() {
|
||||||
// reset from any previous call first
|
// reset from any previous call first
|
||||||
cursorPosition = 0;
|
cursorPosition = 0;
|
||||||
lastDrawLength = 0;
|
horizontalScrollPosition = 0;
|
||||||
justHitTab = false;
|
justHitTab = false;
|
||||||
currentHistoryViewPosition = 0;
|
currentHistoryViewPosition = 0;
|
||||||
if(line.length) {
|
if(line.length) {
|
||||||
|
@ -2752,7 +2798,7 @@ class LineGetter {
|
||||||
updateCursorPosition();
|
updateCursorPosition();
|
||||||
terminal.showCursor();
|
terminal.showCursor();
|
||||||
|
|
||||||
lastDrawLength = terminal.width; // setting this so it clears the line
|
lastDrawLength = availableLineLength();
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2876,6 +2922,14 @@ class LineGetter {
|
||||||
line[i] = line[i + 1];
|
line[i] = line[i + 1];
|
||||||
line = line[0 .. $ - 1];
|
line = line[0 .. $ - 1];
|
||||||
line.assumeSafeAppend();
|
line.assumeSafeAppend();
|
||||||
|
|
||||||
|
if(!multiLineMode) {
|
||||||
|
if(horizontalScrollPosition > cursorPosition - 1)
|
||||||
|
horizontalScrollPosition = cursorPosition - 1 - availableLineLength();
|
||||||
|
if(horizontalScrollPosition < 0)
|
||||||
|
horizontalScrollPosition = 0;
|
||||||
|
}
|
||||||
|
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -2895,11 +2949,21 @@ class LineGetter {
|
||||||
case NonCharacterKeyEvent.Key.LeftArrow:
|
case NonCharacterKeyEvent.Key.LeftArrow:
|
||||||
if(cursorPosition)
|
if(cursorPosition)
|
||||||
cursorPosition--;
|
cursorPosition--;
|
||||||
|
if(!multiLineMode) {
|
||||||
|
if(cursorPosition < horizontalScrollPosition)
|
||||||
|
horizontalScrollPosition--;
|
||||||
|
}
|
||||||
|
|
||||||
redraw();
|
redraw();
|
||||||
break;
|
break;
|
||||||
case NonCharacterKeyEvent.Key.RightArrow:
|
case NonCharacterKeyEvent.Key.RightArrow:
|
||||||
if(cursorPosition < line.length)
|
if(cursorPosition < line.length)
|
||||||
cursorPosition++;
|
cursorPosition++;
|
||||||
|
if(!multiLineMode) {
|
||||||
|
if(cursorPosition >= horizontalScrollPosition + availableLineLength())
|
||||||
|
horizontalScrollPosition++;
|
||||||
|
}
|
||||||
|
|
||||||
redraw();
|
redraw();
|
||||||
break;
|
break;
|
||||||
case NonCharacterKeyEvent.Key.UpArrow:
|
case NonCharacterKeyEvent.Key.UpArrow:
|
||||||
|
@ -2920,10 +2984,12 @@ class LineGetter {
|
||||||
break;
|
break;
|
||||||
case NonCharacterKeyEvent.Key.Home:
|
case NonCharacterKeyEvent.Key.Home:
|
||||||
cursorPosition = 0;
|
cursorPosition = 0;
|
||||||
|
horizontalScrollPosition = 0;
|
||||||
redraw();
|
redraw();
|
||||||
break;
|
break;
|
||||||
case NonCharacterKeyEvent.Key.End:
|
case NonCharacterKeyEvent.Key.End:
|
||||||
cursorPosition = cast(int) line.length;
|
cursorPosition = cast(int) line.length;
|
||||||
|
scrollToEnd();
|
||||||
redraw();
|
redraw();
|
||||||
break;
|
break;
|
||||||
case NonCharacterKeyEvent.Key.Insert:
|
case NonCharacterKeyEvent.Key.Insert:
|
||||||
|
@ -2953,7 +3019,7 @@ class LineGetter {
|
||||||
if(me.buttons & MouseEvent.Button.Left) {
|
if(me.buttons & MouseEvent.Button.Left) {
|
||||||
if(me.y == startOfLineY) {
|
if(me.y == startOfLineY) {
|
||||||
// FIXME: prompt.length should be graphemes or at least code poitns
|
// FIXME: prompt.length should be graphemes or at least code poitns
|
||||||
int p = me.x - startOfLineX - cast(int) prompt.length;
|
int p = me.x - startOfLineX - cast(int) prompt.length + horizontalScrollPosition;
|
||||||
if(p >= 0 && p < line.length) {
|
if(p >= 0 && p < line.length) {
|
||||||
justHitTab = false;
|
justHitTab = false;
|
||||||
cursorPosition = p;
|
cursorPosition = p;
|
||||||
|
@ -2995,6 +3061,43 @@ class LineGetter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds default constructors that just forward to the superclass
|
||||||
|
mixin template LineGetterConstructors() {
|
||||||
|
this(Terminal* tty, string historyFilename = null) {
|
||||||
|
super(tty, historyFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a line getter that customizes the tab completion to
|
||||||
|
/// fill in file names separated by spaces, like a command line thing.
|
||||||
|
class FileLineGetter : LineGetter {
|
||||||
|
mixin LineGetterConstructors;
|
||||||
|
|
||||||
|
/// You can set this property to tell it where to search for the files
|
||||||
|
/// to complete.
|
||||||
|
string searchDirectory = ".";
|
||||||
|
|
||||||
|
override protected string[] tabComplete(in dchar[] candidate) {
|
||||||
|
import std.file, std.conv, std.algorithm, std.string;
|
||||||
|
const(dchar)[] soFar = candidate;
|
||||||
|
auto idx = candidate.lastIndexOf(" ");
|
||||||
|
if(idx != -1)
|
||||||
|
soFar = candidate[idx + 1 .. $];
|
||||||
|
|
||||||
|
string[] list;
|
||||||
|
foreach(string name; dirEntries(searchDirectory, SpanMode.breadth)) {
|
||||||
|
// try without the ./
|
||||||
|
if(startsWith(name[2..$], soFar))
|
||||||
|
list ~= text(candidate, name[searchDirectory.length + 1 + soFar.length .. $]);
|
||||||
|
else // and with
|
||||||
|
if(startsWith(name, soFar))
|
||||||
|
list ~= text(candidate, name[soFar.length .. $]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
version(Windows) {
|
version(Windows) {
|
||||||
// to get the directory for saving history in the line things
|
// to get the directory for saving history in the line things
|
||||||
enum CSIDL_APPDATA = 26;
|
enum CSIDL_APPDATA = 26;
|
||||||
|
|
Loading…
Reference in New Issue