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 + + + 1 + Previous prompt + + + + + 1 + Next prompt + + 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; } }