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

Commit

Permalink
Feature/oni sessions (#2479)
Browse files Browse the repository at this point in the history
* add session manager, store and sidebar pane

* hook up session services to app

* move side effects into epics

* add creation and cancel creation actions

* add restore session epic and remove console.log

* await sessions in case of error

* add error handling to session functions

* Revert "Refocus previously open menu on reactivating Oni (#2472)"

This reverts commit 97f0c61.

* Revert "Refocus previously open menu on reactivating Oni (#2472)"

This reverts commit 97f0c61.

* remove console.log

* remove unused props passed to session pane

* add persist session command [WIP]

* Add current session action and update store with i

t

* use get user config add icons and some error handling

* add unclick handlers for sessions
move section title to general location
add delete session functionality

* add title and toggling of sessions add on vim leave handler

* fix lint errors

* refactor epics in preparation for 1.0 and rxjs 6

* update snapshot

* add bufdo bd command prior to restoring session
fix delete session bug causing reappearances

* create separate close all buffers method

* remove update session method

* Add audit time to update current session

* add close all buffers method and use persist session action in update session

* add restore session error action and complete action

* add console log for debugging return metadata directly

* add error handling to git blame function

* reduce persist audit time make ids in session.tsx readonly

* comment out console.log

* check neovim for current session when updating
this ensures the session is valid
added getCurrentSession method which checks vims v:this_session var

* fix lint errors

* add tests for sessions component and mock for neovim instance

* switch generic input check to specific text input view check

* add update timestamp functionality so these are more accurate

* switch to adding updated at by checking mtime of f

ile

* switch to storing sessions in persistent store

* add delete functionality to persistent store and mock

* fix lint error

* rename sessionName var to name for simplicity

* add session manager initial test

* create path using path.join

* add experimental config flag

* update Oni mock and increase sessionmanager tests

* add session store tests - [WIP]

* return simple session manager mock
remove need for instantiation, use done callback

* remove session store tests as redux observable api has changed
a large refactor of all observables is going to be required to upgrade redux observable so seems counter productive to write tests that will need to be re-written entirely once that is done

* add user configurable session directory

* tweak sidebar item styles

* update vim navigator to pass function to update selected item on click
render session sidebar item second

* fix lint error

* fix broken tests
  • Loading branch information
akinsho authored Aug 22, 2018
1 parent 2fd82f3 commit e3f65e6
Show file tree
Hide file tree
Showing 37 changed files with 1,245 additions and 149 deletions.
5 changes: 5 additions & 0 deletions browser/src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const start = async (args: string[]): Promise<void> => {
const themesPromise = import("./Services/Themes")
const iconThemesPromise = import("./Services/IconThemes")

const sessionManagerPromise = import("./Services/Sessions")
const sidebarPromise = import("./Services/Sidebar")
const overlayPromise = import("./Services/Overlay")
const statusBarPromise = import("./Services/StatusBar")
Expand Down Expand Up @@ -327,6 +328,10 @@ export const start = async (args: string[]): Promise<void> => {
Sidebar.getInstance(),
WindowManager.windowManager,
)

const Sessions = await sessionManagerPromise
Sessions.activate(oniApi, sidebarManager)

Performance.endMeasure("Oni.Start.Sidebar")

const createLanguageClientsFromConfiguration =
Expand Down
40 changes: 40 additions & 0 deletions browser/src/Editor/NeovimEditor/NeovimEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { Completion, CompletionProviders } from "./../../Services/Completion"
import { Configuration, IConfigurationValues } from "./../../Services/Configuration"
import { IDiagnosticsDataSource } from "./../../Services/Diagnostics"
import { Overlay, OverlayManager } from "./../../Services/Overlay"
import { ISession } from "./../../Services/Sessions"
import { SnippetManager } from "./../../Services/Snippets"
import { TokenColors } from "./../../Services/TokenColors"

Expand Down Expand Up @@ -99,6 +100,8 @@ import { CanvasRenderer } from "../../Renderer/CanvasRenderer"
import { WebGLRenderer } from "../../Renderer/WebGL/WebGLRenderer"
import { getInstance as getNotificationsInstance } from "./../../Services/Notifications"

type NeovimError = [number, string]

export class NeovimEditor extends Editor implements Oni.Editor {
private _bufferManager: BufferManager
private _neovimInstance: NeovimInstance
Expand Down Expand Up @@ -889,6 +892,31 @@ export class NeovimEditor extends Editor implements Oni.Editor {
)
}

// "v:this_session" |this_session-variable| - is a variable nvim sets to the path of
// the current session file when one is loaded we use it here to check the current session
// if it in oni's session dir then this is updated
public async getCurrentSession(): Promise<string | void> {
const result = await this._neovimInstance.request<string | NeovimError>("nvim_get_vvar", [
"this_session",
])

if (Array.isArray(result)) {
return this._handleNeovimError(result)
}
return result
}

public async persistSession(session: ISession) {
const result = await this._neovimInstance.command(`mksession! ${session.file}`)
return this._handleNeovimError(result)
}

public async restoreSession(session: ISession) {
await this._neovimInstance.closeAllBuffers()
const result = await this._neovimInstance.command(`source ${session.file}`)
return this._handleNeovimError(result)
}

public async openFile(
file: string,
openOptions: Oni.FileOpenOptions = Oni.DefaultFileOpenOptions,
Expand Down Expand Up @@ -1295,4 +1323,16 @@ export class NeovimEditor extends Editor implements Oni.Editor {
}
}
}

private _handleNeovimError(result: NeovimError | void): void {
if (!result) {
return null
}
// the first value of the error response is a 0
if (Array.isArray(result) && !result[0]) {
const [, error] = result
Log.warn(error)
throw new Error(error)
}
}
}
17 changes: 17 additions & 0 deletions browser/src/Editor/OniEditor/OniEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { NeovimEditor } from "./../NeovimEditor"

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

import { ISession } from "../../Services/Sessions"
import { IBuffer } from "../BufferManager"
import ColorHighlightLayer from "./ColorHighlightLayer"
import { ImageBufferLayer } from "./ImageBufferLayer"
Expand Down Expand Up @@ -101,6 +102,10 @@ export class OniEditor extends Utility.Disposable implements Oni.Editor {
return this._neovimEditor.activeBuffer
}

public get onQuit(): IEvent<void> {
return this._neovimEditor.onNeovimQuit
}

// Capabilities
public get neovim(): Oni.NeovimEditorCapability {
return this._neovimEditor.neovim
Expand Down Expand Up @@ -288,6 +293,18 @@ export class OniEditor extends Utility.Disposable implements Oni.Editor {
this._neovimEditor.executeCommand(command)
}

public restoreSession(sessionDetails: ISession) {
return this._neovimEditor.restoreSession(sessionDetails)
}

public getCurrentSession() {
return this._neovimEditor.getCurrentSession()
}

public persistSession(sessionDetails: ISession) {
return this._neovimEditor.persistSession(sessionDetails)
}

public getBuffers(): Array<Oni.Buffer | Oni.InactiveBuffer> {
return this._neovimEditor.getBuffers()
}
Expand Down
4 changes: 4 additions & 0 deletions browser/src/Input/KeyBindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const applyDefaultKeyBindings = (oni: Oni.Plugin.Api, config: Configurati
!isMenuOpen()

const isExplorerActive = () => isSidebarPaneOpen("oni.sidebar.explorer")
const areSessionsActive = () => isSidebarPaneOpen("oni.sidebar.sessions")
const isVCSActive = () => isSidebarPaneOpen("oni.sidebar.vcs")

const isMenuOpen = () => menu.isMenuOpen()
Expand Down Expand Up @@ -168,4 +169,7 @@ export const applyDefaultKeyBindings = (oni: Oni.Plugin.Api, config: Configurati
input.bind("u", "vcs.unstage", isVCSActive)
input.bind("<c-r>", "vcs.refresh", isVCSActive)
input.bind("?", "vcs.showHelp", isVCSActive)

// Sessions
input.bind("<c-d>", "oni.sessions.delete", areSessionsActive)
}
10 changes: 10 additions & 0 deletions browser/src/PersistentStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const PersistentSettings = remote.require("electron-settings")
export interface IPersistentStore<T> {
get(): Promise<T>
set(value: T): Promise<void>
delete(key: string): Promise<T>
has(key: string): boolean
}

export const getPersistentStore = <T>(
Expand Down Expand Up @@ -70,4 +72,12 @@ export class PersistentStore<T> implements IPersistentStore<T> {

PersistentSettings.set(this._storeKey, JSON.stringify(this._currentValue))
}

public has(key: string) {
return PersistentSettings.has(key)
}

public async delete(key: string) {
return PersistentSettings.delete(`${this._storeKey}.${key}`)
}
}
6 changes: 4 additions & 2 deletions browser/src/Services/Configuration/DefaultConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ const BaseConfiguration: IConfigurationValues = {
"wildmenu.mode": true,
"commandline.mode": true,
"commandline.icons": true,
"experimental.vcs.sidebar": false,
"experimental.particles.enabled": false,
"experimental.preview.enabled": false,
"experimental.welcome.enabled": false,
"experimental.particles.enabled": false,
"experimental.sessions.enabled": false,
"experimental.sessions.directory": null,
"experimental.vcs.sidebar": false,
"experimental.vcs.blame.enabled": false,
"experimental.vcs.blame.mode": "auto",
"experimental.vcs.blame.timeout": 800,
Expand Down
5 changes: 4 additions & 1 deletion browser/src/Services/Configuration/IConfigurationValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ export interface IConfigurationValues {

// Whether or not the learning pane is available
"experimental.particles.enabled": boolean

// Whether or not the sessions sidebar pane is enabled
"experimental.sessions.enabled": boolean
// A User specified directory for where Oni session files should be saved
"experimental.sessions.directory": string
// Whether Version control sidebar item is enabled
"experimental.vcs.sidebar": boolean
// Whether the color highlight layer is enabled
Expand Down
179 changes: 179 additions & 0 deletions browser/src/Services/Sessions/SessionManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import * as fs from "fs-extra"
import { Editor, EditorManager, Plugin } from "oni-api"
import { IEvent } from "oni-types"
import * as path from "path"

import { SidebarManager } from "../Sidebar"
import { SessionActions, SessionsPane, store } from "./"
import { getPersistentStore, IPersistentStore } from "./../../PersistentStore"
import { getUserConfigFolderPath } from "./../../Services/Configuration/UserConfiguration"

export interface ISession {
name: string
id: string
file: string
directory: string
updatedAt?: string
workspace: string
// can be use to save other metadata for restoration like statusbar info or sidebar info etc
metadata?: { [key: string]: any }
}

export interface ISessionService {
sessionsDir: string
sessions: ISession[]
persistSession(sessionName: string): Promise<ISession>
restoreSession(sessionName: string): Promise<ISession>
}

export interface UpdatedOni extends Plugin.Api {
editors: UpdatedEditorManager
}

interface UpdatedEditorManager extends EditorManager {
activeEditor: UpdatedEditor
}

interface UpdatedEditor extends Editor {
onQuit: IEvent<void>
persistSession(sessionDetails: ISession): Promise<ISession>
restoreSession(sessionDetails: ISession): Promise<ISession>
getCurrentSession(): Promise<string | void>
}

/**
* Class SessionManager
*
* Provides a service to manage oni session i.e. buffers, screen layout etc.
*
*/
export class SessionManager implements ISessionService {
private _store = store({ sessionManager: this, fs })
private get _sessionsDir() {
const defaultDirectory = path.join(getUserConfigFolderPath(), "sessions")
const userDirectory = this._oni.configuration.getValue<string>(
"experimental.sessions.directory",
)
const directory = userDirectory || defaultDirectory
return directory
}

constructor(
private _oni: UpdatedOni,
private _sidebarManager: SidebarManager,
private _persistentStore: IPersistentStore<{ [sessionName: string]: ISession }>,
) {
fs.ensureDirSync(this.sessionsDir)
const enabled = this._oni.configuration.getValue<boolean>("experimental.sessions.enabled")
if (enabled) {
this._sidebarManager.add(
"save",
new SessionsPane({ store: this._store, commands: this._oni.commands }),
)
}
this._setupSubscriptions()
}

public get sessions() {
return this._store.getState().sessions
}

public get sessionsDir() {
return this._sessionsDir
}

public async updateOniSession(name: string, value: Partial<ISession>) {
const persistedSessions = await this._persistentStore.get()
if (name in persistedSessions) {
this._persistentStore.set({
...persistedSessions,
[name]: { ...persistedSessions[name], ...value },
})
}
}

public async createOniSession(sessionName: string) {
const persistedSessions = await this._persistentStore.get()
const file = this._getSessionFilename(sessionName)

const session: ISession = {
file,
id: sessionName,
name: sessionName,
directory: this.sessionsDir,
workspace: this._oni.workspace.activeWorkspace,
metadata: null,
}

this._persistentStore.set({ ...persistedSessions, [sessionName]: session })

return session
}

/**
* Retrieve or Create a persistent Oni Session
*
* @name getSessionFromStore
* @function
* @param {string} sessionName The name of the session
* @returns {ISession} The session metadata object
*/
public async getSessionFromStore(name: string) {
const sessions = await this._persistentStore.get()
if (name in sessions) {
return sessions[name]
}
return this.createOniSession(name)
}

public persistSession = async (sessionName: string) => {
const sessionDetails = await this.getSessionFromStore(sessionName)
await this._oni.editors.activeEditor.persistSession(sessionDetails)
return sessionDetails
}

public deleteSession = async (sessionName: string) => {
await this._persistentStore.delete(sessionName)
}

public getCurrentSession = async () => {
const filepath = await this._oni.editors.activeEditor.getCurrentSession()
if (!filepath) {
return null
}
const [name] = path.basename(filepath).split(".")
return filepath.includes(this._sessionsDir) ? this.getSessionFromStore(name) : null
}

public restoreSession = async (name: string) => {
const sessionDetails = await this.getSessionFromStore(name)
await this._oni.editors.activeEditor.restoreSession(sessionDetails)
const session = await this.getCurrentSession()
return session
}

private _getSessionFilename(name: string) {
return path.join(this.sessionsDir, `${name}.vim`)
}

private _setupSubscriptions() {
this._oni.editors.activeEditor.onBufferEnter.subscribe(() => {
this._store.dispatch(SessionActions.updateCurrentSession())
})
this._oni.editors.activeEditor.onQuit.subscribe(() => {
this._store.dispatch(SessionActions.updateCurrentSession())
})
}
}

function init() {
let instance: SessionManager
return {
getInstance: () => instance,
activate: (oni: Plugin.Api, sidebarManager: SidebarManager) => {
const persistentStore = getPersistentStore("sessions", {}, 1)
instance = new SessionManager(oni as UpdatedOni, sidebarManager, persistentStore)
},
}
}
export const { activate, getInstance } = init()
Loading

0 comments on commit e3f65e6

Please sign in to comment.