diff --git a/data/gsettings/com.gexperts.Tilix.gschema.xml b/data/gsettings/com.gexperts.Tilix.gschema.xml
index 54f33a00..98a893ce 100644
--- a/data/gsettings/com.gexperts.Tilix.gschema.xml
+++ b/data/gsettings/com.gexperts.Tilix.gschema.xml
@@ -1214,6 +1214,15 @@
'disabled'
Keyboard shortcut to open directory in file browser
+
+
+ 'disabled'
+ Keyboard shortcut to move to next prompt
+
+
+ 'disabled'
+ Keyboard shortcut to move to previous prompt
+
'<Ctrl><Alt>t'
diff --git a/data/resources/ui/shortcuts.ui b/data/resources/ui/shortcuts.ui
index 114035a1..b2cae438 100644
--- a/data/resources/ui/shortcuts.ui
+++ b/data/resources/ui/shortcuts.ui
@@ -479,6 +479,18 @@
Page down
+
+
+
+
+
+
diff --git a/source/gx/tilix/prefeditor/prefdialog.d b/source/gx/tilix/prefeditor/prefdialog.d
index 9e370697..886fc36d 100644
--- a/source/gx/tilix/prefeditor/prefdialog.d
+++ b/source/gx/tilix/prefeditor/prefdialog.d
@@ -976,10 +976,18 @@ private:
return result;
}
+ TerminalFeature[string] getVTEFeatureShortcuts() {
+ TerminalFeature[string] result;
+ result["terminal-next-prompt"] = TerminalFeature.EVENT_SCREEN_CHANGED;
+ result["terminal-previous-prompt"] = TerminalFeature.EVENT_SCREEN_CHANGED;
+ return result;
+ }
+
void loadShortcuts(TreeStore ts) {
int[2][string] gtkVersioned = getGTKVersionedShortcuts();
int[2][string] vteVersioned = getVTEVersionedShortcuts();
+ TerminalFeature[string] vteFeatured = getVTEFeatureShortcuts();
loadLocalizedShortcutLabels();
string[] keys = gsShortcuts.listKeys();
@@ -997,6 +1005,9 @@ private:
int[2] vteVersion = vteVersioned[key];
if (!checkVTEVersionNumber(vteVersion[0], vteVersion[1])) continue;
}
+ if (key in vteFeatured) {
+ if (!checkVTEFeature(vteFeatured[key])) continue;
+ }
string prefix, id;
getActionNameFromKey(key, prefix, id);
diff --git a/source/gx/tilix/terminal/actions.d b/source/gx/tilix/terminal/actions.d
index 3e692e20..cd6ed83d 100644
--- a/source/gx/tilix/terminal/actions.d
+++ b/source/gx/tilix/terminal/actions.d
@@ -41,4 +41,6 @@ enum ACTION_SCROLL_DOWN = "scroll-down";
enum ACTION_PAGE_UP = "page-up";
enum ACTION_PAGE_DOWN = "page-down";
enum ACTION_MONITOR_SILENCE = "monitor-silence";
-enum ACTION_FILE_BROWSER = "file-browser";
\ No newline at end of file
+enum ACTION_FILE_BROWSER = "file-browser";
+enum ACTION_PREVIOUS_PROMPT = "previous-prompt";
+enum ACTION_NEXT_PROMPT = "next-prompt";
\ No newline at end of file
diff --git a/source/gx/tilix/terminal/terminal.d b/source/gx/tilix/terminal/terminal.d
index 05754ba4..45673d8c 100644
--- a/source/gx/tilix/terminal/terminal.d
+++ b/source/gx/tilix/terminal/terminal.d
@@ -22,6 +22,7 @@ import std.format;
import std.json;
import std.math;
import std.process;
+import std.range;
import std.regex;
import std.stdio;
import std.string;
@@ -567,6 +568,16 @@ private:
}
});
+ if (checkVTEFeature(TerminalFeature.EVENT_SCREEN_CHANGED)) {
+ registerActionWithSettings(group, ACTION_PREFIX, ACTION_NEXT_PROMPT, gsShortcuts, delegate(GVariant, SimpleAction) {
+ moveToPrompt(1);
+ });
+ registerActionWithSettings(group, ACTION_PREFIX, ACTION_PREVIOUS_PROMPT, gsShortcuts, delegate(GVariant, SimpleAction) {
+ moveToPrompt(-1);
+ });
+ }
+
+
registerActionWithSettings(group, ACTION_PREFIX, ACTION_SELECT_ALL, gsShortcuts, delegate(GVariant, SimpleAction) { vte.selectAll(); });
//Link Actions, no shortcuts, context menu only
@@ -667,6 +678,8 @@ private:
});
registerActionWithSettings(group, ACTION_PREFIX, ACTION_RESET_AND_CLEAR, gsShortcuts, delegate(GVariant, SimpleAction) {
vte.reset(true, true);
+ // Clear history of prompts
+ checkPromptBuffer();
if (isSynchronizedInput()) {
SyncInputEvent se = SyncInputEvent(_terminalUUID, SyncInputEventType.RESET_AND_CLEAR, null, null);
onSyncInput.emit(this, se);
@@ -1028,6 +1041,13 @@ private:
vteHandlers ~= vte.addOnKeyPress(delegate(Event event, Widget widget) {
if (vte is null) return false;
+ if (event.key.keyval == GdkKeysyms.GDK_Return && checkVTEFeature(TerminalFeature.EVENT_SCREEN_CHANGED) && currentScreen == TerminalScreen.NORMAL) {
+ long row, column;
+ vte.getCursorPosition(column, row);
+ promptPosition ~= [row];
+ tracef("Added prompt position %d", row);
+ }
+
if (isSynchronizedInput() && event.key.sendEvent != SendEvent.SYNC) {
static if (USE_COMMIT_SYNCHRONIZATION) {
// Only synchronize hard code VTE keys otherwise let commit event take care of it
@@ -1060,6 +1080,15 @@ private:
});
}
+ // Used to track changes to scroll buffer to clear
+ // prompt positions if user cleared VTE buffer, i.e. "clear" command.
+ // Used for terminal-next-prompt and terminal-previous-prompt actions
+ if (checkVTEFeature(TerminalFeature.EVENT_SCREEN_CHANGED)) {
+ vte.addOnTextDeleted(delegate(VTE) {
+ checkPromptBuffer();
+ });
+ }
+
pmContext = new Popover(vte);
pmContext.setModal(true);
pmContext.setPosition(PositionType.BOTTOM);
@@ -1476,6 +1505,41 @@ private:
onSessionAttach.emit(this, sessionUUID);
}
+// Block for handling cycling between command prompts
+private:
+ long[] promptPosition;
+
+ void moveToPrompt(int direction) {
+ if (!checkVTEFeature(TerminalFeature.EVENT_SCREEN_CHANGED) || currentScreen != TerminalScreen.NORMAL) return;
+ long result;
+ long row = to!long(vte.getVadjustment().getValue());
+ auto sorted = assumeSorted(promptPosition);
+ if (direction < 0) {
+ auto range = sorted.lowerBound(row);
+ if (range.length > 0) {
+ result = range[range.length -1];
+ } else result = 0;
+ } else {
+ auto range = sorted.upperBound(row);
+ if (range.length > 0) {
+ result = range[0];
+ } else result = to!long(vte.getVadjustment().getUpper());
+ }
+ tracef("Current row %d, Moving to command prompt at %d", row, result);
+ vte.getVadjustment.setValue(to!double(result));
+ }
+
+ void checkPromptBuffer() {
+ if (!checkVTEFeature(TerminalFeature.EVENT_SCREEN_CHANGED) || currentScreen != TerminalScreen.NORMAL) return;
+ if (promptPosition.length == 0) return;
+ // If upper bound of last recorded prompt is bigger then current upper bound of rows user must have cleared buffer, i.e. clear command
+ tracef("Check position %d against buffer size %f", promptPosition[promptPosition.length -1], vte.getVadjustment().getUpper());
+ if (promptPosition[promptPosition.length -1] < vte.getVadjustment().getUpper()) {
+ promptPosition = [];
+ trace("Cleared prompt positions");
+ }
+ }
+
// Block for processing triggers
private:
@@ -3689,6 +3753,7 @@ public:
break;
case SyncInputEventType.RESET_AND_CLEAR:
vte.reset(true, true);
+ checkPromptBuffer();
break;
}
}