From acc507ea9c9caf66cbb0ae22d0be2b4f16123d62 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Mon, 16 Oct 2023 00:28:06 +0200 Subject: [PATCH] Support MoveInCommand --- AeroSpace.xcodeproj/project.pbxproj | 8 ++++++++ config-examples/default-config.toml | 16 ++++++++++------ src/command/MoveInCommand.swift | 22 ++++++++++++++++++++++ src/command/parseCommand.swift | 4 ++++ src/config/parseConfig.swift | 1 + src/tree/TilingContainer.swift | 4 ++++ test/command/MoveInCommandTest.swift | 23 +++++++++++++++++++++++ 7 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/command/MoveInCommand.swift create mode 100644 test/command/MoveInCommandTest.swift diff --git a/AeroSpace.xcodeproj/project.pbxproj b/AeroSpace.xcodeproj/project.pbxproj index a95d4953..2f9b2f93 100644 --- a/AeroSpace.xcodeproj/project.pbxproj +++ b/AeroSpace.xcodeproj/project.pbxproj @@ -21,11 +21,13 @@ 2E06134604F2510189F1FA85 /* ExecAndWaitCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9EDB8770FBA8B2E6FE91BBB /* ExecAndWaitCommand.swift */; }; 374CE35B85B941B8F584C113 /* FlattenWorkspaceTreeCommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FDECECC773EBA30661EB8A /* FlattenWorkspaceTreeCommandTest.swift */; }; 3BD6FF4CC51532977DA0C05A /* AeroAny.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82476B9BEBAC00EB9E32256F /* AeroAny.swift */; }; + 3BE358D5C209F279BB71AC95 /* MoveInCommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E5ED8E885494CAEEC70A90 /* MoveInCommandTest.swift */; }; 42197B9C71A0CDDE65804A6A /* accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D31BF26EAFA96F675D2C14B /* accessibility.swift */; }; 45AA5FD4A023AF751922BC22 /* BundleEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B7A2DF0D1F72B80B1F04240 /* BundleEx.swift */; }; 45EA2D1C90430C432E123B51 /* keysMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0D40CBD65704BA9595C2FA /* keysMap.swift */; }; 4D62EC19C6A1E3BBF54306BA /* Copyable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B4C778D2594EB23C295741 /* Copyable.swift */; }; 56E72B24303F5F337B31B776 /* TrayMenuModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F52E346D024960EAF5938 /* TrayMenuModel.swift */; }; + 5A3B1D50474AFBE7C05D2E6F /* MoveInCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E330182A6FACBABABB6FC51 /* MoveInCommand.swift */; }; 5DA2DA21600E8B5BCA3DCFC0 /* LayoutCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B673C67B00DBFCC27FFE7 /* LayoutCommand.swift */; }; 5FB1B80967DF269BA45F84A4 /* resizeWithMouse.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90E205F7C4A569840262EB8 /* resizeWithMouse.swift */; }; 6317AB471F4C4F5D66A25784 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EEDBFFCA7A77D96B18FB0732 /* Assets.xcassets */; }; @@ -90,6 +92,7 @@ 00ECDFE176777828D560A737 /* TilingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilingContainer.swift; sourceTree = ""; }; 09685297933511208058F7CF /* AeroSpace.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AeroSpace.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0FF221FD5C0CB4163E322D6D /* MoveWorkspaceToDisplayCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveWorkspaceToDisplayCommand.swift; sourceTree = ""; }; + 13E5ED8E885494CAEEC70A90 /* MoveInCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveInCommandTest.swift; sourceTree = ""; }; 1A2B673C67B00DBFCC27FFE7 /* LayoutCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutCommand.swift; sourceTree = ""; }; 1C0D40CBD65704BA9595C2FA /* keysMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = keysMap.swift; sourceTree = ""; }; 1E81623E8954701269A22322 /* AeroSpaceApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AeroSpaceApp.swift; sourceTree = ""; }; @@ -112,6 +115,7 @@ 6352ADEE6625D9703CFCA99A /* Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; 67B9FFF81EB0327ABD51A7FE /* MoveThroughCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveThroughCommand.swift; sourceTree = ""; }; 67DBAF4ECF8A0B931FC34EAD /* parseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = parseConfig.swift; sourceTree = ""; }; + 6E330182A6FACBABABB6FC51 /* MoveInCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveInCommand.swift; sourceTree = ""; }; 6F1905935B0C61590A96EFEF /* TestWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestWindow.swift; sourceTree = ""; }; 7A247B616D1951777C565D02 /* MoveThroughCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveThroughCommandTest.swift; sourceTree = ""; }; 7DBD002C4A68AED07BB63EFA /* MoveContainerToWorkspaceCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveContainerToWorkspaceCommandTest.swift; sourceTree = ""; }; @@ -186,6 +190,7 @@ 53FDECECC773EBA30661EB8A /* FlattenWorkspaceTreeCommandTest.swift */, D0EAADE8D2FB5D05FA5456B0 /* FocusCommandTest.swift */, 7DBD002C4A68AED07BB63EFA /* MoveContainerToWorkspaceCommandTest.swift */, + 13E5ED8E885494CAEEC70A90 /* MoveInCommandTest.swift */, 7A247B616D1951777C565D02 /* MoveThroughCommandTest.swift */, ); path = command; @@ -291,6 +296,7 @@ 1A2B673C67B00DBFCC27FFE7 /* LayoutCommand.swift */, 4B0CD3C2A0E86CDB9DF312AB /* ModeCommand.swift */, CD62043D7F5113A8BA635FDF /* MoveContainerToWorkspaceCommand.swift */, + 6E330182A6FACBABABB6FC51 /* MoveInCommand.swift */, 67B9FFF81EB0327ABD51A7FE /* MoveThroughCommand.swift */, 0FF221FD5C0CB4163E322D6D /* MoveWorkspaceToDisplayCommand.swift */, CB03A4736BC3F6D19E4E69F3 /* parseCommand.swift */, @@ -432,6 +438,7 @@ 374CE35B85B941B8F584C113 /* FlattenWorkspaceTreeCommandTest.swift in Sources */, 1311398A83B998908773C54D /* FocusCommandTest.swift in Sources */, A55F31B0CC357B37108B8F54 /* MoveContainerToWorkspaceCommandTest.swift in Sources */, + 3BE358D5C209F279BB71AC95 /* MoveInCommandTest.swift in Sources */, B1E527CF4941A4E9B8D9C3D0 /* MoveThroughCommandTest.swift in Sources */, E22ACB36C90695FBAC78226E /* TestWindow.swift in Sources */, 70A82A4A9DFC89286C4F7696 /* TilingContainer.swift in Sources */, @@ -467,6 +474,7 @@ 6E4E235FDA41307B19F16182 /* ModeCommand.swift in Sources */, 920FDF8498DCCB62149D1719 /* Monitor.swift in Sources */, 89064BDDB5E9D17BEDE52E8C /* MoveContainerToWorkspaceCommand.swift in Sources */, + 5A3B1D50474AFBE7C05D2E6F /* MoveInCommand.swift in Sources */, EDFDE707B4DC5E500B1709B1 /* MoveThroughCommand.swift in Sources */, 05407EA2B9AC3F9371B56E6B /* MoveWorkspaceToDisplayCommand.swift in Sources */, 635733FDDF37E44364372B74 /* MruStack.swift in Sources */, diff --git a/config-examples/default-config.toml b/config-examples/default-config.toml index d9d0cfc2..6e5c01a9 100644 --- a/config-examples/default-config.toml +++ b/config-examples/default-config.toml @@ -75,6 +75,7 @@ alt-y = 'workspace Y' alt-z = 'workspace Z' alt-shift-semicolon = 'mode service' +alt-shift-slash = 'mode move-in' alt-shift-0 = 'move-container-to-workspace 0' alt-shift-1 = 'move-container-to-workspace 1' @@ -112,16 +113,19 @@ alt-shift-z = 'move-container-to-workspace Z' alt-tab = 'workspace-back-and-forth' alt-shift-tab = 'move-workspace-to-display next' -# alt-shift-slash.alt-shift-k = 'move-in up' -# alt-shift-slash.alt-shift-h = 'move-in left' -# alt-shift-slash.alt-shift-j = 'move-in down' -# alt-shift-slash.alt-shift-l = 'move-in right' - [mode.service.binding] r = ['flatten-workspace-tree', 'mode main'] o = ['reload-config', 'mode main'] s = ['layout sticky', 'mode main'] f = ['layout floating', 'mode main'] -backslash = 'close-all-windows-but-current' +backslash = ['close-all-windows-but-current', 'mode main'] +esc = 'mode main' +enter = 'mode main' + +[mode.move-in.binding] +alt-shift-h = ['move-in left', 'mode main'] +alt-shift-j = ['move-in down', 'mode main'] +alt-shift-k = ['move-in up', 'mode main'] +alt-shift-l = ['move-in right', 'mode main'] esc = 'mode main' enter = 'mode main' diff --git a/src/command/MoveInCommand.swift b/src/command/MoveInCommand.swift new file mode 100644 index 00000000..dc38a543 --- /dev/null +++ b/src/command/MoveInCommand.swift @@ -0,0 +1,22 @@ +struct MoveInCommand: Command { + let direction: CardinalDirection + + func runWithoutRefresh() { + precondition(Thread.current.isMainThread) + guard let currentWindow = focusedWindowOrEffectivelyFocused else { return } + guard let (parent, ownIndex) = currentWindow.closestParent(hasChildrenInDirection: direction, withLayout: nil) else { return } + let moveInTarget = parent.children[ownIndex + direction.focusOffset] + let prevBinding = moveInTarget.unbindFromParent() + let newParent = TilingContainer( + parent: parent, + adaptiveWeight: prevBinding.adaptiveWeight, + parent.orientation.opposite, + .List, + index: prevBinding.index + ) + currentWindow.unbindFromParent() + + moveInTarget.bindTo(parent: newParent, adaptiveWeight: WEIGHT_AUTO, index: 0) + currentWindow.bindTo(parent: newParent, adaptiveWeight: WEIGHT_AUTO, index: direction.isPositive ? 0 : INDEX_BIND_LAST) + } +} diff --git a/src/command/parseCommand.swift b/src/command/parseCommand.swift index 1bd94b35..ebfa3388 100644 --- a/src/command/parseCommand.swift +++ b/src/command/parseCommand.swift @@ -27,6 +27,10 @@ private func parseSingleCommand(_ raw: String) -> ParsedCommand { return parseSingleArg(args, firstWord).map { MoveContainerToWorkspaceCommand(targetWorkspaceName: $0) } } else if firstWord == "mode" { return parseSingleArg(args, firstWord).map { ModeCommand(idToActivate: $0) } + } else if firstWord == "move-in" { + return parseSingleArg(args, firstWord) + .flatMap { CardinalDirection(rawValue: $0).orFailure("Can't parse '\(firstWord)' direction") } + .map { MoveInCommand(direction: $0) } } else if firstWord == "move-workspace-to-display" { return parseSingleArg(args, firstWord) .flatMap { MoveWorkspaceToDisplayCommand.DisplayTarget(rawValue: $0).orFailure("Can't parse '\(firstWord)' display target") } diff --git a/src/config/parseConfig.swift b/src/config/parseConfig.swift index db5906ed..47d827d0 100644 --- a/src/config/parseConfig.swift +++ b/src/config/parseConfig.swift @@ -230,6 +230,7 @@ private func parseBindings(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktra let keyBacktrace = backtrace + .key(binding) let (binding, error): (HotkeyBinding?, TomlParseError?) = parseBinding(binding, keyBacktrace) .flatMap { (modifiers, key) -> ParsedTomlResult in + // todo support parsing of implicit modes? parseCommand(rawCommand).toParsedTomlResult(keyBacktrace).map { HotkeyBinding(modifiers, key, $0) } } .getOrNils() diff --git a/src/tree/TilingContainer.swift b/src/tree/TilingContainer.swift index 90bb633a..88f3d9c1 100644 --- a/src/tree/TilingContainer.swift +++ b/src/tree/TilingContainer.swift @@ -47,6 +47,10 @@ enum Orientation { case V } +extension Orientation { + var opposite: Orientation { self == .H ? .V : .H } +} + enum Layout { case List case Accordion diff --git a/test/command/MoveInCommandTest.swift b/test/command/MoveInCommandTest.swift new file mode 100644 index 00000000..4b896909 --- /dev/null +++ b/test/command/MoveInCommandTest.swift @@ -0,0 +1,23 @@ +import XCTest +@testable import AeroSpace_Debug + +final class MoveInCommandTest: XCTestCase { + override func setUpWithError() throws { setUpWorkspacesForTests() } + + func testMoveIn() async { + let root = Workspace.get(byName: name).rootTilingContainer.apply { + TestWindow(id: 0, parent: $0) + TestWindow(id: 1, parent: $0).focus() + TestWindow(id: 2, parent: $0) + } + + await MoveInCommand(direction: .right).runWithoutRefresh() + XCTAssertEqual(root.layoutDescription, .h_list([ + .window(0), + .v_list([ + .window(1), + .window(2) + ]) + ])) + } +}