Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ All notable changes to Open Stus are recorded here. The format follows

### Added

- **Shift+Enter opens outside Open Stus.** On a folder it opens the system file manager there
(Explorer on Windows, the desktop's own elsewhere) - on `..` the folder the panel shows; on a file
it starts the file with its associated program and comes straight back. Inside an archive a file
is taken out first, and a folder opens the archive file itself.

- **A click on the sort letter changes the drive.** The letter in the top-left corner of a panel's
column titles (`n` for by name) opens that panel's drive menu, as `Alt+F1` / `Alt+F2` do.

- **Archives open in the panel.** Enter (or `Ctrl+PgDn`) on a zip - and its relatives jar, war,
nupkg, vsix, whl, apk - a tar, a `.tar.gz` / `.tgz` or a lone `.gz` walks into it as if it were a
folder: the title and the prompt show `...\tools.zip\bin`, `..` climbs back out onto the archive,
Expand Down Expand Up @@ -177,6 +185,14 @@ the four reported issues at the top of this list.

### Changed

- **Enter no longer waits for Word or Excel.** A document, or a program with a window of its own,
is handed to the system and the panels come straight back, instead of the console sitting
locked until the program is closed. Console programs, batch files and documents a console
program opens (a `.py` run by `py.exe`) still run on the user screen as before. On Windows the
choice comes from the subsystem in the program's header; elsewhere a file without the execute
bit goes to `xdg-open` / `open` rather than failing in the shell.
- **Only a click moves the focus between panels.** The pointer passing over the other panel no
longer makes it the active one; the wheel scrolls whichever panel it is over, focus or not.
- **The drive menu lines the panels up.** Picking, with `Alt+F1` / `Alt+F2`, the drive the other
panel is on opens the other panel's folder rather than the drive's root.
- **The editor's caret is the console's own cursor.** It is no longer an inverted cell drawn into
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ generated from `HelpScreen.Bindings`, so they cannot drift apart.
| `Left / Right` | Move one column, or edit the command line |
| `PgUp / PgDn` | Scroll one page |
| `Home / End` | First / last item |
| `Enter` | Enter a folder or an archive, or run the file under the cursor |
| `Enter` | Enter a folder or an archive, or run the file; a document or windowed program opens without waiting |
| `Shift+Enter` | Open the folder in the system file manager (on .. the folder shown), or the file with its program |
| `Ctrl+PgDn` | Enter the folder or archive under the cursor |
| `Ctrl+PgUp` | Go to the parent folder |
| `Ctrl+\` | Go to the root of the current drive |
Expand All @@ -291,6 +292,7 @@ generated from `HelpScreen.Bindings`, so they cannot drift apart.
| `Ctrl+H` | Show or hide hidden and system files |
| `Ctrl+B` | Show or hide the function key bar |
| `Alt+F1 / Alt+F2` | Change the drive of the left / right panel; the other panel's folder when it is on that drive |
| `Click the sort letter` | Change that panel's drive; the sort letter is the top-left corner of the column titles |
| `Alt+<letter>` | Quick search by name |
| `Ctrl+T` | Open a new tab on the current folder |
| `Ctrl+W` | Close the current tab |
Expand Down
3 changes: 2 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ <h2>Get it running</h2>
<tr><td><kbd>Ctrl</kbd>+<kbd>T</kbd> / <kbd>Ctrl</kbd>+<kbd>W</kbd></td><td>Open / close a tab on the current folder</td></tr>
<tr><td><kbd>Ctrl</kbd>+<kbd>O</kbd></td><td>Hide the panels and see the user screen</td></tr>
<tr><td><kbd>Ctrl</kbd>+<kbd>R</kbd></td><td>Reverse search through the command history</td></tr>
<tr><td><kbd>Alt</kbd>+<kbd>F1</kbd> / <kbd>Alt</kbd>+<kbd>F2</kbd></td><td>Change the left / right drive</td></tr>
<tr><td><kbd>Shift</kbd>+<kbd>Enter</kbd></td><td>Open the folder in the system file manager, or the file with its program</td></tr>
<tr><td><kbd>Alt</kbd>+<kbd>F1</kbd> / <kbd>Alt</kbd>+<kbd>F2</kbd></td><td>Change the left / right drive (or click the sort letter)</td></tr>
<tr><td><kbd>Alt</kbd>+<kbd>F7</kbd></td><td>Find files by mask and content</td></tr>
<tr><td><kbd>F1</kbd></td><td>Every binding, generated from the same table as the README</td></tr>
</table>
Expand Down
34 changes: 33 additions & 1 deletion src/OpenStus/Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -949,11 +949,20 @@ private void HandleGlobalMouse(MouseEvent mouse)
return;
}

if (!target.IsActive)
// Only a click hands the focus over. The pointer drifting across the other panel - or the
// wheel turning above it, which scrolls it - leaves the focus where it is.
if (!target.IsActive && mouse.IsPress)
{
SetActivePanel(target);
}

// The sort letter in the top-left corner doubles as the panel's drive button.
if (mouse.IsPress && mouse.Button == MouseButton.Left && target.IsDriveButtonAt(mouse.X, mouse.Y))
{
ShowDriveMenu(left: target == _left);
return;
}

target.HandleMouse(mouse, this);
}

Expand Down Expand Up @@ -1066,6 +1075,29 @@ public void RunShellCommand(string command)
_dirty = true;
}

/// <inheritdoc/>
public void RunFile(string path)
{
if (CommandExecutor.RunsInConsole(path))
{
RunShellCommand("\"" + path + "\"");
return;
}

OpenExternally(path);
}

/// <inheritdoc/>
public void OpenExternally(string path)
{
if (!CommandExecutor.Open(path, ActiveFilePanel.WorkingDirectory, Terminal, out string? error))
{
_ui.Error("Open", Shorten(path, 60) + "\n" + (error ?? "The system could not open it."));
}

_dirty = true;
}

/// <inheritdoc/>
public void InsertIntoCommandLine(string text)
{
Expand Down
15 changes: 15 additions & 0 deletions src/OpenStus/Core/IAppContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ public interface IAppContext
/// <param name="command">The command line, exactly as typed.</param>
void RunShellCommand(string command);

/// <summary>
/// Runs a file the way Enter does: a console program or script runs on the user screen like a
/// typed command, while a document or a windowed program is handed to the operating system
/// and left running, so the panels come straight back instead of waiting for it to close.
/// </summary>
/// <param name="path">The file to run.</param>
void RunFile(string path);

/// <summary>
/// Hands a file or folder to the operating system without waiting (Shift+Enter): a folder
/// opens in the system file manager, a file with the program the system associates with it.
/// </summary>
/// <param name="path">The file or folder to open.</param>
void OpenExternally(string path);

/// <summary>Inserts text at the command line caret (Ctrl+J, Ctrl+F, Ctrl+[ and friends).</summary>
/// <param name="text">The text to insert.</param>
void InsertIntoCommandLine(string text);
Expand Down
83 changes: 71 additions & 12 deletions src/OpenStus/Panels/FilePanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,9 @@ public bool HandleKey(KeyEvent key, IAppContext ctx)
case ConsoleKey.Enter when none:
Activate(app);
return true;
case ConsoleKey.Enter when shift:
OpenExternally(app);
return true;
case ConsoleKey.Enter when ctrl:
InsertCurrentName(app);
return true;
Expand Down Expand Up @@ -1394,11 +1397,12 @@ public bool HandleKey(KeyEvent key, IAppContext ctx)

/// <inheritdoc/>
/// <remarks>
/// A click in a panel that does not have the focus is <em>not</em> consumed: the method returns
/// <see langword="false"/> so the shell can make this panel active and, if it wants to, replay
/// the event. Everything else is handled here - the wheel scrolls the window without moving the
/// cursor, the left button moves the cursor (and activates the entry when it was already under
/// the cursor, or on a double click), and the right button toggles the tag and moves the cursor.
/// The wheel scrolls the window without moving the cursor, whether or not the panel has the
/// focus. A click in a panel that does not have the focus is <em>not</em> consumed: the method
/// returns <see langword="false"/> so the shell can make this panel active and replay the
/// event. Everything else is handled here - the left button moves the cursor (and activates
/// the entry when it was already under the cursor, or on a double click), and the right button
/// toggles the tag and moves the cursor.
/// </remarks>
public bool HandleMouse(MouseEvent m, IAppContext ctx)
{
Expand All @@ -1407,17 +1411,17 @@ public bool HandleMouse(MouseEvent m, IAppContext ctx)
return false;
}

if (!IsActive)
{
return false;
}

if (m.Kind == MouseKind.Wheel)
{
ScrollBy(-m.Wheel * WheelRows);
return true;
}

if (!IsActive)
{
return false;
}

if (m.Kind is not (MouseKind.Down or MouseKind.DoubleClick))
{
return false;
Expand Down Expand Up @@ -1502,6 +1506,23 @@ public int IndexAt(int screenX, int screenY)
return index >= 0 && index < _entries.Count ? index : -1;
}

/// <summary>
/// Whether a screen cell is the sort letter in the top-left corner of the column titles,
/// which a click turns into the drive menu.
/// </summary>
/// <param name="screenX">Screen column.</param>
/// <param name="screenY">Screen row.</param>
/// <returns><see langword="true"/> on that one cell of a drawn panel.</returns>
public bool IsDriveButtonAt(int screenX, int screenY)
{
Rect b = Bounds;
return IsVisible &&
b.Width >= MinWidth &&
b.Height >= RequiredHeight &&
screenX == b.X + 1 &&
screenY == b.Y + HeaderRows - 1;
}

/// <summary>Scrolls the window without moving the cursor, the way the wheel does.</summary>
/// <param name="rows">How many rows to scroll; positive scrolls towards the end of the listing.</param>
public void ScrollBy(int rows)
Expand Down Expand Up @@ -1701,7 +1722,7 @@ private void Activate(IAppContext? app)
return;
}

app?.RunShellCommand("\"" + extracted + "\"");
app?.RunFile(extracted);
return;
}

Expand All @@ -1711,7 +1732,45 @@ private void Activate(IAppContext? app)
return;
}

app?.RunShellCommand("\"" + current.FullPath + "\"");
app?.RunFile(current.FullPath);
}

/// <summary>
/// Shift+Enter: hands the entry under the cursor to the operating system without waiting - a
/// folder to the system file manager, a file (an archive included) to its associated program.
/// </summary>
/// <remarks>
/// <c>..</c> opens the folder the panel shows. Inside an archive a file is taken out to the
/// scratch area first, and a folder - which has no real path - opens the archive file itself.
/// </remarks>
private void OpenExternally(IAppContext? app)
{
FileEntry? current = Current;
if (app is null || current is null)
{
return;
}

if (_archiveFile is not null)
{
if (current.IsDirectory)
{
app.OpenExternally(_archiveFile);
return;
}

string? extracted = ExtractToTemp(current, out string? error);
if (extracted is null)
{
app.Ui.Error("Open", error ?? "The file could not be extracted.");
return;
}

app.OpenExternally(extracted);
return;
}

app.OpenExternally(current.IsParent ? CurrentPath : current.FullPath);
}

/// <summary>
Expand Down
Loading
Loading