Skip to content

Commit

Permalink
Support --wrap-around in workspace and move-node-to-workspace commands
Browse files Browse the repository at this point in the history
closes #45
  • Loading branch information
nikitabobko committed Dec 14, 2023
1 parent bd83254 commit f6a98ca
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 39 deletions.
3 changes: 1 addition & 2 deletions src/AeroSpaceApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ struct AeroSpaceApp: App {
Button {
refreshSession {
WorkspaceCommand(args: WorkspaceCmdArgs(
target: .workspaceName(workspace.name),
autoBackAndForth: false
target: .workspaceName(name: workspace.name, autoBackAndForth: false)
)).runOnFocusedSubject()
}
} label: {
Expand Down
5 changes: 3 additions & 2 deletions src/command/MoveNodeToWorkspaceCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ struct MoveNodeToWorkspaceCommand: Command {
case .next:
fallthrough
case .prev:
guard let workspace = getNextPrevWorkspace(current: subject.workspace, next: args.target == .next) else { return }
guard let workspace = getNextPrevWorkspace(current: subject.workspace, target: args.target) else { return }
targetWorkspace = workspace
case .workspaceName(let name):
case .workspaceName(let name, let autoBackAndForth):
check(!autoBackAndForth)
targetWorkspace = Workspace.get(byName: name)
}
if preserveWorkspace == targetWorkspace {
Expand Down
3 changes: 1 addition & 2 deletions src/command/WorkspaceBackAndForthCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ struct WorkspaceBackAndForthCommand: Command {
check(Thread.current.isMainThread)
guard let previousFocusedWorkspaceName else { return }
WorkspaceCommand(args: WorkspaceCmdArgs(
target: .workspaceName(previousFocusedWorkspaceName),
autoBackAndForth: false
target: .workspaceName(name: previousFocusedWorkspaceName, autoBackAndForth: false)
)).run(&subject)
}
}
27 changes: 22 additions & 5 deletions src/command/WorkspaceCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ struct WorkspaceCommand : Command {
case .next:
fallthrough
case .prev:
guard let workspace = getNextPrevWorkspace(current: subject.workspace, next: args.target == .next) else { return }
guard let workspace = getNextPrevWorkspace(current: subject.workspace, target: args.target) else { return }
workspaceName = workspace.name
case .workspaceName(let _workspaceName):
case .workspaceName(let _workspaceName, let autoBackAndForth):
workspaceName = _workspaceName
if args.autoBackAndForth && subject.workspace.name == workspaceName {
if autoBackAndForth && subject.workspace.name == workspaceName {
WorkspaceBackAndForthCommand().run(&subject)
return
}
Expand All @@ -30,9 +30,26 @@ struct WorkspaceCommand : Command {
}
}

func getNextPrevWorkspace(current: Workspace, next: Bool) -> Workspace? {
func getNextPrevWorkspace(current: Workspace, target: WorkspaceTarget) -> Workspace? {
let next: Bool
let wrapAround: Bool
switch target {
case .next(let _wrapAround):
next = true
wrapAround = _wrapAround
case .prev(let _wrapAround):
next = false
wrapAround = _wrapAround
case .workspaceName(_):
error("Invalid argument \(target)")
}
let workspaces: [Workspace] = Workspace.all.toSet().union([current]).sortedBy { $0.name }
guard let index = workspaces.firstIndex(of: current) else { error("Impossible") }
guard let workspace = workspaces.getOrNil(atIndex: next ? index + 1 : index - 1) else { return nil }
let workspace: Workspace?
if wrapAround {
workspace = workspaces.get(wrappingIndex: next ? index + 1 : index - 1)
} else {
workspace = workspaces.getOrNil(atIndex: next ? index + 1 : index - 1)
}
return workspace
}
17 changes: 10 additions & 7 deletions src/command/parse/MoveNodeToWorkspaceCmdArgs.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
private struct RawMoveNodeToWorkspaceCmdArgs: RawCmdArgs {
var target: WorkspaceTarget?
var target: RawWorkspaceTarget?
var wrapAroundNextPrev: Bool?

static let info = CmdInfo<Self>(
help: """
USAGE: move-node-to-workspace [-h|--help] (next|prev)
USAGE: move-node-to-workspace [-h|--help] [--wrap-around] (next|prev)
OR: move-node-to-workspace [-h|--help] <workspace-name>
OPTIONS:
-h, --help Print help
--wrap-around Make it possible to move nodes between first and last workspaces
(alphabetical order) using (next|prev)
ARGUMENTS:
<workspace-name> Workspace name to move focused window to
""",
options: [:],
arguments: [ArgParser(\.target, parseWorkspaceTarget)]
options: ["--wrap-around": trueBoolFlag(\.wrapAroundNextPrev)],
arguments: [ArgParser(\.target, parseRawWorkspaceTarget)]
)
}

Expand All @@ -28,8 +31,8 @@ func parseMoveNodeToWorkspaceCmdArgs(_ args: [String]) -> ParsedCmd<MoveNodeToWo
guard let target = raw.target else {
return .failure("<workspace-name> is mandatory argument")
}
return .cmd(MoveNodeToWorkspaceCmdArgs(
target: target
))
return target.parse(wrapAround: raw.wrapAroundNextPrev, autoBackAndForth: false).flatMap { target in
.cmd(MoveNodeToWorkspaceCmdArgs(target: target))
}
}
}
55 changes: 42 additions & 13 deletions src/command/parse/WorkspaceCmdArgs.swift
Original file line number Diff line number Diff line change
@@ -1,41 +1,71 @@
private struct RawWorkspaceCmdArgs: RawCmdArgs {
var target: WorkspaceTarget?
var target: RawWorkspaceTarget?
var autoBackAndForth: Bool?
var wrapAroundNextPrev: Bool?

static let info = CmdInfo<Self>(
help: """
USAGE: workspace [-h|--help] [--auto-back-and-forth] (next|prev)
USAGE: workspace [-h|--help] [--wrap-around] (next|prev)
OR: workspace [-h|--help] [--auto-back-and-forth] <workspace-name>
OPTIONS:
-h, --help Print help
--auto-back-and-forth Automatic 'back-and-forth' when switching to already
focused workspace
--wrap-around Make it possible to jump between first and last workspaces
(alphabetical order) using (next|prev)
ARGUMENTS:
<workspace-name> Workspace name to focus
""",
options: [
"--auto-back-and-forth": trueBoolFlag(\.autoBackAndForth)
"--auto-back-and-forth": trueBoolFlag(\.autoBackAndForth),
"--wrap-around": trueBoolFlag(\.wrapAroundNextPrev)
],
arguments: [ArgParser(\.target, parseWorkspaceTarget)]
arguments: [ArgParser(\.target, parseRawWorkspaceTarget)]
)
}

struct WorkspaceCmdArgs: CmdArgs, Equatable {
let kind: CmdKind = .workspace
let target: WorkspaceTarget
let autoBackAndForth: Bool
}

enum WorkspaceTarget: Equatable {
enum RawWorkspaceTarget: Equatable {
case next
case prev
//case back_and_forth // todo what about 'prev-focused'? todo at least the name needs to be reserved
case workspaceName(String)

func parse(wrapAround: Bool?, autoBackAndForth: Bool?) -> ParsedCmd<WorkspaceTarget> {
if autoBackAndForth != nil {
guard case .workspaceName = self else {
return .failure("--auto-back-and-forth is not allowed for (next|prev)")
}
}
switch self {
case .next:
check(autoBackAndForth == nil)
return .cmd(.next(wrapAround: wrapAround ?? false))
case .prev:
check(autoBackAndForth == nil)
return .cmd(.prev(wrapAround: wrapAround ?? false))
case .workspaceName(let name):
if wrapAround != nil {
return .failure("--wrap-around is allowed only for (next|prev)")
}
return .cmd(.workspaceName(name: name, autoBackAndForth: autoBackAndForth ?? false))
}
}
}

enum WorkspaceTarget: Equatable {
case next(wrapAround: Bool)
case prev(wrapAround: Bool)
//case back_and_forth // todo what about 'prev-focused'? todo at least the name needs to be reserved
case workspaceName(name: String, autoBackAndForth: Bool)

func workspaceNameOrNil() -> String? {
if case .workspaceName(let name) = self {
if case .workspaceName(let name, _) = self {
return name
} else {
return nil
Expand All @@ -49,14 +79,13 @@ func parseWorkspaceCmdArgs(_ args: [String]) -> ParsedCmd<WorkspaceCmdArgs> {
guard let target = raw.target else {
return .failure("<workspace-name> is mandatory argument")
}
return .cmd(WorkspaceCmdArgs(
target: target,
autoBackAndForth: raw.autoBackAndForth ?? false
))
return target.parse(wrapAround: raw.wrapAroundNextPrev, autoBackAndForth: raw.autoBackAndForth).flatMap { target in
.cmd(WorkspaceCmdArgs(target: target))
}
}
}

func parseWorkspaceTarget(_ arg: String) -> Parsed<WorkspaceTarget> {
func parseRawWorkspaceTarget(_ arg: String) -> Parsed<RawWorkspaceTarget> {
switch arg {
case "next":
return .success(.next)
Expand Down
3 changes: 1 addition & 2 deletions src/tree/MacWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ final class MacWindow: Window, CustomStringConvertible {
if workspace == previousFocusedWorkspaceName || workspace == focusedWorkspaceName {
refreshSession {
WorkspaceCommand(args: WorkspaceCmdArgs(
target: .workspaceName(workspace),
autoBackAndForth: false
target: .workspaceName(name: workspace, autoBackAndForth: false)
)).runOnFocusedSubject()
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/command/MoveNodeToWorkspaceCommandTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ final class MoveNodeToWorkspaceCommandTest: XCTestCase {
TestWindow(id: 1, parent: $0).focus()
}

MoveNodeToWorkspaceCommand(args: MoveNodeToWorkspaceCmdArgs(target: .workspaceName("b"))).runOnFocusedSubject()
MoveNodeToWorkspaceCommand(args: MoveNodeToWorkspaceCmdArgs(target: .workspaceName(name: "b", autoBackAndForth: false))).runOnFocusedSubject()
XCTAssertTrue(workspaceA.isEffectivelyEmpty)
XCTAssertEqual((Workspace.get(byName: "b").rootTilingContainer.children.singleOrNil() as? Window)?.windowId, 1)
}
Expand All @@ -20,7 +20,7 @@ final class MoveNodeToWorkspaceCommandTest: XCTestCase {
TestWindow(id: 1, parent: $0).focus()
}

MoveNodeToWorkspaceCommand(args: MoveNodeToWorkspaceCmdArgs(target: .workspaceName("b"))).runOnFocusedSubject()
MoveNodeToWorkspaceCommand(args: MoveNodeToWorkspaceCmdArgs(target: .workspaceName(name: "b", autoBackAndForth: false))).runOnFocusedSubject()
XCTAssertTrue(workspaceA.isEffectivelyEmpty)
XCTAssertEqual(Workspace.get(byName: "b").children.filterIsInstance(of: Window.self).singleOrNil()?.windowId, 1)
}
Expand Down
12 changes: 9 additions & 3 deletions test/command/WorkspaceCommandTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ final class WorkspaceCommandTest: XCTestCase {
XCTAssertTrue(parseCommand("workspace").failureMsgOrNil?.contains("mandatory") == true)
XCTAssertEqual(
parseCommand("workspace next").cmdOrNil?.describe,
.workspace(args: WorkspaceCmdArgs(target: .next, autoBackAndForth: false))
.workspace(args: WorkspaceCmdArgs(target: .next(wrapAround: false)))
)
XCTAssertEqual(
parseCommand("workspace --auto-back-and-forth next").cmdOrNil?.describe,
.workspace(args: WorkspaceCmdArgs(target: .next, autoBackAndForth: true))
parseCommand("workspace --auto-back-and-forth W").cmdOrNil?.describe,
.workspace(args: WorkspaceCmdArgs(target: .workspaceName(name: "W", autoBackAndForth: true)))
)
XCTAssertTrue(parseCommand("workspace --wrap-around W").failureMsgOrNil?.contains("--wrap-around is allowed only for (next|prev)") == true)
XCTAssertTrue(parseCommand("workspace --auto-back-and-forth next").failureMsgOrNil?.contains("--auto-back-and-forth is not allowed for (next|prev)") == true)
XCTAssertEqual(
parseCommand("workspace next --wrap-around").cmdOrNil?.describe,
.workspace(args: WorkspaceCmdArgs(target: .next(wrapAround: true)))
)
}
}
2 changes: 1 addition & 1 deletion test/config/ConfigTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ final class ConfigTest: XCTestCase {
checkFurtherCallbacks: true,
run: [
LayoutCommand(args: LayoutCmdArgs(toggleBetween: [.floating])),
MoveNodeToWorkspaceCommand(args: MoveNodeToWorkspaceCmdArgs(target: .workspaceName("W")))
MoveNodeToWorkspaceCommand(args: MoveNodeToWorkspaceCmdArgs(target: .workspaceName(name: "W", autoBackAndForth: false)))
]
),
WindowDetectedCallback(
Expand Down

0 comments on commit f6a98ca

Please sign in to comment.