-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Yatao Li
committed
Sep 4, 2019
1 parent
b488d1c
commit eeffb42
Showing
6 changed files
with
470 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Starter pipeline | ||
# Start with a minimal pipeline that you can customize to build and deploy your code. | ||
# Add steps that build, run tests, deploy, and more: | ||
# https://aka.ms/yaml | ||
|
||
trigger: | ||
- master | ||
|
||
pool: | ||
vmImage: 'ubuntu-latest' | ||
steps: | ||
- task: PowerShell@2 | ||
inputs: | ||
filePath: 'pack.ps1' | ||
- task: PublishBuildArtifacts@1 | ||
inputs: | ||
PathtoPublish: 'publish' | ||
ArtifactName: 'drop' | ||
publishLocation: 'Container' | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
New-Item -ItemType Directory -Force -Name out | ||
New-Item -ItemType Directory -Force -Name publish | ||
Remove-Item out/* -Recurse -Force | ||
Remove-Item publish/* -Recurse -Force | ||
|
||
# client | ||
|
||
npm install | ||
npm run compile | ||
npm pack --silent | ||
Move-Item *.tgz publish/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/*--------------------------------------------------------- | ||
* Copyright (C) Microsoft Corporation. All rights reserved. | ||
*--------------------------------------------------------*/ | ||
|
||
import { workspace, extensions, ExtensionContext } from 'coc.nvim' | ||
import {sleep} from "./utils" | ||
import fs = require("fs"); | ||
import path = require("path"); | ||
import process = require("process"); | ||
import { IncomingMessage, RequestOptions, Agent } from 'http' | ||
import { parse } from 'url' | ||
const tunnel = require('tunnel') | ||
const followRedirects = require("follow-redirects") | ||
const unzip = require("extract-zip"); | ||
const rimraf = require("rimraf") | ||
|
||
export enum OperatingSystem { | ||
Unknown, | ||
Windows, | ||
MacOS, | ||
Linux, | ||
} | ||
|
||
export interface IPlatformDetails { | ||
operatingSystem: OperatingSystem; | ||
isOS64Bit: boolean; | ||
isProcess64Bit: boolean; | ||
} | ||
|
||
export function getPlatformDetails(): IPlatformDetails { | ||
let operatingSystem = OperatingSystem.Unknown; | ||
|
||
if (process.platform === "win32") { | ||
operatingSystem = OperatingSystem.Windows; | ||
} else if (process.platform === "darwin") { | ||
operatingSystem = OperatingSystem.MacOS; | ||
} else if (process.platform === "linux") { | ||
operatingSystem = OperatingSystem.Linux; | ||
} | ||
|
||
const isProcess64Bit = process.arch === "x64"; | ||
|
||
return { | ||
operatingSystem, | ||
isOS64Bit: isProcess64Bit || process.env.hasOwnProperty("PROCESSOR_ARCHITEW6432"), | ||
isProcess64Bit, | ||
}; | ||
} | ||
|
||
export function getPlatformSignature(): string | ||
{ | ||
const plat = getPlatformDetails() | ||
|
||
const os_sig = (()=>{ | ||
switch(plat.operatingSystem){ | ||
case OperatingSystem.Windows: return "win" | ||
case OperatingSystem.Linux: return "linux" | ||
case OperatingSystem.MacOS: return "osx" | ||
default: return "unknown" | ||
} | ||
})() | ||
|
||
const arch_sig = (()=>{ | ||
if(plat.isProcess64Bit) return "x64" | ||
else return "x86" | ||
})() | ||
|
||
return `${os_sig}-${arch_sig}` | ||
} | ||
|
||
export interface ILanguageServerPackage | ||
{ | ||
executable: string | ||
downloadUrl: string | ||
} | ||
|
||
export interface ILanguageServerRepository | ||
{ | ||
[platform:string]: ILanguageServerPackage | ||
} | ||
|
||
export type LanguageServerDownloadChannel = | ||
| { type: "nightly" } | ||
| { type: "latest" } | ||
| { type: "specific-tag" } | ||
|
||
export class LanguageServerProvider | ||
{ | ||
private extensionStoragePath: string | ||
private languageServerDirectory: string | ||
private languageServerZip: string | ||
private languageServerExe: string | ||
private languageServerPackage: ILanguageServerPackage | ||
|
||
constructor(private extension: ExtensionContext, private repo: ILanguageServerRepository, private channel: LanguageServerDownloadChannel) | ||
{ | ||
const platsig = getPlatformSignature() | ||
this.extensionStoragePath = extension.storagePath | ||
this.languageServerPackage = repo[platsig] | ||
|
||
if(!this.languageServerPackage) { throw "Platform not supported" } | ||
|
||
this.languageServerDirectory = path.join(this.extensionStoragePath, "server") | ||
this.languageServerZip = this.languageServerDirectory + ".zip" | ||
this.languageServerExe = path.join(this.languageServerDirectory, this.languageServerPackage.executable) | ||
} | ||
|
||
public async downloadLanguageServer(): Promise<void> { | ||
|
||
let item = workspace.createStatusBarItem(0, {progress: true}) | ||
item.text = "Downloading F# Language Server" | ||
item.show() | ||
|
||
if(!fs.existsSync(this.extensionStoragePath)) { | ||
fs.mkdirSync(this.extensionStoragePath) | ||
} | ||
|
||
if(fs.existsSync(this.languageServerDirectory)){ | ||
rimraf.sync(this.languageServerDirectory) | ||
} | ||
|
||
let url = this.languageServerPackage.downloadUrl | ||
|
||
if(this.channel.type === "nightly") | ||
{ | ||
url = url.replace("RELEASE", "nightly") | ||
} | ||
|
||
fs.mkdirSync(this.languageServerDirectory) | ||
|
||
await new Promise<void>((resolve, reject) => { | ||
const req = followRedirects.https.request(url, (res: IncomingMessage) => { | ||
if (res.statusCode != 200) { | ||
reject(new Error(`Invalid response from ${url}: ${res.statusCode}`)) | ||
return | ||
} | ||
let file = fs.createWriteStream(this.languageServerZip) | ||
let stream = res.pipe(file) | ||
stream.on('finish', resolve) | ||
}) | ||
req.on('error', reject) | ||
req.end() | ||
}) | ||
|
||
await new Promise<void>((resolve, reject) => { | ||
unzip(this.languageServerZip, {dir: this.languageServerDirectory}, (err: any) => { | ||
if(err) reject(err) | ||
else resolve() | ||
}) | ||
}) | ||
|
||
fs.unlinkSync(this.languageServerZip) | ||
item.dispose() | ||
} | ||
|
||
// returns the full path to the language server executable | ||
public async getLanguageServer(): Promise<string> { | ||
|
||
const plat = getPlatformDetails() | ||
|
||
if (!fs.existsSync(this.languageServerExe)) { | ||
await this.downloadLanguageServer() | ||
} | ||
|
||
// Make sure the server is executable | ||
if (plat.operatingSystem !== OperatingSystem.Windows) { | ||
fs.chmodSync(this.languageServerExe, "755") | ||
} | ||
|
||
return this.languageServerExe | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { LanguageClient, StatusBarItem, workspace } from 'coc.nvim'; | ||
|
||
export interface StartProgress { | ||
title: string | ||
nFiles: number | ||
} | ||
|
||
export function createProgressListeners(client: LanguageClient) { | ||
// Create a "checking files" progress indicator | ||
let progressListener = new class { | ||
countChecked = 0 | ||
nFiles = 0 | ||
title: string = "" | ||
statusBarItem: StatusBarItem = null; | ||
|
||
startProgress(start: StartProgress) { | ||
// TODO implement user cancellation (???) | ||
this.title = start.title | ||
this.nFiles = start.nFiles | ||
this.statusBarItem = workspace.createStatusBarItem(0, { progress : true }); | ||
this.statusBarItem.text = this.title; | ||
} | ||
|
||
private percentComplete() { | ||
return Math.floor(this.countChecked / (this.nFiles + 1) * 100); | ||
} | ||
|
||
incrementProgress(fileName: string) { | ||
if (this.statusBarItem != null) { | ||
this.countChecked++; | ||
let newPercent = this.percentComplete(); | ||
this.statusBarItem.text = `${this.title} (${newPercent}%)... [${fileName}]` | ||
this.statusBarItem.show(); | ||
} | ||
} | ||
|
||
endProgress() { | ||
this.countChecked = 0 | ||
this.nFiles = 0 | ||
this.statusBarItem.hide() | ||
this.statusBarItem.dispose() | ||
this.statusBarItem = null | ||
} | ||
} | ||
|
||
// Use custom notifications to drive progressListener | ||
client.onNotification('fsharp/startProgress', (start: StartProgress) => { | ||
progressListener.startProgress(start); | ||
}); | ||
client.onNotification('fsharp/incrementProgress', (fileName: string) => { | ||
progressListener.incrementProgress(fileName); | ||
}); | ||
client.onNotification('fsharp/endProgress', () => { | ||
progressListener.endProgress(); | ||
}); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* -------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
* ------------------------------------------------------------------------------------------ */ | ||
|
||
import coc = require("coc.nvim"); | ||
import {workspace} from 'coc.nvim' | ||
import {sleep, getCurrentSelection} from './utils'; | ||
|
||
export class REPLProcess { | ||
|
||
public onExited: coc.Event<void>; | ||
private onExitedEmitter = new coc.Emitter<void>(); | ||
private consoleTerminal: coc.Terminal = undefined; | ||
private consoleCloseSubscription: coc.Disposable; | ||
private log: coc.OutputChannel; | ||
|
||
constructor(private title: string, private progPath: string, private progArgs: string[]) { | ||
this.log = coc.workspace.createOutputChannel(title) | ||
this.onExited = this.onExitedEmitter.event; | ||
} | ||
|
||
public async start() { | ||
|
||
if (this.consoleTerminal) { | ||
this.log.appendLine(`${this.title} already started.`) | ||
this.consoleTerminal.show(true) | ||
return | ||
} | ||
|
||
this.log.appendLine(`${this.title} starting.`) | ||
|
||
this.consoleTerminal = await coc.workspace.createTerminal({ | ||
name: this.title, | ||
shellPath: this.progPath, | ||
shellArgs: this.progArgs | ||
}) | ||
|
||
this.consoleCloseSubscription = | ||
coc.workspace.onDidCloseTerminal( | ||
(terminal) => { | ||
if (terminal === this.consoleTerminal) { | ||
this.log.appendLine(`${this.title} terminated or terminal UI was closed`); | ||
this.onExitedEmitter.fire(); | ||
} | ||
}, this); | ||
} | ||
|
||
public showConsole(preserveFocus: boolean) { | ||
if (this.consoleTerminal) { | ||
this.consoleTerminal.show(preserveFocus); | ||
} | ||
} | ||
|
||
public async eval(line: string) { | ||
if (this.consoleTerminal) { | ||
this.consoleTerminal.sendText(line) | ||
} | ||
} | ||
|
||
public async scrollToBottom() { | ||
this.consoleTerminal.show(false) | ||
await sleep(200) | ||
await coc.workspace.nvim.command("wincmd w") | ||
} | ||
|
||
public dispose() { | ||
|
||
if (this.consoleCloseSubscription) { | ||
this.consoleCloseSubscription.dispose(); | ||
this.consoleCloseSubscription = undefined; | ||
} | ||
|
||
if (this.consoleTerminal) { | ||
this.log.appendLine(`Terminating ${this.title} process...`); | ||
this.consoleTerminal.dispose(); | ||
this.consoleTerminal = undefined; | ||
} | ||
} | ||
} | ||
|
||
let currentREPL: REPLProcess = undefined | ||
async function createREPL () { | ||
if(currentREPL) { | ||
currentREPL.dispose() | ||
currentREPL = undefined | ||
} | ||
currentREPL = new REPLProcess("F# REPL", "dotnet", ["fsi", "--readline+"]) | ||
currentREPL.onExited(() => { | ||
currentREPL = undefined | ||
}) | ||
await currentREPL.start() | ||
return currentREPL.onExited | ||
} | ||
|
||
|
||
export async function doEval(mode: string) { | ||
|
||
let document = await workspace.document | ||
if (!document || document.filetype !== 'fsharp') { | ||
return | ||
} | ||
|
||
if(!currentREPL) { | ||
await createREPL() | ||
} | ||
|
||
// TODO: move to workspace.getCurrentSelection when we get an answer: | ||
// https://github.com/neoclide/coc.nvim/issues/933 | ||
const content = await getCurrentSelection(mode) | ||
for(let line of content){ | ||
await currentREPL.eval(line) | ||
} | ||
await currentREPL.eval(";;") | ||
// see :help feedkeys | ||
await workspace.nvim.call('eval', `feedkeys("\\<esc>${content.length}j", "in")`) | ||
// await currentREPL.scrollToBottom() | ||
} |
Oops, something went wrong.