Skip to content
This repository has been archived by the owner on Apr 1, 2020. It is now read-only.

Commit

Permalink
Externalized UI: Implement Oni split mode (#1682)
Browse files Browse the repository at this point in the history
* Add split bindings

* Get tests green again

* Get split tests green

* Add layout logic plus testc ases

* Fix lint issues

* Get oni-splits working end-to-end

* Tweak positioning of splits

* Add basic window layout algorithm

* Fix lint issue

* Fix test issues

* Fix window click behavior

* Fix compilation issue

* Fix split direction issue

* Fix lint issue

* Hook up <C-w>s/<C-w>v bindings for oni.tabs.mode

* Fall back to native splits if editor.split.mode isn't set to oni

* Remove todo

* Fix class name for editor
  • Loading branch information
bryphe committed Mar 5, 2018
1 parent 0307da1 commit f974159
Show file tree
Hide file tree
Showing 11 changed files with 554 additions and 109 deletions.
73 changes: 53 additions & 20 deletions browser/src/Editor/OniEditor/OniEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as Log from "./../../Log"
import { PluginManager } from "./../../Plugins/PluginManager"

import { IColors } from "./../../Services/Colors"
import { commandManager } from "./../../Services/CommandManager"
import { CompletionProviders } from "./../../Services/Completion"
import { Configuration } from "./../../Services/Configuration"
import { IDiagnosticsDataSource } from "./../../Services/Diagnostics"
Expand All @@ -45,7 +46,7 @@ import { ErrorsContainer } from "./containers/ErrorsContainer"

import { NeovimEditor } from "./../NeovimEditor"

import { windowManager } from "./../../Services/WindowManager"
import { SplitDirection, windowManager } from "./../../Services/WindowManager"

import { ImageBufferLayer } from "./ImageBufferLayer"

Expand Down Expand Up @@ -165,6 +166,22 @@ export class OniEditor implements IEditor {
this._neovimEditor.enter()

editorManager.setActiveEditor(this)

commandManager.registerCommand({
command: "editor.split.horizontal",
execute: () => this._split("horizontal"),
enabled: () => editorManager.activeEditor === this,
name: null,
detail: null,
})

commandManager.registerCommand({
command: "editor.split.vertical",
execute: () => this._split("vertical"),
enabled: () => editorManager.activeEditor === this,
name: null,
detail: null,
})
}

public leave(): void {
Expand All @@ -182,25 +199,9 @@ export class OniEditor implements IEditor {
openMode === Oni.FileOpenMode.HorizontalSplit ||
openMode === Oni.FileOpenMode.VerticalSplit
) {
const newEditor = new OniEditor(
this._colors,
this._completionProviders,
this._configuration,
this._diagnostics,
this._languageManager,
this._menuManager,
this._overlayManager,
this._pluginManager,
this._snippetManager,
this._tasks,
this._themeManager,
this._tokenColors,
this._workspace,
)

// TODO
windowManager.createSplit("vertical", newEditor)
await newEditor.init([])
const splitDirection =
openMode === Oni.FileOpenMode.HorizontalSplit ? "horizontal" : "vertical"
const newEditor = await this._split(splitDirection)
return newEditor.openFile(file, { openMode: Oni.FileOpenMode.Edit })
}
}
Expand Down Expand Up @@ -251,4 +252,36 @@ export class OniEditor implements IEditor {
public render(): JSX.Element {
return this._neovimEditor.render()
}

private async _split(direction: SplitDirection): Promise<OniEditor> {
if (this._configuration.getValue("editor.split.mode") !== "oni") {
if (direction === "horizontal") {
await this._neovimEditor.neovim.command(":sp")
} else {
await this._neovimEditor.neovim.command(":vsp")
}

return this
}

const newEditor = new OniEditor(
this._colors,
this._completionProviders,
this._configuration,
this._diagnostics,
this._languageManager,
this._menuManager,
this._overlayManager,
this._pluginManager,
this._snippetManager,
this._tasks,
this._themeManager,
this._tokenColors,
this._workspace,
)

windowManager.createSplit(direction, newEditor, this)
await newEditor.init([])
return newEditor
}
}
61 changes: 42 additions & 19 deletions browser/src/Services/WindowManager/LinearSplitProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,27 @@
import {
Direction,
IAugmentedSplitInfo,
ISplitInfo,
IWindowSplitProvider,
SingleSplitProvider,
SplitDirection,
SplitOrLeaf,
} from "./index"

export class LinearSplitProvider implements IWindowSplitProvider {
private _splitProviders: IWindowSplitProvider[] = []
export const getInverseDirection = (splitDirection: SplitDirection): SplitDirection => {
switch (splitDirection) {
case "horizontal":
return "vertical"
case "vertical":
default:
return "horizontal"
}
}

constructor(private _direction: SplitDirection) {}
export class LinearSplitProvider implements IWindowSplitProvider {
constructor(
private _direction: SplitDirection,
private _splitProviders: IWindowSplitProvider[] = [],
) {}

public contains(split: IAugmentedSplitInfo): boolean {
return this._getProviderForSplit(split) != null
Expand Down Expand Up @@ -46,18 +57,16 @@ export class LinearSplitProvider implements IWindowSplitProvider {
direction: SplitDirection,
referenceSplit?: IAugmentedSplitInfo,
): boolean {
// If there are no children, we can just match direction
if (this._splitProviders.length === 0) {
this._direction = getInverseDirection(direction)
this._splitProviders.push(new SingleSplitProvider(split))
return true
}

// If there is no reference split, we can just tack this split on
if (!referenceSplit) {
if (direction === this._direction) {
this._splitProviders.push(new SingleSplitProvider(split))
} else {
const childSplitProvider = new LinearSplitProvider(this._direction)
childSplitProvider._splitProviders = this._splitProviders
this._splitProviders = [childSplitProvider]
this._splitProviders.push(new SingleSplitProvider(split))
this._direction = direction
}

this._splitProviders.push(new SingleSplitProvider(split))
return true
}

Expand All @@ -67,7 +76,7 @@ export class LinearSplitProvider implements IWindowSplitProvider {
return false
}

const result = containingSplit.split(split, direction)
const result = containingSplit.split(split, direction, referenceSplit)

// Containing split handled it, so we're good
if (result) {
Expand All @@ -77,11 +86,25 @@ export class LinearSplitProvider implements IWindowSplitProvider {
// If the split requested is oriented differently,
// create a new provider to handle that
if (direction !== this._direction) {
// TODO
} else {
// Otherwise, we can - let's wrap up the split in a provider
const singleSplitProvider = new SingleSplitProvider(split)
this._splitProviders.push(singleSplitProvider)
} else {
// Otherwise, we can - let's wrap up the split in a provider

const previousIndex = this._splitProviders.indexOf(containingSplit)
const elementsBefore = this._splitProviders.slice(0, previousIndex)
const elementsAfter = this._splitProviders.slice(
previousIndex + 1,
this._splitProviders.length,
)

const children = [containingSplit, new SingleSplitProvider(split)]
const childSplitProvider = new LinearSplitProvider(
getInverseDirection(this._direction),
children,
)

this._splitProviders = [...elementsBefore, childSplitProvider, ...elementsAfter]
}

return true
Expand Down Expand Up @@ -136,7 +159,7 @@ export class LinearSplitProvider implements IWindowSplitProvider {
return this._splitProviders[newIndex].move(null, direction)
}

public getState(): SplitOrLeaf<IAugmentedSplitInfo> {
public getState(): ISplitInfo<IAugmentedSplitInfo> {
return {
type: "Split",
direction: this._direction,
Expand Down
36 changes: 17 additions & 19 deletions browser/src/Services/WindowManager/WindowManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export class AugmentedWindow implements IAugmentedSplitInfo {

constructor(private _id: string, private _innerSplit: Oni.IWindowSplit | any) {}

public get innerSplit(): Oni.IWindowSplit {
return this._innerSplit
}

public render(): JSX.Element {
return this._innerSplit.render()
}
Expand Down Expand Up @@ -146,27 +150,10 @@ export class WindowManager {
this._rootNavigator.setRelationship(this._leftDock, this._primarySplit, "right")
}

// public split(
// direction: SplitDirection,
// newSplit: Oni.IWindowSplit,
// referenceSplit?: Oni.IWindowSplit,
// ) {

// this._primarySplit.split(augmentedWindow, direction, referenceSplit)
// const newState = this._primarySplit.getState() as ISplitInfo<Oni.IWindowSplit>

// this._store.dispatch({
// type: "SET_PRIMARY_SPLITS",
// splits: newState,
// })

// this._focusNewSplit(newSplit)
// }

public createSplit(
splitLocation: Direction | SplitDirection,
newSplit: Oni.IWindowSplit,
referenceSplit?: any,
referenceSplit?: Oni.IWindowSplit,
): WindowSplitHandle {
const nextId = this._lastId++
const windowId = "oni.window." + nextId.toString()
Expand All @@ -189,7 +176,8 @@ export class WindowManager {
}
case "horizontal":
case "vertical":
this._primarySplit.split(augmentedWindow, splitLocation, referenceSplit)
const augmentedRefSplit = this._getAugmentedWindowSplitFromSplit(referenceSplit)
this._primarySplit.split(augmentedWindow, splitLocation, augmentedRefSplit)
const newState = this._primarySplit.getState() as ISplitInfo<Oni.IWindowSplit>

this._store.dispatch({
Expand All @@ -203,6 +191,11 @@ export class WindowManager {
return new WindowSplitHandle(this._store, this, windowId)
}

public getSplitHandle(split: Oni.IWindowSplit): WindowSplitHandle {
const augmentedSplit = this._getAugmentedWindowSplitFromSplit(split)
return new WindowSplitHandle(this._store, this, augmentedSplit.id)
}

public move(direction: Direction): void {
const focusedSplit = this._store.getState().focusedSplitId

Expand Down Expand Up @@ -259,6 +252,11 @@ export class WindowManager {
this._focusNewSplit(split)
}

private _getAugmentedWindowSplitFromSplit(split: Oni.IWindowSplit): IAugmentedSplitInfo {
const augmentedWindows = Object.values(this._idToSplit)
return augmentedWindows.find(aw => aw.innerSplit === split) || null
}

private _focusNewSplit(newSplit: any): void {
if (this.activeSplit && this.activeSplit.leave) {
this.activeSplit.leave()
Expand Down
2 changes: 2 additions & 0 deletions browser/src/Services/WindowManager/WindowManagerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface IAugmentedSplitInfo extends Oni.IWindowSplit {
// Internal bookkeeping
id: string

innerSplit: Oni.IWindowSplit

// Potential API methods
enter?(): void
leave?(): void
Expand Down
1 change: 1 addition & 0 deletions browser/src/Services/WindowManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* to the active editor, and managing transitions between editors.
*/

export * from "./layoutFromSplitInfo"
export * from "./LinearSplitProvider"
export * from "./RelationalSplitNavigator"
export * from "./SingleSplitProvider"
Expand Down
69 changes: 69 additions & 0 deletions browser/src/Services/WindowManager/layoutFromSplitInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* layoutFromSplitInfo.ts
*
* Function to layout splits into a particular window
*/

import * as Oni from "oni-api"

import { IAugmentedSplitInfo, ISplitInfo, SplitOrLeaf } from "./WindowManagerStore"

export interface LayoutResultInfo {
rectangle: Oni.Shapes.Rectangle
split: IAugmentedSplitInfo
}

export interface LayoutResult {
[windowId: string]: LayoutResultInfo
}

export const layoutFromSplitInfo = (
splits: ISplitInfo<IAugmentedSplitInfo>,
width: number,
height: number,
): LayoutResult => {
return layoutFromSplitInfoHelper(splits, Oni.Shapes.Rectangle.create(0, 0, width, height))
}

const layoutFromSplitInfoHelper = (
split: SplitOrLeaf<IAugmentedSplitInfo>,
rectangle: Oni.Shapes.Rectangle,
): LayoutResult => {
// Base case..
if (split.type === "Leaf") {
return {
[split.contents.id]: {
rectangle,
split: split.contents,
},
}
}

if (split.splits.length === 0) {
return {}
}

// Recursive case
//
// TODO: Handle specified sizes for the windows. We're just distributing the space evenly, currently.
const splitWidth =
split.direction === "horizontal" ? rectangle.width / split.splits.length : rectangle.width
const splitHeight =
split.direction === "vertical" ? rectangle.height / split.splits.length : rectangle.height

let ret = {}

for (let i = 0; i < split.splits.length; i++) {
const x = split.direction === "horizontal" ? rectangle.x + splitWidth * i : rectangle.x
const y = split.direction === "vertical" ? rectangle.y + splitHeight * i : rectangle.y

const rect = Oni.Shapes.Rectangle.create(x, y, splitWidth, splitHeight)

ret = {
...ret,
...layoutFromSplitInfoHelper(split.splits[i], rect),
}
}

return ret
}
Loading

0 comments on commit f974159

Please sign in to comment.