@@ -2,6 +2,7 @@ import AppKit
22import Common
33import HotKey
44import TOMLKit
5+ import OrderedCollections
56
67@MainActor
78func readConfig( forceConfigUrl: URL ? = nil ) -> Result < ( Config , URL ) , String > {
@@ -83,11 +84,14 @@ struct Parser<S: ConvenienceCopyable, T>: ParserProtocol {
8384
8485private let keyMappingConfigRootKey = " key-mapping "
8586private let modeConfigRootKey = " mode "
87+ private let persistentWorkspacesKey = " persistent-workspaces "
8688
8789// For every new config option you add, think:
8890// 1. Does it make sense to have different value
8991// 2. Prefer commands and commands flags over toml options if possible
9092private let configParser : [ String : any ParserProtocol < Config > ] = [
93+ " config-version " : Parser ( \. configVersion, parseConfigVersion) ,
94+
9195 " after-login-command " : Parser ( \. afterLoginCommand, parseAfterLoginCommand) ,
9296 " after-startup-command " : Parser ( \. afterStartupCommand) { parseCommandOrCommands ( $0) . toParsedToml ( $1) } ,
9397
@@ -105,6 +109,7 @@ private let configParser: [String: any ParserProtocol<Config>] = [
105109 " start-at-login " : Parser ( \. startAtLogin, parseBool) ,
106110 " automatically-unhide-macos-hidden-apps " : Parser ( \. automaticallyUnhideMacosHiddenApps, parseBool) ,
107111 " accordion-padding " : Parser ( \. accordionPadding, parseInt) ,
112+ persistentWorkspacesKey: Parser ( \. persistentWorkspaces, parsePersistentWorkspaces) ,
108113 " exec-on-workspace-change " : Parser ( \. execOnWorkspaceChange, parseArrayOfStrings) ,
109114 " exec " : Parser ( \. execConfig, parseExecConfig) ,
110115
@@ -181,17 +186,24 @@ func parseCommandOrCommands(_ raw: TOMLValueConvertible) -> Parsed<[any Command]
181186 config. keyMapping = mapping
182187 }
183188
189+ // Parse modeConfigRootKey after keyMappingConfigRootKey
184190 if let modes = rawTable [ modeConfigRootKey] . flatMap ( { parseModes ( $0, . rootKey( modeConfigRootKey) , & errors, config. keyMapping. resolve ( ) ) } ) {
185191 config. modes = modes
186192 }
187193
188- config. preservedWorkspaceNames = config. modes. values. lazy
189- . flatMap { ( mode: Mode ) -> [ HotkeyBinding ] in Array ( mode. bindings. values) }
190- . flatMap { ( binding: HotkeyBinding ) -> [ String ] in
191- binding. commands. filterIsInstance ( of: WorkspaceCommand . self) . compactMap { $0. args. target. val. workspaceNameOrNil ( ) ? . raw } +
192- binding. commands. filterIsInstance ( of: MoveNodeToWorkspaceCommand . self) . compactMap { $0. args. target. val. workspaceNameOrNil ( ) ? . raw }
194+ if config. configVersion <= 1 {
195+ if rawTable. contains ( key: persistentWorkspacesKey) {
196+ errors += [ . semantic( . rootKey( persistentWorkspacesKey) , " This config option is only available since 'config-version = 2' " ) ]
193197 }
194- + ( config. workspaceToMonitorForceAssignment) . keys
198+ config. persistentWorkspaces = ( config. modes. values. lazy
199+ . flatMap { ( mode: Mode ) -> [ HotkeyBinding ] in Array ( mode. bindings. values) }
200+ . flatMap { ( binding: HotkeyBinding ) -> [ String ] in
201+ binding. commands. filterIsInstance ( of: WorkspaceCommand . self) . compactMap { $0. args. target. val. workspaceNameOrNil ( ) ? . raw } +
202+ binding. commands. filterIsInstance ( of: MoveNodeToWorkspaceCommand . self) . compactMap { $0. args. target. val. workspaceNameOrNil ( ) ? . raw }
203+ }
204+ + ( config. workspaceToMonitorForceAssignment) . keys)
205+ . toOrderedSet ( )
206+ }
195207
196208 if config. enableNormalizationFlattenContainers {
197209 let containsSplitCommand = config. modes. values. lazy. flatMap { $0. bindings. values }
@@ -222,6 +234,13 @@ func parseIndentForNestedContainersWithTheSameOrientation(
222234 return . failure( . semantic( backtrace, msg) )
223235}
224236
237+ func parseConfigVersion( _ raw: TOMLValueConvertible , _ backtrace: TomlBacktrace ) -> ParsedToml < Int > {
238+ let min = 1
239+ let max = 2
240+ return parseInt ( raw, backtrace)
241+ . filter ( . semantic( backtrace, " Must be in [ \( min) , \( max) ] range " ) ) { ( min ... max) . contains ( $0) }
242+ }
243+
225244func parseInt( _ raw: TOMLValueConvertible , _ backtrace: TomlBacktrace ) -> ParsedToml < Int > {
226245 raw. int. orFailure ( expectedActualTypeError ( expected: . int, actual: raw. type, backtrace) )
227246}
@@ -289,6 +308,14 @@ private func skipParsing<T: Sendable>(_ value: T) -> @Sendable (_ raw: TOMLValue
289308 { _, _ in . success( value) }
290309}
291310
311+ private func parsePersistentWorkspaces( _ raw: TOMLValueConvertible , _ backtrace: TomlBacktrace ) -> ParsedToml < OrderedSet < String > > {
312+ parseArrayOfStrings ( raw, backtrace)
313+ . flatMap { arr in
314+ let set = arr. toOrderedSet ( )
315+ return set. count == arr. count ? . success( set) : . failure( . semantic( backtrace, " Contains duplicated workspace names " ) )
316+ }
317+ }
318+
292319private func parseArrayOfStrings( _ raw: TOMLValueConvertible , _ backtrace: TomlBacktrace ) -> ParsedToml < [ String ] > {
293320 parseTomlArray ( raw, backtrace)
294321 . flatMap { arr in
0 commit comments