diff --git a/docs/_common/core/styling.md b/docs/_includes/core/styling.md similarity index 100% rename from docs/_common/core/styling.md rename to docs/_includes/core/styling.md diff --git a/docs/_includes/core/workspace-overview.md b/docs/_includes/core/workspace-overview.md new file mode 100644 index 000000000..ea77054f0 --- /dev/null +++ b/docs/_includes/core/workspace-overview.md @@ -0,0 +1,5 @@ +A "workspace" or in Whim is a collection of windows displayed on a single monitor. The layouts of workspaces are determined by their [layout engines](../../configure/core/layout-engines.md). Each workspace has a single active layout engine, and can cycle through different layout engines. + +There must be at least as many workspaces defined in the config as there are monitors connected to the system. If there are more workspaces defined than monitors, workspaces named `Workspace {workspaces.Count + 1}` will be created for the extra monitors. If there are fewer workspaces defined than monitors, the extra monitors will not have any workspaces displayed on them. + +When Whim exits, it will save the current workspaces and the current positions of each window within them. When Whim is started again, it will attempt to merge the saved workspaces with the workspaces defined in the config. diff --git a/docs/_common/plugins/command-palette.md b/docs/_includes/plugins/command-palette.md similarity index 100% rename from docs/_common/plugins/command-palette.md rename to docs/_includes/plugins/command-palette.md diff --git a/docs/_common/plugins/focus-indicator.md b/docs/_includes/plugins/focus-indicator.md similarity index 100% rename from docs/_common/plugins/focus-indicator.md rename to docs/_includes/plugins/focus-indicator.md diff --git a/docs/_common/plugins/gaps.md b/docs/_includes/plugins/gaps.md similarity index 100% rename from docs/_common/plugins/gaps.md rename to docs/_includes/plugins/gaps.md diff --git a/docs/_common/plugins/layout-preview.md b/docs/_includes/plugins/layout-preview.md similarity index 100% rename from docs/_common/plugins/layout-preview.md rename to docs/_includes/plugins/layout-preview.md diff --git a/docs/_common/plugins/slice-layout.md b/docs/_includes/plugins/slice-layout.md similarity index 100% rename from docs/_common/plugins/slice-layout.md rename to docs/_includes/plugins/slice-layout.md diff --git a/docs/_common/plugins/tree-layout.md b/docs/_includes/plugins/tree-layout.md similarity index 100% rename from docs/_common/plugins/tree-layout.md rename to docs/_includes/plugins/tree-layout.md diff --git a/docs/_common/plugins/updater.md b/docs/_includes/plugins/updater.md similarity index 100% rename from docs/_common/plugins/updater.md rename to docs/_includes/plugins/updater.md diff --git a/docs/configure/core/styling.md b/docs/configure/core/styling.md index 143f60da4..707ca558c 100644 --- a/docs/configure/core/styling.md +++ b/docs/configure/core/styling.md @@ -1,3 +1,3 @@ # Styling -[!INCLUDE [Styling](../../_common/core/styling.md)] +[!INCLUDE [Styling](../../_includes/core/styling.md)] diff --git a/docs/configure/core/workspaces.md b/docs/configure/core/workspaces.md index 5ee4532e9..925771c3d 100644 --- a/docs/configure/core/workspaces.md +++ b/docs/configure/core/workspaces.md @@ -1,3 +1,26 @@ # Workspaces -👷🏗️🚧 +[!INCLUDE[Workspace overview](../../_includes/core/workspace-overview.md)] + +## Configuration + +The `workspaces` property is a list of workspaces that can be displayed on a monitor. Each workspace has a name and a list of layout engines. The layout engines determine how windows are displayed on the workspace. If no layout engines are provided, the workspace will default to the layout engines defined in [Layout Engines](layout-engines.md). + +```yaml +workspaces: + entries: + - name: Browser + layout_engines: + - type: SliceLayoutEngine + variant: + type: column + - type: SliceLayoutEngine + variant: + type: row + - type: TreeLayoutEngine + + - name: IDE + - name: Chat + - name: Spotify + - name: Other +``` diff --git a/docs/configure/plugins/command-palette.md b/docs/configure/plugins/command-palette.md index a9a21cc57..5df1a8632 100644 --- a/docs/configure/plugins/command-palette.md +++ b/docs/configure/plugins/command-palette.md @@ -23,7 +23,7 @@ plugins: | `max_width_pixels` | The maximum width of the command palette in pixels. | | `y_position_percent` | The y position of the command palette as a percentage of the monitor height. | -[!INCLUDE [Commands](../../_common/plugins/command-palette.md)] +[!INCLUDE [Commands](../../_includes/plugins/command-palette.md)] ## Tree Layout diff --git a/docs/configure/plugins/focus-indicator.md b/docs/configure/plugins/focus-indicator.md index 500fffe72..ce144e567 100644 --- a/docs/configure/plugins/focus-indicator.md +++ b/docs/configure/plugins/focus-indicator.md @@ -172,4 +172,4 @@ The color can be any valid RGB or RGBA hex color. The following named colors are | `yellow` | `#FFFFFF00` | | | `yellow_green` | `#FF9ACD32` | | -[!INCLUDE [Commands](../../_common/plugins/focus-indicator.md)] +[!INCLUDE [Commands](../../_includes/plugins/focus-indicator.md)] diff --git a/docs/configure/plugins/gaps.md b/docs/configure/plugins/gaps.md index 00c999b45..07f9bd99c 100644 --- a/docs/configure/plugins/gaps.md +++ b/docs/configure/plugins/gaps.md @@ -24,4 +24,4 @@ plugins: | `default_outer_delta` | The default outer gap used by commands `gaps.outer.increase` and `gaps.outer.decrease` | | `default_inner_delta` | The default inner gap used by commands `gaps.inner.increase` and `gaps.inner.decrease` | -[!INCLUDE [Commands](../../_common/plugins/gaps.md)] +[!INCLUDE [Commands](../../_includes/plugins/gaps.md)] diff --git a/docs/configure/plugins/layout-preview.md b/docs/configure/plugins/layout-preview.md index cea9de805..11fe9fad5 100644 --- a/docs/configure/plugins/layout-preview.md +++ b/docs/configure/plugins/layout-preview.md @@ -16,4 +16,4 @@ plugins: | ------------ | -------------------------------------------------- | | `is_enabled` | Whether the plugin is enabled. Defaults to `true`. | -[!INCLUDE [Commands](../../_common/plugins/layout-preview.md)] +[!INCLUDE [Commands](../../_includes/plugins/layout-preview.md)] diff --git a/docs/configure/plugins/slice-layout.md b/docs/configure/plugins/slice-layout.md index dc07356f6..77733b613 100644 --- a/docs/configure/plugins/slice-layout.md +++ b/docs/configure/plugins/slice-layout.md @@ -4,4 +4,4 @@ The `SliceLayoutPlugin` is automatically loaded when the `SliceLayoutEngine` is ## Commands -[!INCLUDE [Commands](../../_common/plugins/slice-layout.md)] +[!INCLUDE [Commands](../../_includes/plugins/slice-layout.md)] diff --git a/docs/configure/plugins/tree-layout.md b/docs/configure/plugins/tree-layout.md index 6bbf73a85..f5661f35f 100644 --- a/docs/configure/plugins/tree-layout.md +++ b/docs/configure/plugins/tree-layout.md @@ -4,4 +4,4 @@ The `TreeLayoutPlugin` is automatically loaded when the `TreeLayoutEngine` is us ## Commands -[!INCLUDE [Commands](../../_common/plugins/tree-layout.md)] +[!INCLUDE [Commands](../../_includes/plugins/tree-layout.md)] diff --git a/docs/configure/plugins/updater.md b/docs/configure/plugins/updater.md index e7f935777..82ddce375 100644 --- a/docs/configure/plugins/updater.md +++ b/docs/configure/plugins/updater.md @@ -18,4 +18,4 @@ plugins: | `release_channel` | The release channel to use. Options are `alpha`, `beta`, and `stable`. Default is `alpha`. | | `update_frequency` | How often to check for updates. Options are `never`, `daily`, `weekly`, and `monthly`. Default is `weekly`. | -[!INCLUDE [Commands](../../_common/plugins/updater.md)] +[!INCLUDE [Commands](../../_includes/plugins/updater.md)] diff --git a/docs/docfx.json b/docs/docfx.json index 845853738..9245ebda1 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -20,7 +20,7 @@ "content": [ { "files": ["**/*.{md,yml}"], - "exclude": ["_site/**"] + "exclude": ["_site/**", "_includes/**"] } ], "resource": [ diff --git a/docs/script/core/styling.md b/docs/script/core/styling.md index a317edfb5..39d3ae848 100644 --- a/docs/script/core/styling.md +++ b/docs/script/core/styling.md @@ -1,3 +1,3 @@ # Styling -[!INCLUDE [Styling](../../_common/core/styling.md)] +[!INCLUDE [Styling](../../_includes/core/styling.md)] diff --git a/docs/script/core/workspaces.md b/docs/script/core/workspaces.md index 9694eae8f..5fa9ee07e 100644 --- a/docs/script/core/workspaces.md +++ b/docs/script/core/workspaces.md @@ -1,6 +1,8 @@ # Workspaces -A "workspace" or in Whim is a collection of windows displayed on a single monitor. The layouts of workspaces are determined by their [layout engines](layout-engines.md). Each workspace has a single active layout engine, and can cycle through different layout engines. +[!INCLUDE[Workspace overview](../../_includes/core/workspace-overview.md)] + +## Configuration The lets you specify the default layout engines for workspaces. For example, the following config sets up three workspaces, and two layout engines: @@ -38,10 +40,6 @@ context.Store.Dispatch( The available layout engines are listed in [Layout Engines](./layout-engines.md). -If no name is provided, the name will default to `Workspace {workspaces.Count + 1}.` - -When Whim exits, it will save the current workspaces and the current positions of each window within them. When Whim is started again, it will attempt to merge the saved workspaces with the workspaces defined in the config. - ## Example Command ```csharp diff --git a/docs/script/plugins/command-palette.md b/docs/script/plugins/command-palette.md index c2dd01358..3ae106197 100644 --- a/docs/script/plugins/command-palette.md +++ b/docs/script/plugins/command-palette.md @@ -35,4 +35,4 @@ void DoConfig(IContext context) return DoConfig; ``` -[!INCLUDE [Commands](../../_common/plugins/command-palette.md)] +[!INCLUDE [Commands](../../_includes/plugins/command-palette.md)] diff --git a/docs/script/plugins/focus-indicator.md b/docs/script/plugins/focus-indicator.md index 77f124f0e..089de6767 100644 --- a/docs/script/plugins/focus-indicator.md +++ b/docs/script/plugins/focus-indicator.md @@ -30,4 +30,4 @@ void DoConfig(IContext context) return DoConfig; ``` -[!INCLUDE [Commands](../../_common/plugins/focus-indicator.md)] +[!INCLUDE [Commands](../../_includes/plugins/focus-indicator.md)] diff --git a/docs/script/plugins/gaps.md b/docs/script/plugins/gaps.md index abda2b0df..6e145b1b9 100644 --- a/docs/script/plugins/gaps.md +++ b/docs/script/plugins/gaps.md @@ -29,4 +29,4 @@ void DoConfig(IContext context) return DoConfig; ``` -[!INCLUDE [Commands](../../_common/plugins/gaps.md)] +[!INCLUDE [Commands](../../_includes/plugins/gaps.md)] diff --git a/docs/script/plugins/layout-preview.md b/docs/script/plugins/layout-preview.md index e5b27f7fb..01c02f1bb 100644 --- a/docs/script/plugins/layout-preview.md +++ b/docs/script/plugins/layout-preview.md @@ -33,4 +33,4 @@ return DoConfig; ``` -[!INCLUDE [Commands](../../_common/plugins/layout-preview.md)] +[!INCLUDE [Commands](../../_includes/plugins/layout-preview.md)] diff --git a/docs/script/plugins/slice-layout.md b/docs/script/plugins/slice-layout.md index 49308c76f..5e5b94b10 100644 --- a/docs/script/plugins/slice-layout.md +++ b/docs/script/plugins/slice-layout.md @@ -28,4 +28,4 @@ void DoConfig(IContext context) return DoConfig; ``` -[!INCLUDE [Commands](../../_common/plugins/slice-layout.md)] +[!INCLUDE [Commands](../../_includes/plugins/slice-layout.md)] diff --git a/docs/script/plugins/tree-layout.md b/docs/script/plugins/tree-layout.md index 97e2bfed5..07cdb828b 100644 --- a/docs/script/plugins/tree-layout.md +++ b/docs/script/plugins/tree-layout.md @@ -30,4 +30,4 @@ void DoConfig(IContext context) return DoConfig; ``` -[!INCLUDE [Commands](../../_common/plugins/tree-layout.md)] +[!INCLUDE [Commands](../../_includes/plugins/tree-layout.md)] diff --git a/docs/script/plugins/updater.md b/docs/script/plugins/updater.md index d3b02f73b..f0d781ffa 100644 --- a/docs/script/plugins/updater.md +++ b/docs/script/plugins/updater.md @@ -32,4 +32,4 @@ void DoConfig(IContext context) return DoConfig; ``` -[!INCLUDE [Commands](../../_common/plugins/updater.md)] +[!INCLUDE [Commands](../../_includes/plugins/updater.md)] diff --git a/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoaderTests.cs b/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoaderTests.cs index b1cdc499e..4765fc622 100644 --- a/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoaderTests.cs +++ b/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoaderTests.cs @@ -101,7 +101,7 @@ public void Load_FocusLayoutEngine(string config, bool maximize, bool isYaml, IC // Then the layout engine is loaded Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx)!; Assert.Single(engines); engines[0].Should().BeEquivalentTo(new FocusLayoutEngine(engines[0].Identity, maximize)); } @@ -148,7 +148,46 @@ public void Load_InvalidFocusLayoutEngine(string config, bool isYaml, IContext c // Then the layout engine is not loaded Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); - Assert.Empty(engines); + ILayoutEngine[]? engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + Assert.Null(engines); + } + + public static TheoryData NoLayoutEnginesConfig => + new() + { + { + """ + layout_engines: + entries: [] + """, + true + }, + { + """ + { + "layout_engines": { + "entries": [] + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(NoLayoutEnginesConfig))] + public void Load_NoLayoutEngines(string config, bool isYaml, IContext ctx) + { + // Given an invalid config with FocusLayoutEngine set + ctx.PluginManager.LoadedPlugins.Returns([]); + YamlLoaderTestUtils.SetupFileConfig(ctx, config, isYaml); + + // When loading the layout engine + bool result = YamlLoader.Load(ctx); + + // Then the layout engine is not loaded + Assert.True(result); + + ILayoutEngine[]? engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + Assert.Null(engines); } } diff --git a/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoader_SliceLayoutEngineTests.cs b/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoader_SliceLayoutEngineTests.cs index 810505eef..64824434e 100644 --- a/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoader_SliceLayoutEngineTests.cs +++ b/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoader_SliceLayoutEngineTests.cs @@ -53,7 +53,7 @@ public void Load_ColumnLayoutEngine(string config, bool isYaml, IContext ctx, IS // Then the layout engine is loaded Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx)!; Assert.Single(engines); SliceLayouts.CreateColumnLayout(ctx, plugin, engines[0].Identity).Should().BeEquivalentTo(engines[0]); } @@ -103,7 +103,7 @@ public void Load_RowLayoutEngine(string config, bool isYaml, IContext ctx, ISlic // Then the layout engine is loaded Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx)!; Assert.Single(engines); SliceLayouts.CreateRowLayout(ctx, plugin, engines[0].Identity).Should().BeEquivalentTo(engines[0]); } @@ -153,7 +153,7 @@ public void Load_PrimaryStackLayoutEngine(string config, bool isYaml, IContext c // Then the layout engine is loaded Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx)!; Assert.Single(engines); SliceLayouts.CreatePrimaryStackLayout(ctx, plugin, engines[0].Identity).Should().BeEquivalentTo(engines[0]); } @@ -213,7 +213,7 @@ ISliceLayoutPlugin plugin // Then the layout engine is loaded Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx)!; Assert.Single(engines); SliceLayouts .CreateMultiColumnLayout(ctx, plugin, engines[0].Identity, columns) @@ -312,7 +312,7 @@ ISliceLayoutPlugin plugin // Then the layout engine is loaded Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx)!; Assert.Single(engines); SliceLayouts .CreateSecondaryPrimaryLayout(ctx, plugin, engines[0].Identity, primaryCount, secondaryCount) @@ -365,8 +365,8 @@ public void Load_InvalidLayoutEngine(string config, bool isYaml, IContext ctx) // Then the layout engine is not loaded Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); - Assert.Empty(engines); + ILayoutEngine[]? engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + Assert.Null(engines); ctx.PluginManager.DidNotReceive().AddPlugin(Arg.Any()); } } diff --git a/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoader_TreeLayoutEngineTests.cs b/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoader_TreeLayoutEngineTests.cs index e35075cba..fae08940d 100644 --- a/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoader_TreeLayoutEngineTests.cs +++ b/src/Whim.Yaml.Tests/Layouts/YamlLayoutEngineLoader_TreeLayoutEngineTests.cs @@ -115,6 +115,31 @@ public class YamlLayoutEngineLoader_TreeLayoutEngineTests false, Direction.Down }, + // Use the default direction. + { + """ + layout_engines: + entries: + - type: TreeLayoutEngine + """, + true, + Direction.Right + }, + { + """ + { + "layout_engines": { + "entries": [ + { + "type": "TreeLayoutEngine" + } + ] + } + } + """, + false, + Direction.Right + }, }; [Theory] @@ -134,7 +159,7 @@ public void LoadTreeLayoutEngine(string yaml, bool isValid, Direction direction, // Then the tree layout engine should be updated Assert.True(result); - ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx); + ILayoutEngine[] engines = YamlLoaderTestUtils.GetLayoutEngines(ctx)!; Assert.Single(engines); new TreeLayoutEngine(ctx, Substitute.For(), engines[0].Identity) .Should() diff --git a/src/Whim.Yaml.Tests/YamlLoader/YamlLoader_LoadWorkspacesTests.cs b/src/Whim.Yaml.Tests/YamlLoader/YamlLoader_LoadWorkspacesTests.cs new file mode 100644 index 000000000..88041d3b7 --- /dev/null +++ b/src/Whim.Yaml.Tests/YamlLoader/YamlLoader_LoadWorkspacesTests.cs @@ -0,0 +1,152 @@ +using System.Collections.Immutable; +using FluentAssertions; +using Whim.SliceLayout; +using Whim.TestUtils; +using Xunit; + +namespace Whim.Yaml.Tests; + +public class YamlLoader_LoadWorkspacesTests +{ + public static TheoryData WorkspacesConfig => + new() + { + { + """ + workspaces: + entries: + - name: workspace1 + - name: workspace2 + - name: workspace3 + layout_engines: + entries: + - type: FocusLayoutEngine + - type: SliceLayoutEngine + variant: + type: column + """, + true + }, + { + """ + { + "workspaces": { + "entries": [ + { + "name": "workspace1" + }, + { + "name": "workspace2" + }, + { + "name": "workspace3", + "layout_engines": { + "entries": [ + { + "type": "FocusLayoutEngine" + }, + { + "type": "SliceLayoutEngine", + "variant": { + "type": "column" + } + } + ] + } + } + ] + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(WorkspacesConfig))] + public void Load_Workspaces(string config, bool isYaml, IContext ctx, ISliceLayoutPlugin plugin) + { + // Given a valid config with workspaces set + YamlLoaderTestUtils.SetupFileConfig(ctx, config, isYaml); + + // When loading the workspaces + bool result = YamlLoader.Load(ctx); + + // Then the workspaces are loaded + Assert.True(result); + + IWorkspace[] workspaces = YamlLoaderTestUtils.GetWorkspaces(ctx)!; + Assert.Equal(3, workspaces.Length); + + // ...with the expected names... +#pragma warning disable CS0618 // Type or member is obsolete + Assert.Equal("workspace1", workspaces[0].Name); + Assert.Equal("workspace2", workspaces[1].Name); + Assert.Equal("workspace3", workspaces[2].Name); +#pragma warning restore CS0618 // Type or member is obsolete + + // ...and the expected layout engines. + Assert.Empty(workspaces[0].LayoutEngines); + Assert.Empty(workspaces[1].LayoutEngines); + + var engines = workspaces[2].LayoutEngines; + ImmutableList expectedLayoutEngines = + [ + new FocusLayoutEngine(engines[0].Identity), + SliceLayouts.CreateColumnLayout(ctx, plugin, engines[1].Identity), + ]; + + expectedLayoutEngines.Should().BeEquivalentTo(engines); + } + + public static TheoryData NoWorkspacesConfig => + new() + { + { + """ + workspaces: + entries: [] + """, + true + }, + { + """ + { + "workspaces": { + "entries": [] + } + } + """, + false + }, + { + """ + workspaces: + """, + true + }, + { + """ + { + "workspaces": {} + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(NoWorkspacesConfig))] + public void Load_NoWorkspaces(string config, bool isYaml, IContext ctx) + { + // Given a valid config with no workspaces set + YamlLoaderTestUtils.SetupFileConfig(ctx, config, isYaml); + + // When loading the workspaces + bool result = YamlLoader.Load(ctx); + + // Then the workspaces are loaded + Assert.True(result); + + IWorkspace[] workspaces = YamlLoaderTestUtils.GetWorkspaces(ctx)!; + Assert.Empty(workspaces); + } +} diff --git a/src/Whim.Yaml.Tests/YamlLoaderTestUtils.cs b/src/Whim.Yaml.Tests/YamlLoaderTestUtils.cs index d159e43d0..bdb68f123 100644 --- a/src/Whim.Yaml.Tests/YamlLoaderTestUtils.cs +++ b/src/Whim.Yaml.Tests/YamlLoaderTestUtils.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using NSubstitute; namespace Whim.Yaml.Tests; @@ -10,10 +11,43 @@ public static void SetupFileConfig(IContext ctx, string config, bool isYaml) ctx.FileManager.ReadAllText(Arg.Is(s => s.EndsWith(isYaml ? "yaml" : "json"))).Returns(config); } - public static ILayoutEngine[] GetLayoutEngines(IContext ctx) + public static ILayoutEngine[]? GetLayoutEngines(IContext ctx) { - SetCreateLayoutEnginesTransform transform = (SetCreateLayoutEnginesTransform) - ctx.Store.ReceivedCalls().First().GetArguments()[0]!; - return transform.CreateLayoutEnginesFn().Select(x => x(new())).ToArray(); + var arguments = ctx.Store.ReceivedCalls().FirstOrDefault()?.GetArguments(); + if (arguments is null) + { + return null; + } + + SetCreateLayoutEnginesTransform? transform = (SetCreateLayoutEnginesTransform?)arguments[0]; + return transform?.CreateLayoutEnginesFn().Select(x => x(new())).ToArray(); + } + + public static IWorkspace[]? GetWorkspaces(IContext ctx) + { + List workspaces = []; + + foreach (var call in ctx.Store.ReceivedCalls()) + { + var arguments = call.GetArguments(); + if (arguments.Length == 1 && arguments[0] is AddWorkspaceTransform transform) + { + IWorkspace workspace = Substitute.For(); + + workspace.Id.Returns(transform.WorkspaceId); + +#pragma warning disable CS0618 // Type or member is obsolete + workspace.Name.Returns(transform.Name); +#pragma warning restore CS0618 // Type or member is obsolete + + + var engines = transform.CreateLeafLayoutEngines?.Select(c => c(new())); + workspace.LayoutEngines.Returns(engines?.ToImmutableList() ?? []); + + workspaces.Add(workspace); + } + } + + return [.. workspaces]; } } diff --git a/src/Whim.Yaml/YamlLayoutEngineLoader.cs b/src/Whim.Yaml/YamlLayoutEngineLoader.cs index 41edf8347..610e77768 100644 --- a/src/Whim.Yaml/YamlLayoutEngineLoader.cs +++ b/src/Whim.Yaml/YamlLayoutEngineLoader.cs @@ -14,8 +14,27 @@ public static void UpdateLayoutEngines(IContext ctx, Schema schema) return; } + CreateLeafLayoutEngine[]? engineCreators = GetCreateLeafLayoutEngines(ctx, [.. layoutEngines.Entries]); + + if (engineCreators is not null) + { + ctx.Store.Dispatch(new SetCreateLayoutEnginesTransform(() => engineCreators)); + } + } + + public static CreateLeafLayoutEngine[]? GetCreateLeafLayoutEngines( + IContext ctx, + Schema.RequiredType[]? layoutEngines + ) + { + if (layoutEngines is null) + { + return null; + } + List leafLayoutEngineCreators = []; - foreach (var engine in layoutEngines.Entries) + + foreach (var engine in layoutEngines) { engine.Match( (in Schema.ALayoutEngineThatDisplaysOneWindowAtATime focusLayoutEngine) => @@ -39,7 +58,7 @@ public static void UpdateLayoutEngines(IContext ctx, Schema schema) ); } - ctx.Store.Dispatch(new SetCreateLayoutEnginesTransform(() => [.. leafLayoutEngineCreators])); + return leafLayoutEngineCreators.Count == 0 ? null : [.. leafLayoutEngineCreators]; } private static void CreateFocusLayoutEngineCreator( @@ -122,14 +141,18 @@ is not TreeLayoutPlugin plugin ctx.PluginManager.AddPlugin(plugin); } - Direction defaultAddNodeDirection = (string)treeLayoutEngine.InitialDirection switch + Direction defaultAddNodeDirection = Direction.Right; + if (treeLayoutEngine.InitialDirection.TryGetString(out string? initialDirection)) { - "left" => Direction.Left, - "right" => Direction.Right, - "up" => Direction.Up, - "down" => Direction.Down, - _ => Direction.Right, - }; + defaultAddNodeDirection = initialDirection switch + { + "left" => Direction.Left, + "right" => Direction.Right, + "up" => Direction.Up, + "down" => Direction.Down, + _ => Direction.Right, + }; + } leafLayoutEngineCreators.Add( (id) => diff --git a/src/Whim.Yaml/YamlLoader.cs b/src/Whim.Yaml/YamlLoader.cs index 2a605d8df..502b0c528 100644 --- a/src/Whim.Yaml/YamlLoader.cs +++ b/src/Whim.Yaml/YamlLoader.cs @@ -28,6 +28,8 @@ public static bool Load(IContext ctx) return false; } + UpdateWorkspaces(ctx, schema); + UpdateKeybinds(ctx, schema); UpdateFilters(ctx, schema); UpdateRouters(ctx, schema); @@ -67,11 +69,39 @@ public static bool Load(IContext ctx) return null; } + private static void UpdateWorkspaces(IContext ctx, Schema schema) + { + if (!schema.Workspaces.IsValid()) + { + Logger.Debug("Workspaces config is not valid."); + return; + } + + if (schema.Workspaces.Entries.AsOptional() is not { } entries) + { + Logger.Debug("No workspaces found."); + return; + } + + foreach (var workspace in entries) + { + string workspaceName = (string)workspace.Name; + + CreateLeafLayoutEngine[]? engines = null; + if (workspace.LayoutEngines.Entries.AsOptional() is Schema.RequiredEntries.RequiredTypeArray definedEngines) + { + engines = YamlLayoutEngineLoader.GetCreateLeafLayoutEngines(ctx, [.. definedEngines]); + } + + ctx.Store.Dispatch(new AddWorkspaceTransform(workspaceName, engines)); + } + } + private static void UpdateKeybinds(IContext ctx, Schema schema) { if (!schema.Keybinds.IsValid()) { - Logger.Debug("Keybinds plugin is not valid."); + Logger.Debug("Keybinds config is not valid."); return; } @@ -102,7 +132,7 @@ private static void UpdateFilters(IContext ctx, Schema schema) { if (!schema.Filters.IsValid()) { - Logger.Debug("Filters plugin is not valid."); + Logger.Debug("Filters config is not valid."); return; } @@ -141,7 +171,7 @@ private static void UpdateRouters(IContext ctx, Schema schema) { if (!schema.Routers.IsValid()) { - Logger.Debug("Routers plugin is not valid."); + Logger.Debug("Routers cohfig is not valid."); return; } diff --git a/src/Whim.Yaml/schema.json b/src/Whim.Yaml/schema.json index 541d278ae..f31b4fa0e 100644 --- a/src/Whim.Yaml/schema.json +++ b/src/Whim.Yaml/schema.json @@ -6,17 +6,21 @@ "additionalProperties": false, "properties": { - "layout_engines": { + "workspaces": { "type": "object", "properties": { "entries": { "type": "array", - "items": { "$ref": "#/$defs/LayoutEngine" }, - "description": "Layout engines to use for workspaces" + "items": { "$ref": "#/$defs/Workspace" }, + "description": "Workspaces to use" } } }, + "layout_engines": { + "$ref": "#/$defs/LayoutEngineList" + }, + "keybinds": { "type": "object", "properties": { @@ -103,6 +107,33 @@ }, "$defs": { + "Workspace": { + "required": ["name"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the workspace" + }, + "layout_engines": { + "$ref": "#/$defs/LayoutEngineList", + "description": "The layout engines to use for the workspace. If not specified, the layout engines in `layout_engines` will be used." + } + } + }, + + "LayoutEngineList": { + "type": "object", + "required": ["entries"], + "properties": { + "entries": { + "type": "array", + "items": { "$ref": "#/$defs/LayoutEngine" }, + "description": "Layout engines to use for workspaces" + } + } + }, + "LayoutEngine": { "required": ["type"], "anyOf": [