Skip to content

Commit

Permalink
add utils
Browse files Browse the repository at this point in the history
  • Loading branch information
Yatao Li committed Sep 4, 2019
1 parent b488d1c commit eeffb42
Show file tree
Hide file tree
Showing 6 changed files with 470 additions and 0 deletions.
21 changes: 21 additions & 0 deletions azure-pipelines.yml
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'


12 changes: 12 additions & 0 deletions pack.ps1
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/

173 changes: 173 additions & 0 deletions platform.ts
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
}
}

57 changes: 57 additions & 0 deletions progress.ts
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();
});
}

118 changes: 118 additions & 0 deletions repl.ts
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()
}
Loading

0 comments on commit eeffb42

Please sign in to comment.