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

Commit

Permalink
WIP: #485 - Add language server info in status bar (#524)
Browse files Browse the repository at this point in the history
* Add 'Message' component

* Add Message component, along with state/action/reducer

* Add actions for show / hide message dialog, and fix formatting

* Add more items to PLAN

* More stuff in PLAN

* Add initial language client status bar

* Start creating state for statusbar

* Add buffer-leave event

* Fix the STATUSBAR_HIDE action

* Add rotation & filetype

* Update PLAN

* Update PLAN

* Remove wrap from SignatureHelp

* Factor out file type to common element

* Set up statusbar to allow for a globally referenced id, across plugins

* Add setting for status bar font size

* Add configurable font size for the status bar

* Set rotate animation only when initializing is in progress

* Don't show language status if we don't have a filetype

* Add error state

* When there is an error, setup click event to open dev tools

* Update PLAN

* Update PLAN

* Update Config

* Update PLAN

* Fix lint issues

* Update PLAN.md

* Remove PLAN.md

* Merge imports

* Reorder import groups

* Fix import ordering

* Add more detailed logging, and additional error state

* Fix logging message

* Fix lint issues
  • Loading branch information
extr0py authored Jul 22, 2017
1 parent be714b3 commit 4735dbc
Show file tree
Hide file tree
Showing 16 changed files with 395 additions and 42 deletions.
5 changes: 5 additions & 0 deletions browser/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface IConfigValues {
"editor.cursorColumnOpacity": number

"statusbar.enabled": boolean
"statusbar.fontSize": string
}

export class Config extends EventEmitter {
Expand Down Expand Up @@ -144,19 +145,23 @@ export class Config extends EventEmitter {
"editor.cursorColumnOpacity": 0.1,

"statusbar.enabled": true,
"statusbar.fontSize": "12px",
}

private MacConfig: Partial<IConfigValues> = {
"editor.fontFamily": "Menlo",
"editor.fontSize": "12px",
"statusbar.fontSize": "10px",
}

private WindowsConfig: Partial<IConfigValues> = {
"editor.fontFamily": "Consolas",
"statusbar.fontSize": "11px",
}

private LinuxConfig: Partial<IConfigValues> = {
"editor.fontFamily": "DejaVu Sans Mono",
"statusbar.fontSize": "11px",
}

private DefaultPlatformConfig = Platform.isWindows() ? this.WindowsConfig : Platform.isLinux() ? this.LinuxConfig : this.MacConfig
Expand Down
28 changes: 27 additions & 1 deletion browser/src/Plugins/Api/LanguageClient/LanguageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export interface InitializationParamsCreator {
(filePath: string): Promise<LanguageClientInitializationParams>
}

import { LanguageClientState, LanguageClientStatusBar } from "./LanguageClientStatusBar"

/**
* Implementation of a client that talks to a server
* implementing the Language Server Protocol:
Expand All @@ -77,14 +79,20 @@ export class LanguageClient {
private _initializationParams: LanguageClientInitializationParams
private _serverCapabilities: Helpers.ServerCapabilities

private _statusBar: LanguageClientStatusBar

constructor(
private _startOptions: ServerRunOptions,
private _initializationParamsCreator: InitializationParamsCreator,
private _oni: Oni) {

this._currentPromise = Promise.resolve(null)

this._statusBar = new LanguageClientStatusBar(this._oni)

this._oni.on("buffer-enter", (args: Oni.EventContext) => {
this._statusBar.show(args.filetype)
this._statusBar.setStatus(LanguageClientState.Initializing)
this._enqueuePromise(() => {
return this._initializationParamsCreator(args.bufferFullPath)
.then((newParams: LanguageClientInitializationParams) => {
Expand All @@ -111,6 +119,10 @@ export class LanguageClient {
.then(() => this._enqueuePromise(() => this._updateHighlights(args.eventContext.bufferFullPath)))
})

this._oni.on("buffer-leave", (args: Oni.EventContext) => {
this._statusBar.hide()
})

this._oni.on("buffer-update-incremental", (args: Oni.IncrementalBufferUpdateContext) => {
return this._enqueuePromise(() => this._onBufferUpdateIncremental(args))
.then(() => this._enqueuePromise(() => this._updateHighlights(args.eventContext.bufferFullPath)))
Expand Down Expand Up @@ -144,13 +156,21 @@ export class LanguageClient {
const startArgs = this._startOptions.args || []

if (this._startOptions.command) {
console.log(`[LANGUAGE CLIENT]: Starting process via '${this._startOptions.command}`) // tslint:disable-line no-console
this._process = spawn(this._startOptions.command, startArgs)
} else if (this._startOptions.module) {
console.log(`[LANGUAGE CLIENT]: Starting process via node script '${this._startOptions.module}`) // tslint:disable-line no-console
this._process = this._oni.spawnNodeScript(this._startOptions.module, startArgs)
} else {
throw "A command or module must be specified to start the server"
}

if (!this._process || !this._process.pid) {
console.error("[LANGUAGE CLIENT]: Unable to start language server process.") // tslint:disable-line no-console
this._statusBar.setStatus(LanguageClientState.Error)
return Promise.reject(null)
}

console.log(`[LANGUAGE CLIENT]: Started process ${this._process.pid}`) // tslint:disable-line no-console

this._process.on("close", (code: number, signal: string) => {
Expand All @@ -159,6 +179,7 @@ export class LanguageClient {

this._process.stderr.on("data", (msg) => {
console.error(`[LANGUAGE CLIENT - ERROR]: ${msg}`) // tslint:disable-line no-console
this._statusBar.setStatus(LanguageClientState.Error)
})

this._connection = rpc.createMessageConnection(
Expand Down Expand Up @@ -203,11 +224,15 @@ export class LanguageClient {

return this._connection.sendRequest(Helpers.ProtocolConstants.Initialize, oniLanguageClientParams)
.then((response: any) => {
this._statusBar.setStatus(LanguageClientState.Initialized)
console.log(`[LANGUAGE CLIENT: ${initializationParams.clientName}]: Initialized`) // tslint:disable-line no-console
if (response && response.capabilities) {
this._serverCapabilities = response.capabilities
}
}, (err) => console.error(err))
}, (err) => {
this._statusBar.setStatus(LanguageClientState.Error)
console.error(err)
})
}

public end(): Promise<void> {
Expand All @@ -233,6 +258,7 @@ export class LanguageClient {
.then(() => promiseExecutor(),
(err) => {
console.error(err)
this._statusBar.setStatus(LanguageClientState.Error)
return promiseExecutor()
})

Expand Down
105 changes: 105 additions & 0 deletions browser/src/Plugins/Api/LanguageClient/LanguageClientStatusBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* LanguageClientStatusBar.tsx
*
* Implements status bar for Oni
*/

import * as electron from "electron"
import * as React from "react"

import { Icon } from "./../../../UI/Icon"

import { Oni } from "./../Oni"

export class LanguageClientStatusBar {

private _item: Oni.StatusBarItem
private _fileType: string

constructor(private _oni: Oni) {
this._item = this._oni.statusBar.getItem("oni.status.fileType")
}

public show(fileType: string): void {
this._fileType = fileType
this._item.setContents(<StatusBarRenderer state={LanguageClientState.NotAvailable} language={this._fileType} />)
this._item.show()
}

public setStatus(status: LanguageClientState): void {
this._item.setContents(<StatusBarRenderer state={status} language={this._fileType} />)
}

public hide(): void {
this._item.hide()
}
}

export enum LanguageClientState {
NotAvailable = 0,
Initializing,
Initialized,
Active,
Error,
}

const SpinnerIcon = "circle-o-notch"
const ConnectedIcon = "bolt"
const ErrorIcon = "exclamation-circle"

interface StatusBarRendererProps {
state: LanguageClientState
language: string
}

const getIconFromStatus = (status: LanguageClientState) => {
switch (status) {
case LanguageClientState.Initializing:
return SpinnerIcon
case LanguageClientState.Error:
return ErrorIcon
default:
return ConnectedIcon
}
}

const getClassNameFromstatus = (status: LanguageClientState) => {
switch (status) {
case LanguageClientState.Initializing:
return "rotate-animation"
default:
return ""
}
}

const StatusBarRenderer = (props: StatusBarRendererProps) => {
const containerStyle: React.CSSProperties = {
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
backgroundColor: "rgb(35, 35, 35)",
color: "rgb(200, 200, 200)",
paddingRight: "8px",
paddingLeft: "8px",
}

const iconStyle: React.CSSProperties = {
paddingRight: "6px",
minWidth: "14px",
textAlign: "center",
}

const openDevTools = () => {
electron.remote.getCurrentWindow().webContents.openDevTools()
}

const onClick = props.state === LanguageClientState.Error ? openDevTools : null

return <div style={containerStyle} onClick={onClick}>
<span style={iconStyle}>
<Icon name={getIconFromStatus(props.state)} className={getClassNameFromstatus(props.state)} />
</span>
<span>{props.language}</span>
</div>
}
10 changes: 4 additions & 6 deletions browser/src/Plugins/Api/Oni.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,19 +159,17 @@ export class Oni extends EventEmitter implements Oni.Plugin.Api {

if (arg.payload.name === "CursorMoved") {
this.emit("cursor-moved", arg.payload.context)
this.emit("CursorMoved", arg.payload.context)
} else if (arg.payload.name === "CursorMovedI") {
this.emit("cursor-moved", arg.payload.context)
this.emit("CursorMovedI", arg.payload.context)
} else if (arg.payload.name === "BufWritePost") {
this.emit("buffer-saved", arg.payload.context)
this.emit("BufWritePost", arg.payload.context)
} else if (arg.payload.name === "BufEnter") {
this.emit("buffer-enter", arg.payload.context)
this.emit("BufEnter", arg.payload.context)
} else {
this.emit(arg.payload.name, arg.payload.context)
} else if (arg.payload.name === "BufLeave") {
this.emit("buffer-leave", arg.payload.context)
}

this.emit(arg.payload.name, arg.payload.context)
} else if (arg.type === "command") {
this._commands.onCommand(arg.payload.command, arg.payload.args)
} else if (arg.type === "request") {
Expand Down
17 changes: 8 additions & 9 deletions browser/src/Plugins/Api/StatusBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export class StatusBarItem implements Oni.StatusBarItem {
constructor(
private _channel: IPluginChannel,
private _id: string,
private _alignment: StatusBarAlignment,
private _priority: number,
private _alignment?: StatusBarAlignment | null,
private _priority?: number | null,
) { }

public show(): void {
Expand All @@ -42,11 +42,6 @@ export class StatusBarItem implements Oni.StatusBarItem {

public setContents(element: any): void {
this._contents = element
// if (typeof element === "string") {
// this._contents = element
// } else {
// this._contents = element.outerHTML
// }

if (this._visible) {
this.show()
Expand All @@ -65,10 +60,14 @@ export class StatusBar implements Oni.StatusBar {
private _channel: IPluginChannel,
) { }

public createItem(alignment: StatusBarAlignment, priority: number = 0): Oni.StatusBarItem {
public getItem(globalId: string): Oni.StatusBarItem {
return new StatusBarItem(this._channel, globalId)
}

public createItem(alignment: StatusBarAlignment, priority: number = 0, globalId?: string): Oni.StatusBarItem {
this._id++

const statusBarId = `${this._channel.metadata.name}${this._id.toString()}`
const statusBarId = globalId || `${this._channel.metadata.name}${this._id.toString()}`

return new StatusBarItem(this._channel, statusBarId, alignment, priority)
}
Expand Down
55 changes: 39 additions & 16 deletions browser/src/UI/ActionCreators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@
* http://redux.js.org/docs/basics/Actions.html
*/

import * as types from "vscode-languageserver-types"

import * as Events from "./Events"
import { Rectangle } from "./Types"

import { IScreen } from "./../Screen"
import { normalizePath } from "./../Utility"

import * as State from "./State"

import * as Config from "./../Config"
import * as Actions from "./Actions"
import { events } from "./Events"
import { ILog } from "./Logs"
import * as State from "./State"

import * as types from "vscode-languageserver-types"
import * as Config from "./../Config"
import { IScreen } from "./../Screen"
import { normalizePath } from "./../Utility"

export const setBufferState = (file: string, totalLines: number) => ({
type: "SET_BUFFER_STATE",
Expand Down Expand Up @@ -77,16 +76,40 @@ export const clearErrors = (file: string, key: string) => ({
},
})

export const showStatusBarItem = (id: string, contents: JSX.Element, alignment?: State.StatusBarAlignment, priority?: number) => ({
type: "STATUSBAR_SHOW",
export const showMessageDialog = (messageType: State.MessageType, text: string, buttons: State.IMessageDialogButton[], details?: string): Actions.IShowMessageDialog => ({
type: "SHOW_MESSAGE_DIALOG",
payload: {
id,
contents,
alignment,
priority,
messageType,
text,
buttons,
details,
},
})

export const hideMessageDialog = (): Actions.IHideMessageDialog => ({
type: "HIDE_MESSAGE_DIALOG",
})

export const showStatusBarItem = (id: string, contents: JSX.Element, alignment?: State.StatusBarAlignment, priority?: number) => (dispatch: Function, getState: Function) => {

const currentStatusBarItem = getState().statusBar[id]

if (currentStatusBarItem) {
alignment = alignment || currentStatusBarItem.alignment
priority = priority || currentStatusBarItem.priority
}

dispatch({
type: "STATUSBAR_SHOW",
payload: {
id,
contents,
alignment,
priority,
},
})
}

export const hideStatusBarItem = (id: string) => ({
type: "STATUSBAR_HIDE",
payload: {
Expand Down Expand Up @@ -258,15 +281,15 @@ export function setConfigValue<K extends keyof Config.IConfigValues>(k: K, v: Co
}
export const toggleLogFold = (index: number): Actions.IToggleLogFold => ({
type: "TOGGLE_LOG_FOLD",
payload: {index},
payload: { index },
})
export const changeLogsVisibility = (visibility: boolean): Actions.IChangeLogsVisibility => ({
type: "CHANGE_LOGS_VISIBILITY",
payload: {visibility},
payload: { visibility },
})
export const makeLog = (log: ILog): Actions.IMakeLog => ({
type: "MAKE_LOG",
payload: {log},
payload: { log },
})

const _setCursorPosition = (cursorPixelX: any, cursorPixelY: any, fontPixelWidth: any, fontPixelHeight: any, cursorCharacter: string, cursorPixelWidth: number) => ({
Expand Down
Loading

0 comments on commit 4735dbc

Please sign in to comment.