diff --git a/README.md b/README.md index 20dc949068..a6bf4e39b1 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ - [JavaScript and TypeScript](#javascript-and-typescript) - [C#](#c) - [Go](#go) + - [Python](#python) - [Configuration](#configuration) - [Extensibility](#extensibility) - [FAQ](#faq) @@ -263,6 +264,24 @@ _Known Issues_ - There is no Windows support at the moment - this is being tracked by [sourcegraph/go-langserver#113](https://github.com/sourcegraph/go-langserver/issues/113). +#### Python + +_Configuration_ + +Python language support depends on [pyls](https://github.com/palantir/python-language-server) by [Palantir](https://www.palantir.com/), which provides language support for Python. Follow their installation instructions as this language server is not bundled out-of-the-box with Oni. + +> `pyls` must be available in your PATH + +_Supported Language features_ + +| Completion | Goto Definition | Formatting | Enhanced Syntax Highlighting | Quick Info | Signature Help | Live Evaluation | Debugging | +| --- | --- | --- | --- | --- | --- |--- | --- | +| Y | Y | N | N | Y | N | N | N | + +_Known Issues_ + +- Windows support is blocked by this issue: [palantir/python-language-server#53](https://github.com/palantir/python-language-server/issues/53). + ### Configuration > ONI is configurable via a 'config.js' located in $HOME/.oni diff --git a/browser/src/Plugins/Api/LanguageClient/LanguageClient.ts b/browser/src/Plugins/Api/LanguageClient/LanguageClient.ts index 4f43542653..6e81024281 100644 --- a/browser/src/Plugins/Api/LanguageClient/LanguageClient.ts +++ b/browser/src/Plugins/Api/LanguageClient/LanguageClient.ts @@ -5,6 +5,8 @@ * https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md */ +import * as os from "os" + import * as _ from "lodash" import * as rpc from "vscode-jsonrpc" import * as types from "vscode-languageserver-types" @@ -68,6 +70,7 @@ export class LanguageClient { private _currentOpenDocumentPath: string private _currentBuffer: string[] = [] private _initializationParams: LanguageClientInitializationParams + private _serverCapabilities: Helpers.ServerCapabilities constructor( private _startOptions: ServerRunOptions, @@ -142,6 +145,7 @@ export class LanguageClient { new LanguageClientLogger()) this._currentOpenDocumentPath = null + this._serverCapabilities = null this._connection.onNotification(Helpers.ProtocolConstants.Window.LogMessage, (args) => { console.log(JSON.stringify(args)) // tslint:disable-line no-console @@ -174,6 +178,12 @@ export class LanguageClient { this._connection.listen() return this._connection.sendRequest(Helpers.ProtocolConstants.Initialize, initializationParams) + .then((response: any) => { + console.log(`[LANGUAGE CLIENT: ${initializationParams.clientName}]: Initialized`) // tslint:disable-line no-console + if (response && response.capabilities) { + this._serverCapabilities = response.capabilities + } + }, (err) => console.error(err)) } public end(): Promise { @@ -247,8 +257,8 @@ export class LanguageClient { if (contents.length === 0) { return null - } else if (contents.length === 1) { - const title = contents[0] + } else if (contents.length === 1 && contents[0]) { + const title = contents[0].trim() if (!title) { return null @@ -259,12 +269,16 @@ export class LanguageClient { description: "", } } else { + + const description = [...contents] + description.shift() + const descriptionContent = description.join(os.EOL) + return { title: contents[0], - description: contents[1], + description: descriptionContent, } } - }) } @@ -299,11 +313,19 @@ export class LanguageClient { const lineNumber = args.lineNumber const previousLine = this._currentBuffer[lineNumber - 1] - this._currentBuffer[lineNumber - 1] = changedLine - this._connection.sendNotification(Helpers.ProtocolConstants.TextDocument.DidChange, - Helpers.incrementalBufferUpdateToDidChangeTextDocumentParams(args, previousLine)) + if (this._serverCapabilities && this._serverCapabilities.textDocumentSync) { + let changeTextDocumentParams + + if (this._serverCapabilities.textDocumentSync === Helpers.TextDocumentSyncKind.Full) { + changeTextDocumentParams = Helpers.createDidChangeTextDocumentParams(args.eventContext.bufferFullPath, this._currentBuffer, args.eventContext.version) + } else { + changeTextDocumentParams = Helpers.incrementalBufferUpdateToDidChangeTextDocumentParams(args, previousLine) + } + + this._connection.sendNotification(Helpers.ProtocolConstants.TextDocument.DidChange, changeTextDocumentParams) + } return Promise.resolve(null) } @@ -321,7 +343,7 @@ export class LanguageClient { }) } else { this._connection.sendNotification(Helpers.ProtocolConstants.TextDocument.DidChange, - Helpers.bufferUpdateToDidChangeTextDocumentParams(args)) + Helpers.createDidChangeTextDocumentParams(bufferFullPath, lines, args.eventContext.version)) } return Promise.resolve(null) diff --git a/browser/src/Plugins/Api/LanguageClient/LanguageClientHelpers.ts b/browser/src/Plugins/Api/LanguageClient/LanguageClientHelpers.ts index 59d096d6a2..6c9104a3ee 100644 --- a/browser/src/Plugins/Api/LanguageClient/LanguageClientHelpers.ts +++ b/browser/src/Plugins/Api/LanguageClient/LanguageClientHelpers.ts @@ -4,6 +4,7 @@ import * as os from "os" +import * as _ from "lodash" import * as types from "vscode-languageserver-types" export const ProtocolConstants = { @@ -25,16 +26,27 @@ export const ProtocolConstants = { }, } +export namespace TextDocumentSyncKind { + export const None = 0 + export const Full = 1 + export const Incremental = 2 +} + +// ServerCapabilities +// Defined in the LSP protocol: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md +export interface ServerCapabilities { + textDocumentSync?: number +} + export const wrapPathInFileUri = (path: string) => getFilePrefix() + path export const unwrapFileUriPath = (uri: string) => decodeURIComponent((uri).split(getFilePrefix())[1]) export const getTextFromContents = (contents: types.MarkedString | types.MarkedString[]): string[] => { if (contents instanceof Array) { - return contents - .map((markedString) => getTextFromMarkedString(markedString)) + return _.flatMap(contents, (markedString) => getTextFromMarkedString(markedString)) } else { - return [getTextFromMarkedString(contents)] + return getTextFromMarkedString(contents) } } @@ -61,9 +73,7 @@ export const eventContextToTextDocumentPositionParams = (args: Oni.EventContext) }, }) -export const bufferUpdateToDidChangeTextDocumentParams = (args: Oni.BufferUpdateContext) => { - const lines = args.bufferLines - const { bufferFullPath, version } = args.eventContext +export const createDidChangeTextDocumentParams = (bufferFullPath: string, lines: string[], version: number) => { const text = lines.join(os.EOL) return { @@ -94,19 +104,26 @@ export const incrementalBufferUpdateToDidChangeTextDocumentParams = (args: Oni.I } } -const getTextFromMarkedString = (markedString: types.MarkedString): string => { +const getTextFromMarkedString = (markedString: types.MarkedString): string[] => { if (typeof markedString === "string") { - return markedString.trim() + return splitByNewlines(markedString) } else { // TODO: Properly apply syntax highlighting based on the `language` parameter - return markedString.value.trim() + return splitByNewlines(markedString.value) } } -const getFilePrefix = () => { - if (process.platform === "win32") { - return "file://" - } else { - return "file:///" - } +const splitByNewlines = (str: string) => { + // Remove '/r' + return str.split("\r") + .join("") + .split("\n") } + +const getFilePrefix = () => { + if (process.platform === "win32") { + return "file:///" + } else { + return "file://" + } + } diff --git a/browser/src/UI/components/QuickInfo.tsx b/browser/src/UI/components/QuickInfo.tsx index d1b46de15f..6aedfd8b1c 100644 --- a/browser/src/UI/components/QuickInfo.tsx +++ b/browser/src/UI/components/QuickInfo.tsx @@ -1,3 +1,5 @@ +import * as os from "os" + import * as React from "react" import { connect } from "react-redux" @@ -72,7 +74,11 @@ export class QuickInfoTitle extends TextComponent { export class QuickInfoDocumentation extends TextComponent { public render(): JSX.Element { - return
{this.props.text}
+ + const lines = this.props.text.split(os.EOL) + const divs = lines.map((l) =>
{l}
) + + return
{divs}
} } diff --git a/vim/core/oni-plugin-python/lib/index.js b/vim/core/oni-plugin-python/lib/index.js new file mode 100644 index 0000000000..7649e32511 --- /dev/null +++ b/vim/core/oni-plugin-python/lib/index.js @@ -0,0 +1,26 @@ +const fs = require("fs") +const path = require("path") +const childProcess = require("child_process") + +const activate = (Oni) => { + + const serverOptions = { + command: "pyls", + } + + const getInitializationOptionsAsync = (filePath) => { + return Promise.resolve({ + clientName: "python", + rootPath: "file:///" + filePath, + capabilities: { + highlightProvider: true + } + }) + } + + const client = Oni.createLanguageClient(serverOptions, getInitializationOptionsAsync) +} + +module.exports = { + activate +} diff --git a/vim/core/oni-plugin-python/package.json b/vim/core/oni-plugin-python/package.json new file mode 100644 index 0000000000..bfba8eb418 --- /dev/null +++ b/vim/core/oni-plugin-python/package.json @@ -0,0 +1,31 @@ +{ + "name": "oni-plugin-python", + "version": "0.0.1", + "main": "lib/index.js", + "engines": { + "oni": "^0.2.2" + }, + "oni": { + "supportedFileTypes": [ + "python" + ], + "subscriptions": [ + "buffer-update", + "vim-events" + ], + "languageService": [ + "quick-info", + "goto-definition", + "completion-provider", + "find-all-references", + "formatting", + "evaluate-block", + "signature-help" + ], + "diagnosticsService": [] + }, + "dependencies": { + }, + "devDependencies": { + } +}