/**
* index.tsx
*
* Entry point for the BrowserWindow process
*/
///
import { ipcRenderer, remote } from "electron"
import * as minimist from "minimist"
import * as path from "path"
import * as Config from "./Config"
import { IncrementalDeltaRegionTracker } from "./DeltaRegionTracker"
import { Keyboard } from "./Input/Keyboard"
import { Mouse } from "./Input/Mouse"
import { NeovimInstance } from "./NeovimInstance"
import { PluginManager } from "./Plugins/PluginManager"
import { DOMRenderer } from "./Renderer/DOMRenderer"
import { NeovimScreen } from "./Screen"
import { AutoCompletion } from "./Services/AutoCompletion"
import { BufferUpdates } from "./Services/BufferUpdates"
import { CommandManager } from "./Services/CommandManager"
import { registerBuiltInCommands } from "./Services/Commands"
import { Errors } from "./Services/Errors"
import { Formatter } from "./Services/Formatter"
import { LiveEvaluation } from "./Services/LiveEvaluation"
import { MultiProcess } from "./Services/MultiProcess"
import { OutputWindow } from "./Services/Output"
import { QuickOpen } from "./Services/QuickOpen"
import { SyntaxHighlighter } from "./Services/SyntaxHighlighter"
import { Tasks } from "./Services/Tasks"
import { WindowTitle } from "./Services/WindowTitle"
import * as UI from "./UI/index"
import { ErrorOverlay } from "./UI/Overlay/ErrorOverlay"
import { LiveEvaluationOverlay } from "./UI/Overlay/LiveEvaluationOverlay"
import { OverlayManager } from "./UI/Overlay/OverlayManager"
import { ScrollBarOverlay } from "./UI/Overlay/ScrollBarOverlay"
import { Rectangle } from "./UI/Types"
const start = (args: string[]) => {
const services: any[] = []
const parsedArgs = minimist(args)
let cursorLine: boolean
let cursorColumn: boolean
let loadInitVim: boolean = false
// Helper for debugging:
window["UI"] = UI // tslint:disable-line no-string-literal
require("./overlay.less")
let deltaRegion = new IncrementalDeltaRegionTracker()
let screen = new NeovimScreen(deltaRegion)
const commandManager = new CommandManager()
const pluginManager = new PluginManager(commandManager)
let instance = new NeovimInstance(pluginManager, document.body.offsetWidth, document.body.offsetHeight)
const editorElement = document.getElementById("oni-text-editor") as HTMLDivElement
let renderer = new DOMRenderer()
renderer.start(editorElement)
let pendingTimeout: any = null
// Services
const autoCompletion = new AutoCompletion(instance)
const bufferUpdates = new BufferUpdates(instance, pluginManager)
const errorService = new Errors(instance)
const quickOpen = new QuickOpen(instance)
const windowTitle = new WindowTitle(instance)
const multiProcess = new MultiProcess()
const formatter = new Formatter(instance, pluginManager, bufferUpdates)
const outputWindow = new OutputWindow(instance, pluginManager)
const liveEvaluation = new LiveEvaluation(instance, pluginManager)
const syntaxHighlighter = new SyntaxHighlighter(instance, pluginManager)
const tasks = new Tasks(outputWindow)
registerBuiltInCommands(commandManager, pluginManager, instance)
tasks.registerTaskProvider(commandManager)
tasks.registerTaskProvider(errorService)
services.push(autoCompletion)
services.push(bufferUpdates)
services.push(errorService)
services.push(quickOpen)
services.push(windowTitle)
services.push(tasks)
services.push(formatter)
services.push(liveEvaluation)
services.push(multiProcess)
services.push(syntaxHighlighter)
services.push(outputWindow)
// Overlays
const overlayManager = new OverlayManager(screen, instance)
const errorOverlay = new ErrorOverlay()
const liveEvaluationOverlay = new LiveEvaluationOverlay()
const scrollbarOverlay = new ScrollBarOverlay()
overlayManager.addOverlay("errors", errorOverlay)
overlayManager.addOverlay("live-eval", liveEvaluationOverlay)
overlayManager.addOverlay("scrollbar", scrollbarOverlay)
overlayManager.on("current-window-size-changed", (dimensionsInPixels: Rectangle) => UI.Actions.setActiveWindowDimensionsChanged(dimensionsInPixels))
pluginManager.on("signature-help-response", (err: string, signatureHelp: any) => { // FIXME: setup Oni import
if (err) {
UI.Actions.hideSignatureHelp()
} else {
UI.Actions.showSignatureHelp(signatureHelp)
}
})
pluginManager.on("set-errors", (key: string, fileName: string, errors: any[], color: string) => {
errorService.setErrors(fileName, errors)
color = color || "red"
errorOverlay.setErrors(key, fileName, errors, color)
const errorMarkers = errors.map((e: any) => ({
line: e.lineNumber,
height: 1,
color,
}))
scrollbarOverlay.setMarkers(path.resolve(fileName), key, errorMarkers)
})
liveEvaluation.on("evaluate-block-result", (file: string, blocks: any[]) => {
liveEvaluationOverlay.setLiveEvaluationResult(file, blocks)
})
pluginManager.on("find-all-references", (references: Oni.Plugin.ReferencesResult) => {
const convertToQuickFixItem = (item: Oni.Plugin.ReferencesResultItem) => ({
filename: item.fullPath,
lnum: item.line,
col: item.column,
text: item.lineText,
})
const quickFixItems = references.items.map((item) => convertToQuickFixItem(item))
instance.quickFix.setqflist(quickFixItems, ` Find All References: ${references.tokenName}`)
instance.command("copen")
instance.command(`execute "normal! /${references.tokenName}\\"`)
})
instance.on("event", (eventName: string, evt: any) => {
// TODO: Can we get rid of these?
errorOverlay.onVimEvent(eventName, evt)
liveEvaluationOverlay.onVimEvent(eventName, evt)
scrollbarOverlay.onVimEvent(eventName, evt)
tasks.onEvent(evt)
if (eventName === "BufEnter") {
// TODO: More convenient way to hide all UI?
UI.Actions.hideCompletions()
UI.Actions.hidePopupMenu()
UI.Actions.hideSignatureHelp()
UI.Actions.hideQuickInfo()
}
if (eventName === "DirChanged") {
instance.getCurrentWorkingDirectory()
.then((newDirectory) => process.chdir(newDirectory))
}
})
instance.on("error", (_err: string) => {
UI.showNeovimInstallHelp()
})
instance.on("buffer-update", (context: any, lines: string[]) => {
scrollbarOverlay.onBufferUpdate(context, lines)
})
instance.on("window-display-update", (eventContext: Oni.EventContext, lineMapping: any) => {
overlayManager.notifyWindowDimensionsChanged(eventContext, lineMapping)
})
instance.on("action", (action: any) => {
renderer.onAction(action)
screen.dispatch(action)
UI.Actions.setColors(screen.foregroundColor)
if (!pendingTimeout) {
pendingTimeout = setTimeout(updateFunction, 0) as any // FIXME: null
}
})
instance.on("mode-change", (newMode: string) => {
UI.Actions.setMode(newMode)
if (newMode === "normal") {
if (cursorLine) { // TODO: Add "unhide" i.e. only show if previously visible
UI.Actions.showCursorLine()
}
if (cursorColumn) {
UI.Actions.showCursorColumn()
}
UI.Actions.hideCompletions()
UI.Actions.hideSignatureHelp()
} else if (newMode === "insert") {
UI.Actions.hideQuickInfo()
if (cursorLine) { // TODO: Add "unhide" i.e. only show if previously visible
UI.Actions.showCursorLine()
}
if (cursorColumn) {
UI.Actions.showCursorColumn()
}
} else if (newMode === "cmdline") {
UI.Actions.hideCursorColumn() // TODO: cleaner way to hide and unhide?
UI.Actions.hideCursorLine()
UI.Actions.hideCompletions()
UI.Actions.hideQuickInfo()
}
})
const renderFunction = () => {
if (pendingTimeout) {
UI.Actions.setCursorPosition(screen)
}
renderer.update(screen, deltaRegion)
deltaRegion.cleanUpRenderedCells()
window.requestAnimationFrame(() => renderFunction())
}
renderFunction()
const updateFunction = () => {
// TODO: Move cursor to component
UI.Actions.setCursorPosition(screen)
UI.setBackgroundColor(screen.backgroundColor)
clearTimeout(pendingTimeout as any) // FIXME: null
pendingTimeout = null
}
const config = Config.instance()
const configChange = () => {
cursorLine = config.getValue("editor.cursorLine")
cursorColumn = config.getValue("editor.cursorColumn")
UI.Actions.setCursorLineOpacity(config.getValue("editor.cursorLineOpacity"))
UI.Actions.setCursorColumnOpacity(config.getValue("editor.cursorColumnOpacity"))
if (cursorLine) {
UI.Actions.showCursorLine()
}
if (cursorColumn) {
UI.Actions.showCursorColumn()
}
const window = remote.getCurrentWindow()
const hideMenu: boolean = config.getValue("oni.hideMenu")
window.setAutoHideMenuBar(hideMenu)
window.setMenuBarVisibility(!hideMenu)
const loadInit: boolean = config.getValue("oni.loadInitVim")
if (loadInit !== loadInitVim) {
ipcRenderer.send("rebuild-menu", loadInit)
// don't rebuild menu unless oni.loadInitVim actually changed
loadInitVim = loadInit
}
window.setFullScreen(config.getValue("editor.fullScreenOnStart"))
instance.setFont(config.getValue("editor.fontFamily"), config.getValue("editor.fontSize"))
updateFunction()
}
configChange() // initialize values
config.registerListener(configChange)
instance.start(parsedArgs._)
const mouse = new Mouse(editorElement, screen)
mouse.on("mouse", (mouseInput: string) => {
UI.Actions.hideCompletions()
instance.input(mouseInput)
})
const keyboard = new Keyboard()
keyboard.on("keydown", (key: string) => {
if (key === "") {
formatter.formatBuffer()
return
}
if (UI.Selectors.isPopupMenuOpen()) {
if (key === "") {
UI.Actions.hidePopupMenu()
} else if (key === "") {
UI.Actions.selectMenuItem(false)
} else if (key === "") {
UI.Actions.selectMenuItem(true)
} else if (key === "") {
UI.Actions.nextMenuItem()
} else if (key === "") {
UI.Actions.previousMenuItem()
}
return
}
if (UI.Selectors.areCompletionsVisible()) {
if (key === "") {
autoCompletion.complete()
return
} else if (key === "") {
UI.Actions.nextCompletion()
return
} else if (key === "") {
UI.Actions.previousCompletion()
return
}
}
if (key === "") {
commandManager.executeCommand("oni.editor.gotoDefinition", null)
} else if (key === "" && screen.mode === "normal") {
quickOpen.show()
} else if (key === "" && screen.mode === "normal") {
tasks.show()
} else if (key === "") {
multiProcess.focusPreviousInstance()
} else if (key === "") {
multiProcess.focusNextInstance()
} else {
instance.input(key)
}
})
UI.events.on("completion-item-selected", (item: any) => {
pluginManager.notifyCompletionItemSelected(item)
})
const resize = () => {
let width = document.body.offsetWidth
let height = document.body.offsetHeight
deltaRegion.dirtyAllCells()
instance.resize(width, height)
renderer.onResize()
}
window.addEventListener("resize", resize)
window["__neovim"] = instance // tslint:disable-line no-string-literal
window["__screen"] = screen // tslint:disable-line no-string-literal
UI.init()
ipcRenderer.on("menu-item-click", (_evt, message: string) => {
if (message.startsWith(":")) {
instance.command("exec \"" + message + "\"")
} else {
instance.command("exec \":normal! " + message + "\"")
}
})
ipcRenderer.on("execute-command", (_evt, command: string) => {
commandManager.executeCommand(command, null)
})
}
ipcRenderer.on("init", (_evt, message) => {
process.chdir(message.workingDirectory)
start(message.args)
})