Merge branch 'master' into staging

This commit is contained in:
Bryan Phelps 2018-12-15 13:31:49 -08:00
commit 03d7c26207
61 changed files with 2151 additions and 386 deletions

2
.github/config.yml vendored
View File

@ -38,3 +38,5 @@ backers:
- 2318955
- 28788713
- 1491574
- 6972449
- 20133970

1
.yarnrc Normal file
View File

@ -0,0 +1 @@
--add.ignore-engines true

View File

@ -144,6 +144,9 @@ Thanks you to all our backers for making Oni possible!
* Jonas Strømsodd
* Trevor Barton
* Tercio de Melo
* Jon Plotner
* Patrick Ball
* Grégory Reinbold
<a href="https://opencollective.com/oni/tiers/backer/0/website" target="_blank"><img src="https://opencollective.com/oni/tiers/backer/0/avatar.png"></a>
<a href="https://opencollective.com/oni/tiers/backer/1/website" target="_blank"><img src="https://opencollective.com/oni/tiers/backer/1/avatar.png"></a>

View File

@ -1,12 +1,34 @@
![alt text](./assets/oni-header.png)
<p align="center">
<img src="./assets/oni-header.png" alt="Logo">
<h3 align="center">Modern Modal Editing</h3>
</p>
### Modern Modal Editing
<p align="center">
<a href="https://travis-ci.org/onivim/oni">
<img src="https://travis-ci.org/onivim/oni.svg?branch=master" alt="Build Status">
</a>
<a href="https://ci.appveyor.com/project/oni/oni">
<img src="https://ci.appveyor.com/api/projects/status/s13bs7ail9ihkpnm?svg=true" alt="Build Status">
</a>
<a href="https://codecov.io/gh/onivim/oni">
<img src="https://codecov.io/gh/onivim/oni/branch/master/graph/badge.svg" alt="codecov">
</a>
</p>
[![Build Status](https://travis-ci.org/onivim/oni.svg?branch=master)](https://travis-ci.org/onivim/oni) [![Build status](https://ci.appveyor.com/api/projects/status/s13bs7ail9ihkpnm?svg=true)](https://ci.appveyor.com/project/oni/oni) [![codecov](https://codecov.io/gh/onivim/oni/branch/master/graph/badge.svg)](https://codecov.io/gh/onivim/oni)
[![Join the chat on discord!](https://img.shields.io/discord/417774914645262338.svg)](https://discord.gg/7maEAxV)
[![Backers on Open Collective](https://opencollective.com/oni/backers/badge.svg)](https://opencollective.com/oni#backer) [![BountySource Active Bounties](https://api.bountysource.com/badge/tracker?tracker_id=48462304)](https://www.bountysource.com/teams/oni)
[![Total Downloads](https://img.shields.io/github/downloads/onivim/oni/total.svg)](https://github.com/onivim/oni/releases)
<p align="center">
<a href="https://discord.gg/7maEAxV">
<img src="https://img.shields.io/discord/417774914645262338.svg" alt="Join the chat on discord!">
</a>
<a href="https://opencollective.com/oni#backer">
<img src="https://opencollective.com/oni/backers/badge.svg" alt="Backers on Open Collective">
</a>
<a href="https://www.bountysource.com/teams/oni">
<img src="https://api.bountysource.com/badge/tracker?tracker_id=48462304" alt="BountySource Active Bounties">
</a>
<a href="https://github.com/onivim/oni/releases">
<img src="https://img.shields.io/github/downloads/onivim/oni/total.svg" alt="Total Downloads">
</a>
</p>
<h2 align="center">Supporting Oni</h2>
@ -113,6 +135,16 @@ The goal of this project is to give an editor that gives the best of both worlds
* [FAQ](https://github.com/onivim/oni/wiki/FAQ)
* [Roadmap](https://github.com/onivim/oni/wiki/Roadmap)
## Available Plugins
Some available plugins created by Oni users are listed below (if you'd like to add your
plugin to this list please create a PR updating this **README** with the details).
* [Oni Touchbar Plugin](https://github.com/jordan-arenstein/oni-plugin-touchbar) - by [jordan-arenstein](https://github.com/jordan-arenstein?tab=overview&from=2018-07-01&to=2018-07-31)
* [quickFind](https://github.com/marene/quickFind) - by [marene](https://github.com/marene)
* Themes
* [Night Owl](https://github.com/Akin909/oni-theme-night-owl)
## Contributing
There many ways to get involved & contribute to Oni:

View File

@ -1,22 +1,22 @@
# Test against the latest version of this Node.js version
environment:
nodejs_version: "8"
nodejs_version: "8"
os: unstable
branches:
only:
- master
- /^release.*/
- master
- /^release.*/
# Skip CI build if the changes match these rules exactly.
# Ie, if the BACKERS.md file is changed, we don't need to build.
skip_commits:
files:
- '**/*.md'
files:
- "**/*.md"
cache:
- .oni_build_cache -> package.json
- .oni_build_cache -> package.json
platform:
- x86
@ -24,42 +24,21 @@ platform:
# Install scripts. (runs after repo cloning)
install:
# Ensure the Git Submoduldes have been pulled down too
- git submodule update --init --recursive
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# Workaround https://github.com/npm/npm/issues/18380
- npm install -g yarn
- node --version
- npm --version
# install modules
- yarn install
- yarn run check-cached-binaries
artifacts:
- path: dist/*.exe
name: SetupExe
- path: dist/*.zip
name: ProductZip
- path: dist/media/screenshot-win32.png
name: DemoScreenshot
deploy:
- provider: GitHub
repository: onivim/oni
auth_token:
secure: IUThGC/uAhOIpZ+B/yS/tMBPBETwL1qttaaTbZYmbSP468Lmd0buoleDLSxrrLpH
draft: false
prerelease: false
force_update: true
on:
appveyor_repo_tag: true
# Ensure the Git Submoduldes have been pulled down too
- git submodule update --init --recursive
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# Workaround https://github.com/npm/npm/issues/18380
- npm install -g yarn
- node --version
- npm --version
# install modules
- yarn install
- yarn run check-cached-binaries
# Post-install test scripts.
test_script:
- powershell build/script/appveyor-test.ps1
- powershell build/script/appveyor-test.ps1
# Don't actually build.
build: off

View File

@ -105,7 +105,7 @@ export const start = async (args: string[]): Promise<void> => {
const cssPromise = import("./CSS")
const completionProvidersPromise = import("./Services/Completion/CompletionProviders")
const parsedArgs = minimist(args)
const parsedArgs = minimist(args, { string: "_" })
const currentWorkingDirectory = process.cwd()
const normalizedFiles = parsedArgs._.map(
arg => (path.isAbsolute(arg) ? arg : path.join(currentWorkingDirectory, arg)),

View File

@ -16,8 +16,13 @@ export interface IBufferHighlightsUpdater {
clearHighlightsForLine(line: number): void
}
// Helper class to efficiently update
// buffer highlights in a batch.
/**
* Helper class to efficiently update
* buffer highlights in a batch
*
* @name BufferHighlightsUpdater
* @class
*/
export class BufferHighlightsUpdater implements IBufferHighlightsUpdater {
private _calls: any[] = []

View File

@ -10,6 +10,8 @@ import * as types from "vscode-languageserver-types"
import { Event, IEvent } from "oni-types"
import { getDocumentationText } from "../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import { ContextMenu } from "./../../Services/ContextMenu"
import * as CompletionUtility from "./../../Services/Completion/CompletionUtility"
@ -64,7 +66,7 @@ const _convertCompletionForContextMenu = (completion: types.CompletionItem): any
const getCompletionDocumentation = (item: types.CompletionItem): string | null => {
if (item.documentation) {
return item.documentation
return getDocumentationText(item.documentation)
} else if (item.data && item.data.documentation) {
return item.data.documentation
} else {

View File

@ -315,19 +315,15 @@ export class NeovimEditor extends Editor implements Oni.Editor {
this._actions.setColors(updatedColors)
}
this._colors.onColorsChanged.subscribe(() => onColorsChanged())
this._colors.onColorsChanged.subscribe(onColorsChanged)
onColorsChanged()
const onTokenColorschanged = () => {
if (this._neovimInstance.isInitialized) {
this._neovimInstance.tokenColorSynchronizer.synchronizeTokenColors(
this._tokenColors.tokenColors,
)
}
}
this.trackDisposable(
this._tokenColors.onTokenColorsChanged.subscribe(() => onTokenColorschanged()),
this._tokenColors.onTokenColorsChanged.subscribe(() => {
if (this._neovimInstance.isInitialized) {
this._syntaxHighlighter.notifyColorschemeRedraw(`${this.activeBuffer.id}`)
}
}),
)
// Overlays
@ -1292,6 +1288,8 @@ export class NeovimEditor extends Editor implements Oni.Editor {
private async _onColorsChanged(): Promise<void> {
const newColorScheme = await this._neovimInstance.eval<string>("g:colors_name")
const { bufferNumber } = await this._neovimInstance.getContext()
this._syntaxHighlighter.notifyColorschemeRedraw(`${bufferNumber}`)
// In error cases, the neovim API layer returns an array
if (typeof newColorScheme !== "string") {

View File

@ -2,18 +2,20 @@
* CodeAction.ts
*
*/
import * as _ from "lodash"
import { ErrorCodes } from "vscode-jsonrpc/lib/messages"
import * as types from "vscode-languageserver-types"
import * as Oni from "oni-api"
import { MenuManager } from "./../../Services/Menu"
import * as Log from "oni-core-logging"
import { LanguageManager } from "./../../Services/Language"
import { Menu, MenuManager } from "./../../Services/Menu"
import * as Helpers from "./../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import { asObservable } from "./../../Utility"
import { asObservable, sleep } from "./../../Utility"
import { Definition } from "./Definition"
export class Symbols {
@ -59,19 +61,9 @@ export class Symbols {
.debounceTime(25)
.do(() => menu.setLoading(true))
.concatMap(async (newText: string) => {
const buffer = this._editor.activeBuffer
const symbols: types.SymbolInformation[] = await this._languageManager.sendLanguageServerRequest(
buffer.language,
buffer.filePath,
"workspace/symbol",
{
textDocument: {
uri: Helpers.wrapPathInFileUri(buffer.filePath),
},
query: newText,
},
)
return symbols
return this._requestSymbols(this._editor.activeBuffer, "workspace/symbol", menu, {
query: newText,
})
})
.subscribe((newItems: types.SymbolInformation[]) => {
menu.setLoading(false)
@ -94,15 +86,10 @@ export class Symbols {
const buffer = this._editor.activeBuffer
const result: types.SymbolInformation[] = await this._languageManager.sendLanguageServerRequest(
buffer.language,
buffer.filePath,
const result: types.SymbolInformation[] = await this._requestSymbols(
buffer,
"textDocument/documentSymbol",
{
textDocument: {
uri: Helpers.wrapPathInFileUri(buffer.filePath),
},
},
menu,
)
const options: Oni.Menu.MenuOption[] = result.map(item => this._symbolInfoToMenuItem(item))
@ -175,4 +162,40 @@ export class Symbols {
return "question"
}
}
/**
* Send a request for symbols, retrying if the server is not ready, as long as the menu is open.
*/
private async _requestSymbols(
buffer: Oni.Buffer,
command: string,
menu: Menu,
options: any = {},
): Promise<types.SymbolInformation[]> {
while (menu.isOpen()) {
try {
return await this._languageManager.sendLanguageServerRequest(
buffer.language,
buffer.filePath,
command,
_.extend(
{
textDocument: {
uri: Helpers.wrapPathInFileUri(buffer.filePath),
},
},
options,
),
)
} catch (e) {
if (e.code === ErrorCodes.ServerNotInitialized) {
Log.warn("[Symbols] Language server not yet initialised, trying again...")
await sleep(1000)
} else {
throw e
}
}
}
return []
}
}

View File

@ -42,13 +42,13 @@ export const wrapPathInFileUri = (path: string) => getFilePrefix() + Utility.nor
export const unwrapFileUriPath = (uri: string) => decodeURIComponent(uri.split(getFilePrefix())[1])
export const getTextFromContents = (
contents: types.MarkedString | types.MarkedString[],
contents: types.MarkedString | types.MarkupContent | types.MarkedString[],
): IMarkedStringResult[] => {
if (contents instanceof Array) {
return contents.map(markedString => getTextFromMarkedString(markedString))
} else {
return [getTextFromMarkedString(contents)]
}
const text = isMarkupContent(contents) ? getDocumentationText(contents) : contents
return [getTextFromMarkedString(text)]
}
export const pathToTextDocumentIdentifierParms = (path: string) => ({
@ -151,3 +151,15 @@ const getFilePrefix = () => {
return "file://"
}
}
export function isMarkupContent(input: any): input is types.MarkupContent {
return typeof input === "object" && input !== null && "value" in input && "kind" in input
}
export const getDocumentationText = (documentation: string | types.MarkupContent) => {
// Documentation can be a string or an object specifying the documentations type as well as the value.
if (typeof documentation === "string") {
return documentation
}
return documentation.value
}

View File

@ -10,19 +10,38 @@ export interface IShellEnvironmentFetcher {
getEnvironmentVariables(): Promise<any>
}
interface IShellEnv {
default: {
sync: (shell?: string) => NodeJS.ProcessEnv
}
}
export class ShellEnvironmentFetcher implements IShellEnvironmentFetcher {
private _shellEnvPromise: Promise<any>
private _shellEnv: any
private _shellEnv: IShellEnv
constructor() {
// Dynamic imports return { default: Module }
this._shellEnvPromise = import("shell-env")
}
public async getEnvironmentVariables(): Promise<any> {
public async getEnvironmentVariables(): Promise<NodeJS.ProcessEnv> {
if (!this._shellEnv) {
this._shellEnv = await this._shellEnvPromise
return this._shellEnv.sync()
}
try {
const userShell = configuration.getValue<string>("oni.userShell")
const shell = typeof userShell === "string" ? userShell : undefined
const env = this._shellEnv.default.sync(shell)
return env
} catch (error) {
Log.warn(
`[Oni environment fetcher]: unable to get enviroment variables because: ${
error.message
}`,
)
}
return {}
}
}
@ -129,21 +148,35 @@ export class Process implements Oni.Process {
try {
if (!this._env) {
this._env = (await this._shellEnvironmentFetcher.getEnvironmentVariables()) || {}
this._env = await this._shellEnvironmentFetcher.getEnvironmentVariables()
}
existingPath = process.env.Path || process.env.PATH
} catch (e) {
existingPath = process.env.Path || process.env.PATH
}
const additionalEnvironmentVariables =
configuration.getValue("environment.additionalVariables") || {}
const requiredOptions = {
env: {
...process.env,
...this._env,
...originalSpawnOptions.env,
...additionalEnvironmentVariables,
},
}
// TODO: Workaround for the bug fix here:
// https://github.com/neovim/neovim/pull/9345
// Once 0.3.2 is available and we've switched, we won't need this anymore.
if (Platform.isMac() && !requiredOptions.env.LANG) {
requiredOptions.env.LANG = "en_us.UTF-8"
Log.warn(
"'LANG' environment variable not set, using default value of 'en_us.UTF-8'. Consider setting environment.additionalVariables['LANG'].",
)
}
requiredOptions.env.PATH = this.mergePathEnvironmentVariable(
existingPath,
configuration.getValue("environment.additionalPaths"),

View File

@ -146,7 +146,7 @@ export class CanvasRenderer implements INeovimRenderer {
row.forEach((span: ISpan) => {
// All spans that have changed in current rendering pass
const rowIndex = Number.parseInt(y)
const rowIndex = Number.parseInt(y, 10)
const currentCell = screenInfo.getCell(span.startX, rowIndex)

View File

@ -3,6 +3,8 @@
*
* Selectors are functions that take a state and derive a value from it.
*/
import * as isEqual from "lodash/isEqual"
import * as omit from "lodash/omit"
import { ICompletionState } from "./CompletionState"
@ -67,25 +69,69 @@ export const filterCompletionOptions = (
if (!searchText) {
return items
}
if (!items || !items.length) {
return null
}
// Must start with first letter in searchText, and then be at least abbreviated by searchText.
const filterRegEx = new RegExp("^" + searchText.split("").join(".*") + ".*")
const filteredOptions = items.filter(f => {
const textToFilterOn = f.filterText || f.label
return textToFilterOn.match(filterRegEx)
})
const sortedOptions = filteredOptions.sort((itemA, itemB) => {
const itemASortText = itemA.filterText || itemA.label
const itemBSortText = itemB.filterText || itemB.label
return filteredOptions.sort((itemA, itemB) => {
const itemAFilterText = itemA.filterText || itemA.label
const itemBFilterText = itemB.filterText || itemB.label
const indexOfA = itemASortText.indexOf(searchText)
const indexOfB = itemBSortText.indexOf(searchText)
const indexOfA = itemAFilterText.indexOf(searchText)
const indexOfB = itemBFilterText.indexOf(searchText)
return indexOfB - indexOfA
// Ensure abbreviated matches are sorted below exact matches.
if (indexOfA >= 0 && indexOfB === -1) {
return -1
} else if (indexOfA === -1 && indexOfB >= 0) {
return 1
// Else sort by label to keep related results together.
} else if (itemASortText < itemBSortText) {
return -1
} else if (itemASortText > itemBSortText) {
return 1
// Fallback to sort by language server specified sortText.
} else if (itemA.sortText < itemB.sortText) {
return -1
} else if (itemA.sortText > itemB.sortText) {
return 1
}
return 0
})
// Language servers can return duplicate entries (e.g. cquery).
const uniqueOptions: types.CompletionItem[] = _uniq(sortedOptions)
return uniqueOptions
}
/**
* Get unique completion items, assuming they're sorted so duplicates are contiguous.
*
* Adapted from https://github.com/lodash/lodash/blob/master/.internal/baseSortedUniq.js, since
* lodash has no `sortedUniqWith` function.
*/
const _uniq = (array: types.CompletionItem[]) => {
let seenReduced: any
let index = -1
let resIndex = 0
const { length } = array
const result = []
while (++index < length) {
const value = array[index]
// Omit the `sortText` which can be different even if all other attributes are the same.
const reduced = omit(value, "sortText")
if (!index || !isEqual(reduced, seenReduced)) {
seenReduced = reduced
result[resIndex++] = value
}
}
return result
}

View File

@ -134,6 +134,16 @@ export const completionResultsReducer: Reducer<ICompletionResults> = (
return {
...state,
completions: state.completions.map(completion => {
// Prefer `detail` field if available, to avoid splatting e.g. methods with
// the same name but different signature.
if (completion.detail && action.completionItemWithDetails.detail) {
if (completion.detail === action.completionItemWithDetails.detail) {
return action.completionItemWithDetails
} else {
return completion
}
}
if (completion.label === action.completionItemWithDetails.label) {
return action.completionItemWithDetails
} else {

View File

@ -10,6 +10,7 @@ import * as Log from "oni-core-logging"
import * as Helpers from "./../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import { LanguageManager } from "./../Language"
import { IServerCapabilities } from "./../Language/ServerCapabilities"
export interface CompletionsRequestContext {
language: string
@ -86,6 +87,12 @@ export class LanguageServiceCompletionsRequestor implements ICompletionsRequesto
filePath: string,
completionItem: types.CompletionItem,
): Promise<types.CompletionItem> {
const caps: IServerCapabilities = await this._languageManager.getCapabilitiesForLanguage(
language,
)
if (!caps.completionProvider.resolveProvider) {
return completionItem
}
let result
try {
result = await this._languageManager.sendLanguageServerRequest(

View File

@ -109,6 +109,8 @@ const BaseConfiguration: IConfigurationValues = {
"oni.enhancedSyntaxHighlighting": true,
"oni.userShell": undefined,
"oni.loadInitVim": false,
"oni.hideMenu": false,
@ -147,6 +149,8 @@ const BaseConfiguration: IConfigurationValues = {
"editor.quickOpen.alternativeOpenMode": Oni.FileOpenMode.ExistingTab,
"editor.quickOpen.showHidden": true,
"quickOpen.defaultOpenMode": Oni.FileOpenMode.Edit,
"editor.split.mode": "native",
"editor.typingPrediction": true,
@ -172,6 +176,7 @@ const BaseConfiguration: IConfigurationValues = {
"explorer.maxUndoFileSizeInBytes": 500_000,
"environment.additionalPaths": [],
"environment.additionalVariables": {},
"keyDisplayer.showInInsertMode": false,
@ -181,6 +186,14 @@ const BaseConfiguration: IConfigurationValues = {
"language.go.languageServer.command": "go-langserver",
"language.go.textMateGrammar": path.join(__dirname, "extensions", "go", "syntaxes", "go.json"),
"language.vue.textMateGrammar": path.join(
__dirname,
"extensions",
"vue",
"syntaxes",
"vue.json",
),
"language.python.languageServer.command": "pyls",
"language.cpp.languageServer.command": "clangd",
"language.c.languageServer.command": "clangd",

View File

@ -111,6 +111,10 @@ export interface IConfigurationValues {
// Set this to 'false' to avoid loading the default config, and load settings from init.vim instead.
"oni.useDefaultConfig": boolean
// This string represents the path to the shell that the user would like oni to use to extract
// environment variables that it uses derives the $PATH variable from.
"oni.userShell": string
// By default, user's init.vim is not loaded, to avoid conflicts.
// Set this to `true` to enable loading of init.vim.
// Set this to a string to override the init.vim path.
@ -165,6 +169,9 @@ export interface IConfigurationValues {
"editor.quickOpen.alternativeOpenMode": Oni.FileOpenMode
"editor.quickOpen.showHidden": boolean
// this is new command to replace the above legacy editor prefixed command
"quickOpen.defaultOpenMode": Oni.FileOpenMode
"editor.errors.slideOnFocus": boolean
"editor.formatting.formatOnSwitchToNormalMode": boolean // TODO: Make this setting reliable. If formatting is slow, it will hose edits... not fun
@ -207,6 +214,9 @@ export interface IConfigurationValues {
// (and available in terminal integration, later)
"environment.additionalPaths": string[]
// Additional environment variables that override the default settings
"environment.additionalVariables": any
// User configurable array of files for which
// the image layer opens
"editor.imageLayerExtensions": string[]

View File

@ -160,10 +160,6 @@ export class ContextMenu {
)
}
public updateItem(item: any): void {
this._actions.setDetailedMenuItem(item)
}
public hide(): void {
this._actions.hidePopupMenu()
}

View File

@ -23,7 +23,7 @@ import { ContextMenuState } from "./ContextMenu"
export interface IContextMenuItem {
label: string
detail?: string
documentation?: string
documentation?: string | types.MarkupContent
icon?: string
}

View File

@ -18,7 +18,7 @@ export interface KeyBindingMap {
}
const MAX_DELAY_BETWEEN_KEY_CHORD = 250 /* milliseconds */
const MAX_CHORD_SIZE = 4
const MAX_CHORD_SIZE = 6
import { KeyboardResolver } from "./../Input/Keyboard/KeyboardResolver"

View File

@ -16,14 +16,17 @@ export const sortTextEdits = (edits: types.TextEdit[]): types.TextEdit[] => {
return sortedEdits
}
export const convertTextDocumentEditsToFileMap = (
edits: types.TextDocumentEdit[],
export const convertTextDocumentChangesToFileMap = (
edits: Array<types.TextDocumentEdit | types.CreateFile | types.RenameFile | types.DeleteFile>,
): { [fileUri: string]: types.TextEdit[] } => {
if (!edits) {
return {}
}
return edits.reduce((prev, curr) => {
if (!("textDocument" in curr)) {
return prev
}
return {
...prev,
[curr.textDocument.uri]: edits,

View File

@ -2,6 +2,7 @@ import * as React from "react"
import * as types from "vscode-languageserver-types"
import { getDocumentationText } from "../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import {
QuickInfoDocumentation,
QuickInfoElement,
@ -66,7 +67,9 @@ export const getElementsFromType = (signatureHelp: types.SignatureHelp): JSX.Ele
{elements}
</Title>
{!!(selectedArgument && selectedArgument.documentation) && (
<QuickInfoDocumentation text={selectedArgument.documentation} />
<QuickInfoDocumentation
text={getDocumentationText(selectedArgument.documentation)}
/>
)}
</React.Fragment>
)

View File

@ -66,13 +66,6 @@ export const setMenuItems = (id: string, items: any[]) => ({
},
})
export const setDetailedMenuItem = (item: any) => ({
type: "SET_DETAILED_MENU_ITEM",
payload: {
detailedItem: item,
},
})
export const hidePopupMenu = () => (dispatch: any, getState: any) => {
const state = getState()

View File

@ -33,13 +33,6 @@ export interface IShowMenuAction {
}
}
export interface ISetDetailedMenuItem<T> {
type: "SET_DETAILED_MENU_ITEM"
payload: {
detailedItem: T
}
}
export interface ISetMenuItems<T> {
type: "SET_MENU_ITEMS"
payload: {

View File

@ -63,32 +63,6 @@ export function createReducer<T, FilteredT extends T>() {
isLoading: false,
}
}
case "SET_DETAILED_MENU_ITEM": {
if (!s || !s.options) {
return s
}
if (!a.payload.detailedItem) {
return s
}
const options = s.options.map(entry => {
// TODO: Decide on canonical interface for menu options
if ((entry as any).label === a.payload.detailedItem.label) {
return a.payload.detailedItem
} else {
return entry
}
})
const filterFunc = s.filterFunction
const filteredOptions2 = filterFunc(options, s.filter)
return {
...s,
options,
filteredOptions: filteredOptions2,
}
}
case "SET_MENU_ITEMS": {
if (!s || s.id !== a.payload.id) {
return s
@ -129,6 +103,8 @@ export function createReducer<T, FilteredT extends T>() {
return s
}
// Note that for language server completions, the `filterFunc` is a no-op - the
// items are filtered elsewhere (but this FILTER_MENU action is still dispatched).
const filterFunc = s.filterFunction
const filteredOptionsSorted = filterFunc(s.options, a.payload.filter)
@ -136,6 +112,7 @@ export function createReducer<T, FilteredT extends T>() {
...s,
filter: a.payload.filter,
filteredOptions: filteredOptionsSorted,
selectedIndex: 0,
}
}
default:

View File

@ -144,8 +144,8 @@ class Recorder implements Oni.Recorder {
const toArrayBuffer = async (blob: Blob): Promise<ArrayBuffer> => {
return new Promise<ArrayBuffer>((resolve, reject) => {
const fileReader = new FileReader()
fileReader.onload = function() {
const arrayBuffer = this.result
fileReader.onload = evt => {
const arrayBuffer = fileReader.result as ArrayBuffer
resolve(arrayBuffer)
}
fileReader.readAsArrayBuffer(blob)

View File

@ -12,6 +12,7 @@ import { ISyntaxHighlightTokenInfo } from "./SyntaxHighlightingStore"
export interface ISyntaxHighlighter extends IDisposable {
notifyBufferUpdate(evt: Oni.EditorBufferChangedEventArgs): Promise<void>
notifyViewportChanged(bufferId: string, topLineInView: number, bottomLineInView: number): void
notifyColorschemeRedraw(id: string): void
getHighlightTokenAt(bufferId: string, position: types.Position): ISyntaxHighlightTokenInfo
}

View File

@ -4,12 +4,12 @@
* Handles enhanced syntax highlighting
*/
import { Buffer, Editor } from "oni-api"
import * as Log from "oni-core-logging"
import { prettyPrint } from "./../../Utility"
import { TokenColor, TokenColors } from "./../TokenColors"
import { NeovimEditor } from "./../../Editor/NeovimEditor"
import { HighlightInfo } from "./Definitions"
import {
ISyntaxHighlightLineInfo,
@ -18,21 +18,37 @@ import {
} from "./SyntaxHighlightingStore"
import { TokenScorer } from "./TokenScorer"
import { IBufferHighlightsUpdater } from "../../Editor/BufferHighlights"
import * as Selectors from "./SyntaxHighlightSelectors"
// SyntaxHighlightReconciler
//
// Essentially a renderer / reconciler, that will push
// highlight calls to the active buffer based on the active
// window and viewport
interface IBufferWithSyntaxHighlighter extends Buffer {
updateHighlights?: (
tokenColors: TokenColor[],
highlightCallback: (args: IBufferHighlightsUpdater) => void,
) => void
}
export interface IEditorWithSyntaxHighlighter extends Editor {
activeBuffer: IBufferWithSyntaxHighlighter
}
/**
* SyntaxHighlightReconciler
*
* Essentially a renderer / reconciler, that will push
* highlight calls to the active buffer based on the active
* window and viewport
* @name SyntaxHighlightReconciler
* @class
*/
export class SyntaxHighlightReconciler {
private _previousState: { [line: number]: ISyntaxHighlightLineInfo } = {}
private _tokenScorer = new TokenScorer()
constructor(private _editor: NeovimEditor, private _tokenColors: TokenColors) {}
constructor(private _editor: IEditorWithSyntaxHighlighter, private _tokenColors: TokenColors) {}
public update(state: ISyntaxHighlightState) {
const activeBuffer: any = this._editor.activeBuffer
const { activeBuffer } = this._editor
if (!activeBuffer) {
return
@ -77,6 +93,15 @@ export class SyntaxHighlightReconciler {
}
})
// Get only the token colors that apply to the visible section of the buffer
const visibleTokens = tokens.reduce((accumulator, { highlights }) => {
if (highlights) {
const tokenColors = highlights.map(({ tokenColor }) => tokenColor)
accumulator.push(...tokenColors)
}
return accumulator
}, [])
filteredLines.forEach(line => {
const lineNumber = parseInt(line, 10)
this._previousState[line] = Selectors.getLineFromBuffer(
@ -87,26 +112,22 @@ export class SyntaxHighlightReconciler {
if (tokens.length) {
Log.verbose(
"[SyntaxHighlightReconciler] Applying changes to " + tokens.length + " lines.",
`[SyntaxHighlightReconciler] Applying changes to ${tokens.length} lines.`,
)
activeBuffer.updateHighlights(
this._tokenColors.tokenColors,
(highlightUpdater: any) => {
tokens.forEach(token => {
const { line, highlights } = token
if (Log.isDebugLoggingEnabled()) {
Log.debug(
"[SyntaxHighlightingReconciler] Updating tokens for line: " +
token.line +
" | " +
JSON.stringify(highlights),
)
}
activeBuffer.updateHighlights(visibleTokens, highlightUpdater => {
tokens.forEach(token => {
const { line, highlights } = token
if (Log.isDebugLoggingEnabled()) {
Log.debug(
`[SyntaxHighlightingReconciler] Updating tokens for line: ${line} | ${prettyPrint(
highlights,
)}`,
)
}
highlightUpdater.setHighlightsForLine(line, highlights)
})
},
)
highlightUpdater.setHighlightsForLine(line, highlights)
})
})
}
}
}
@ -121,8 +142,10 @@ export class SyntaxHighlightReconciler {
}
private _getHighlightGroupFromScope(scopes: string[]): TokenColor {
const configurationColors = this._tokenColors.tokenColors
const highestRanked = this._tokenScorer.rankTokenScopes(scopes, configurationColors)
const highestRanked = this._tokenScorer.rankTokenScopes(
scopes,
this._tokenColors.tokenColors,
)
return highestRanked
}
}

View File

@ -28,7 +28,10 @@ import {
} from "./SyntaxHighlightingStore"
import { ISyntaxHighlighter } from "./ISyntaxHighlighter"
import { SyntaxHighlightReconciler } from "./SyntaxHighlightReconciler"
import {
IEditorWithSyntaxHighlighter,
SyntaxHighlightReconciler,
} from "./SyntaxHighlightReconciler"
import { getLineFromBuffer } from "./SyntaxHighlightSelectors"
import * as Utility from "./../../Utility"
@ -45,7 +48,10 @@ export class SyntaxHighlighter implements ISyntaxHighlighter {
constructor(private _editor: NeovimEditor, private _tokenColors: TokenColors) {
this._store = createSyntaxHighlightStore()
this._reconciler = new SyntaxHighlightReconciler(this._editor, this._tokenColors)
this._reconciler = new SyntaxHighlightReconciler(
this._editor as IEditorWithSyntaxHighlighter,
this._tokenColors,
)
this._unsubscribe = this._store.subscribe(() => {
const state = this._store.getState()
this._reconciler.update(state)
@ -62,12 +68,10 @@ export class SyntaxHighlighter implements ISyntaxHighlighter {
bottomLineInView: number,
): void {
Log.verbose(
"[SyntaxHighlighting.notifyViewportChanged] - bufferId: " +
bufferId +
" topLineInView: " +
topLineInView +
" bottomLineInView: " +
bottomLineInView,
`[SyntaxHighlighting.notifyViewportChanged] -
bufferId: ${bufferId}
topLineInView: ${topLineInView}
bottomLineInView: ${bottomLineInView}`,
)
const state = this._store.getState()
@ -89,6 +93,10 @@ export class SyntaxHighlighter implements ISyntaxHighlighter {
})
}
public async notifyColorschemeRedraw(bufferId: string) {
this._store.dispatch({ type: "SYNTAX_RESET_BUFFER", bufferId })
}
public async notifyBufferUpdate(evt: Oni.EditorBufferChangedEventArgs): Promise<void> {
const firstChange = evt.contentChanges[0]
if (!firstChange.range && !firstChange.rangeLength) {
@ -157,6 +165,10 @@ export class NullSyntaxHighlighter implements ISyntaxHighlighter {
return null
}
public notifyColorschemeRedraw(id: string): void {
return null
}
public notifyViewportChanged(
bufferId: string,
topLineInView: number,

View File

@ -51,6 +51,11 @@ export const bufferReducer: Reducer<IBufferSyntaxHighlightState> = (
action: ISyntaxHighlightAction,
) => {
switch (action.type) {
case "SYNTAX_RESET_BUFFER":
return {
...state,
lines: linesReducer(state.lines, action),
}
case "SYNTAX_UPDATE_BUFFER":
return {
...state,
@ -125,6 +130,21 @@ export const linesReducer: Reducer<SyntaxHighlightLines> = (
}
return newState
}
case "SYNTAX_RESET_BUFFER":
const resetState = Object.entries(state).reduce<SyntaxHighlightLines>(
(newResetState, [lineNumber, line]) => {
newResetState[lineNumber] = {
tokens: [],
ruleStack: null,
...line,
dirty: true,
}
return newResetState
},
{},
)
return resetState
case "SYNTAX_UPDATE_BUFFER":
const updatedBufferState: SyntaxHighlightLines = {
...state,

View File

@ -73,6 +73,10 @@ export const DefaultSyntaxHighlightState: ISyntaxHighlightState = {
}
export type ISyntaxHighlightAction =
| {
type: "SYNTAX_RESET_BUFFER"
bufferId: string
}
| {
type: "SYNTAX_UPDATE_BUFFER"
language: string
@ -150,14 +154,14 @@ const updateBufferLineMiddleware = (store: any) => (next: any) => (action: any)
action.lineNumber === 0 ? null : buffer.lines[action.lineNumber - 1].ruleStack
const tokenizeResult = grammar.tokenizeLine(action.line, previousRuleStack)
const tokens = tokenizeResult.tokens.map((t: any) => ({
const tokens = tokenizeResult.tokens.map(token => ({
range: types.Range.create(
action.lineNumber,
t.startIndex,
token.startIndex,
action.lineNumber,
t.endIndex,
token.endIndex,
),
scopes: t.scopes,
scopes: token.scopes,
}))
const updateInsertLineAction: ISyntaxHighlightAction = {
@ -180,7 +184,11 @@ const updateBufferLineMiddleware = (store: any) => (next: any) => (action: any)
const updateTokenMiddleware = (store: any) => (next: any) => (action: any) => {
const result: ISyntaxHighlightAction = next(action)
if (action.type === "SYNTAX_UPDATE_BUFFER" || action.type === "SYNTAX_UPDATE_BUFFER_VIEWPORT") {
if (
action.type === "SYNTAX_UPDATE_BUFFER" ||
action.type === "SYNTAX_UPDATE_BUFFER_VIEWPORT" ||
action.type === "SYNTAX_RESET_BUFFER"
) {
const state: ISyntaxHighlightState = store.getState()
const bufferId = action.bufferId
@ -212,7 +220,7 @@ const updateTokenMiddleware = (store: any) => (next: any) => (action: any) => {
syntaxHighlightingJobs.startJob(
new SyntaxHighlightingPeriodicJob(
store as any,
store,
action.bufferId,
grammar,
relevantRange.top,

View File

@ -119,7 +119,7 @@ export class TokenScorer {
if (parts.length < 2) {
return null
}
const matchingToken = theme.find(color => color.scope === scope)
const matchingToken = theme.find(color => color.scope.includes(scope))
if (matchingToken) {
return matchingToken
}

View File

@ -190,8 +190,8 @@ class TokenThemeProvider extends React.Component<IProps, IState> {
public generateTokens({ defaultMap = defaultsToMap, defaultTokens }: IGenerateTokenArgs) {
const newTokens = Object.keys(defaultMap).reduce((acc, defaultTokenName) => {
const defaultToken = this.props.tokenColors.find(
token => token.scope === defaultTokenName,
const defaultToken = this.props.tokenColors.find(token =>
token.scope.includes(defaultTokenName),
)
if (defaultToken) {
const tokens = defaultMap[defaultTokenName].map(name =>

View File

@ -35,6 +35,7 @@ export interface IThemeColors {
"tabs.background": string
"tabs.foreground": string
"tabs.borderBottom": string
"tabs.active.foreground": string
"tabs.active.background": string
@ -288,6 +289,7 @@ export const DefaultThemeColors: IThemeColors = {
"tabs.background": ColorBlack,
"tabs.foreground": ColorWhite,
"tabs.borderBottom": null,
"tabs.active.background": null,
"tabs.active.foreground": null,

View File

@ -8,8 +8,11 @@
import { Event, IDisposable, IEvent } from "oni-types"
import { Configuration, IConfigurationValues } from "./Configuration"
import { ThemeManager } from "./Themes"
export interface TokenColor {
scope: string
scope: string[]
settings: TokenColorStyle
// private field for determining where a token came from
_source?: string
@ -18,6 +21,7 @@ export interface TokenColor {
export interface ThemeToken {
scope: string | string[]
settings: TokenColorStyle
_source?: string
}
export interface TokenColorStyle {
@ -27,9 +31,6 @@ export interface TokenColorStyle {
fontStyle: "bold" | "italic" | "bold italic"
}
import { Configuration, IConfigurationValues } from "./Configuration"
import { ThemeManager } from "./Themes"
export class TokenColors implements IDisposable {
private _subscriptions: IDisposable[] = []
private _tokenColors: TokenColor[] = []
@ -71,38 +72,49 @@ export class TokenColors implements IDisposable {
this._subscriptions = []
}
private _flattenThemeTokens = (themeTokens: ThemeToken[] = []) => {
const multidimensionalTokens = themeTokens.map(token => {
if (Array.isArray(token.scope)) {
return token.scope.map(s => ({
scope: s,
settings: token.settings,
}))
}
return token
})
return [].concat(...multidimensionalTokens).filter(t => !!t.scope)
}
private _updateTokenColors(): void {
const {
activeTheme: {
colors: { "editor.tokenColors": tokenColorsFromTheme = [] },
colors: { "editor.tokenColors": themeTokens = [] },
},
} = this._themeManager
const themeTokens = this._flattenThemeTokens(tokenColorsFromTheme)
const userColors = this._configuration.getValue("editor.tokenColors")
this._tokenColors = this._mergeTokenColors({
const combinedColors = this._mergeTokenColors({
user: userColors,
theme: themeTokens,
defaults: this._defaultTokenColors,
})
this._tokenColors = this._convertThemeTokenScopes(combinedColors)
this._onTokenColorsChangedEvent.dispatch()
}
/**
* Theme tokens can pass in token scopes as a string or an array
* this converts all token scopes passed in to an array of strings
*
* @name convertThemeTokenScopes
* @function
* @param {ThemeToken[]} tokens
* @returns {TokenColor[]}
*/
private _convertThemeTokenScopes(tokens: ThemeToken[]) {
// TODO: figure out how space separated token scopes should be handled
// token.scope.split(" ") -> convert "meta.var string.quoted" -> ["meta.var", "string.quoted"]
// this however breaks prioritisation of tokens
return tokens.map(token => {
const scope = !token.scope
? []
: Array.isArray(token.scope)
? token.scope
: [token.scope]
return { ...token, scope }
})
}
/**
* Merge different token source whilst unifying settings
* each source is passed by name so that later the priority
@ -111,34 +123,32 @@ export class TokenColors implements IDisposable {
* user source
*/
private _mergeTokenColors(tokens: {
user: TokenColor[]
user: ThemeToken[]
defaults: TokenColor[]
theme: TokenColor[]
theme: ThemeToken[]
}) {
return Object.keys(tokens).reduce(
(output, key) => {
const tokenColors: TokenColor[] = tokens[key]
return tokenColors.reduce((mergedTokens, currentToken) => {
return Object.entries(tokens).reduce<ThemeToken[]>(
(output, [_source, tokenColors]) =>
tokenColors.reduce((mergedTokens, currentToken) => {
const duplicateToken = mergedTokens.find(t => currentToken.scope === t.scope)
if (duplicateToken) {
return mergedTokens.map(existingToken => {
if (existingToken.scope === duplicateToken.scope) {
return this._mergeSettings(existingToken, {
...currentToken,
_source: key,
_source,
})
}
return existingToken
})
}
return [...mergedTokens, { ...currentToken, _source: key }]
}, output)
},
[] as TokenColor[],
return [...mergedTokens, { ...currentToken, _source }]
}, output),
[],
)
}
private _mergeSettings(prev: TokenColor, next: TokenColor) {
private _mergeSettings(prev: ThemeToken, next: ThemeToken) {
const priority = {
user: 2,
theme: 1,

View File

@ -26,7 +26,7 @@ import * as Helpers from "./../../Plugins/Api/LanguageClient/LanguageClientHelpe
import { Configuration } from "./../Configuration"
import { EditorManager } from "./../EditorManager"
import { convertTextDocumentEditsToFileMap } from "./../Language/Edits"
import { convertTextDocumentChangesToFileMap } from "./../Language/Edits"
import { WorkspaceConfiguration } from "./WorkspaceConfiguration"
@ -71,10 +71,9 @@ export class Workspace implements Oni.Workspace.Api {
}
public async applyEdits(edits: types.WorkspaceEdit): Promise<void> {
let editsToUse = edits
if (edits.documentChanges) {
editsToUse = convertTextDocumentEditsToFileMap(edits.documentChanges)
}
const editsToUse = edits.documentChanges
? convertTextDocumentChangesToFileMap(edits.documentChanges)
: edits.changes
const files = Object.keys(editsToUse)

View File

@ -63,7 +63,7 @@ const TabsWrapper = styled<ITabsWrapperProps, "div">("div")`
align-items: flex-end;
width: 100%;
overflow-x: hidden;
border-bottom: 4px solid ${themeGet("tabs.background")};
border-bottom: 4px solid ${themeGet("tabs.borderBottom", "tabs.background")};
font-family: ${props => props.fontFamily};
font-size: ${props => props.fontSize};
transform: translateY(-3px);

View File

@ -63,8 +63,6 @@ class WildMenu extends React.Component<Props, State> {
currentPage: 1,
itemsPerPage: 10,
}
private selectedElement: HTMLUListElement
private containerElement: HTMLUListElement
public componentWillReceiveProps(next: Props) {
if (next.selected !== this.props.selected) {
@ -80,16 +78,10 @@ class WildMenu extends React.Component<Props, State> {
return (
visible && (
<WildMenuList innerRef={e => (this.containerElement = e)}>
<WildMenuList>
{currentItems &&
currentItems.map((option, i) => (
<WildMenuItem
innerRef={e =>
i === current - 1 ? (this.selectedElement = e) : null
}
selected={i === current}
key={option + i}
>
<WildMenuItem selected={i === current} key={option + i}>
<span>
<Icon name="file-text" />
</span>

View File

@ -311,3 +311,8 @@ export function get<T>(obj: T, ...paths: string[]): string {
.split(".")
.reduce((a, b) => (a && a[b] ? a[b] : null), obj)
}
export function prettyPrint<T extends object>(item: T, spacing = 2) {
// tslint:disable-next-line
console.log(JSON.stringify(item, null, spacing))
}

View File

@ -524,8 +524,8 @@ export class NeovimInstance extends EventEmitter implements INeovimInstance {
const settings = vimHighlightToTokenColorStyle(currentValue)
const newScopeNames: string[] = VimHighlightToDefaultScope[highlightGroupName] || []
const newScopes = newScopeNames.map((scope): TokenColor => ({
scope,
const newScopes = newScopeNames.map(scope => ({
scope: [scope],
settings,
}))

View File

@ -28,42 +28,41 @@ const getGuiStringFromTokenColor = ({ settings: { fontStyle } }: TokenColor): st
}
export class NeovimTokenColorSynchronizer {
private _currentIndex: number = 0
private _currentIndex = 0
private _tokenScopeSelectorToHighlightName: { [key: string]: string } = {}
private _highlightNameToHighlightValue: { [key: string]: string } = {}
private _highlightNameToHighlightValue = new Map<string, string>()
constructor(private _neovimInstance: NeovimInstance) {}
constructor(private _neovimInstance: NeovimInstance) {
this._neovimInstance.onColorsChanged.subscribe(this._resetHighlightCache)
}
// This method creates highlight groups for any token colors that haven't been set yet
public async synchronizeTokenColors(tokenColors: TokenColor[]): Promise<void> {
const highlightsToAdd = tokenColors.map(tokenColor => {
public async synchronizeTokenColors(tokenColors: TokenColor[]) {
const highlightsToAdd = tokenColors.reduce<string[]>((newHighlights, tokenColor) => {
const highlightName = this._getOrCreateHighlightGroup(tokenColor)
const highlightFromScope = this._convertTokenStyleToHighlightInfo(tokenColor)
const currentHighlight = this._highlightNameToHighlightValue[highlightName]
const currentHighlight = this._highlightNameToHighlightValue.get(highlightName)
if (currentHighlight === highlightFromScope) {
return null
} else {
this._highlightNameToHighlightValue[highlightName] = highlightFromScope
return highlightFromScope
return newHighlights
}
})
this._highlightNameToHighlightValue.set(highlightName, highlightFromScope)
return [...newHighlights, highlightFromScope]
}, [])
const filteredHighlights = highlightsToAdd.filter(hl => !!hl)
const atomicCalls = filteredHighlights.map(hlCommand => ["nvim_command", [hlCommand]])
const atomicCalls = highlightsToAdd.map(hlCommand => ["nvim_command", [hlCommand]])
if (!atomicCalls.length) {
return
}
Log.info(
"[NeovimTokenColorSynchronizer::synchronizeTokenColors] Setting " +
atomicCalls.length +
" highlights",
`[NeovimTokenColorSynchronizer::synchronizeTokenColors] Setting ${
atomicCalls.length
} highlights`,
)
await this._neovimInstance.request("nvim_call_atomic", [atomicCalls])
this._neovimInstance.request("nvim_call_atomic", [atomicCalls])
Log.info(
"[NeovimTokenColorSynchronizer::synchronizeTokenColors] Highlights set successfully",
)
@ -77,6 +76,10 @@ export class NeovimTokenColorSynchronizer {
return this._getOrCreateHighlightGroup(tokenColor)
}
private _resetHighlightCache = () => {
this._highlightNameToHighlightValue.clear()
}
private _convertTokenStyleToHighlightInfo(tokenColor: TokenColor): string {
const name = this._getOrCreateHighlightGroup(tokenColor)
const foregroundColor = Color(tokenColor.settings.foreground).hex()
@ -86,9 +89,9 @@ export class NeovimTokenColorSynchronizer {
}
private _getOrCreateHighlightGroup(tokenColor: TokenColor): string {
const existingGroup = this._tokenScopeSelectorToHighlightName[
this._getKeyFromTokenColor(tokenColor)
]
const tokenKey = this._getKeyFromTokenColor(tokenColor)
const existingGroup = this._tokenScopeSelectorToHighlightName[tokenKey]
if (existingGroup) {
return existingGroup
} else {
@ -98,21 +101,20 @@ export class NeovimTokenColorSynchronizer {
"[NeovimTokenColorSynchronizer::_getOrCreateHighlightGroup] Creating new highlight group - " +
newHighlightGroupName,
)
this._tokenScopeSelectorToHighlightName[
this._getKeyFromTokenColor(tokenColor)
] = newHighlightGroupName
this._tokenScopeSelectorToHighlightName[tokenKey] = newHighlightGroupName
return newHighlightGroupName
}
}
private _getKeyFromTokenColor(tokenColor: TokenColor): string {
const {
settings: { background, foreground, fontStyle },
settings: { background = "none", foreground = "none", fontStyle = "none" },
} = tokenColor
const separator = `__`
const bg = `background-${background}`
const fg = `foreground-${foreground}`
const bold = `bold-${fontStyle && fontStyle.includes("bold")}`
const italic = `italic-${fontStyle && fontStyle.includes("italic")}`
return `${tokenColor.scope}_${bg}_${fg}_${bold}_${italic}`
const bold = `bold-${fontStyle ? fontStyle.includes("bold") : false}`
const italic = `italic-${fontStyle ? fontStyle.includes("italic") : false}`
return [tokenColor.scope, bg, fg, bold, italic].join(separator)
}
}

View File

@ -50,6 +50,11 @@ export interface NeovimActiveWindowState {
dimensions: Oni.Shapes.Rectangle
}
type Lines = string[]
type Height = number
type Width = number
type WindowPosition = [number, number]
export interface NeovimInactiveWindowState {
windowNumber: number
dimensions: Oni.Shapes.Rectangle
@ -70,6 +75,12 @@ export class NeovimWindowManager extends Utility.Disposable {
this._scrollObservable = new Subject<EventContext>()
const updateScroll = (evt: EventContext) => this._scrollObservable.next(evt)
const handleCommandLineEvent = async () => {
const ctx = await this._neovimInstance.getContext()
updateScroll(ctx)
}
// First element of the BufEnter event is the current buffer
this.trackDisposable(
this._neovimInstance.autoCommands.onBufEnter.subscribe(bufs =>
@ -88,6 +99,12 @@ export class NeovimWindowManager extends Utility.Disposable {
this.trackDisposable(
this._neovimInstance.autoCommands.onCursorMoved.subscribe(updateScroll),
)
this.trackDisposable(
this._neovimInstance.onCommandLineShow.subscribe(handleCommandLineEvent),
)
this.trackDisposable(
this._neovimInstance.onCommandLineHide.subscribe(handleCommandLineEvent),
)
this.trackDisposable(this._neovimInstance.autoCommands.onVimResized.subscribe(updateScroll))
this.trackDisposable(this._neovimInstance.onScroll.subscribe(updateScroll))
@ -167,31 +184,38 @@ export class NeovimWindowManager extends Utility.Disposable {
currentWinId: number,
context: EventContext,
): Promise<NeovimActiveWindowState> {
const positionCalls = [
["nvim_call_function", ["line", ["w0"]]],
["nvim_call_function", ["line", ["w$"]]],
]
// We query the top and bottom line positions again despite these being on the context
// as the values from the `BufferUpdate` event can be incorrect
const [[topLine, bottomLine]] = await this._neovimInstance.request<[[number, number], any]>(
"nvim_call_atomic",
[positionCalls],
)
const atomicCalls = [
["nvim_win_get_position", [currentWinId]],
["nvim_win_get_width", [currentWinId]],
["nvim_win_get_height", [currentWinId]],
[
"nvim_buf_get_lines",
[context.bufferNumber, context.windowTopLine - 1, context.windowBottomLine, false],
],
["nvim_buf_get_lines", [context.bufferNumber, topLine - 1, bottomLine, false]],
]
const response = await this._neovimInstance.request("nvim_call_atomic", [atomicCalls])
const response = await this._neovimInstance.request<
Array<[WindowPosition, Width, Height, Lines]>
>("nvim_call_atomic", [atomicCalls])
if (!response) {
return null
}
const values = response[0]
const [values] = response
if (values.length === 4) {
// Grab the results of the `nvim_atomic_call`, as they are returned in an array
const position = values[0]
const [position, width, height, lines] = values
const [row, col] = position
const width = values[1]
const height = values[2]
const lines = values[3]
// The 'gutterOffset' (difference between `wincol` and `column`) is the size of the gutter
// (for example, line numbers). The buffer isn't in that space, so we need to account
@ -270,10 +294,8 @@ export class NeovimWindowManager extends Utility.Disposable {
if (values.length === 3) {
// Grab the results of the `nvim_atomic_call`, as they are returned in an array
const position = values[0]
const [position, width, height] = values
const [row, col] = position
const width = values[1]
const height = values[2]
const dimensions = {
x: col,

View File

@ -0,0 +1,208 @@
import * as assert from "assert"
import * as path from "path"
import * as sinon from "sinon"
import { Event } from "oni-types"
import { ErrorCodes } from "vscode-jsonrpc/lib/messages"
import { Definition } from "../../../src/Editor/NeovimEditor/Definition"
import { Symbols } from "../../../src/Editor/NeovimEditor/Symbols"
import { wrapPathInFileUri } from "../../../src/Plugins/Api/LanguageClient/LanguageClientHelpers"
import { LanguageManager } from "../../../src/Services/Language"
import { Menu, MenuManager } from "../../../src/Services/Menu"
/* tslint:disable:no-string-literal */
const clock: any = global["clock"] // tslint:disable-line
const waitForPromiseResolution: any = global["waitForPromiseResolution"] // tslint:disable-line
describe("Symbols", () => {
let editor: any
let definition: any
let languageManager: any
let menuManager: any
let symbols: any
beforeEach(() => {
editor = sinon.stub()
editor.activeBuffer = "mock buffer"
definition = sinon.createStubInstance(Definition)
languageManager = sinon.createStubInstance(LanguageManager)
menuManager = sinon.createStubInstance(MenuManager)
symbols = new Symbols(editor, definition, languageManager, menuManager)
})
describe("open workspace/document menus", () => {
let menu: any
let onFilterTextChanged: Event<string>
let onItemSelected: Event<string>
beforeEach(() => {
menu = sinon.createStubInstance(Menu)
onFilterTextChanged = new Event<string>()
onItemSelected = new Event<string>()
sinon.stub(menu, "onItemSelected").get(() => onItemSelected)
sinon.stub(menu, "onFilterTextChanged").get(() => onFilterTextChanged)
menuManager.create.returns(menu)
symbols["_requestSymbols"] = sinon.stub().resolves(["first symbol", "second symbol"])
const _symbolInfoToMenuItem = sinon.stub()
_symbolInfoToMenuItem.onCall(0).returns("first transformed")
_symbolInfoToMenuItem.onCall(1).returns("second transformed")
symbols["_symbolInfoToMenuItem"] = _symbolInfoToMenuItem
})
describe("openWorkspaceSymbolsMenu", () => {
let getKey: any
beforeEach(() => {
getKey = sinon.stub()
symbols["_getDetailFromSymbol"] = sinon.stub().returns(getKey)
})
it("requests workspace symbols when filter text is changed", async () => {
// setup
symbols.openWorkspaceSymbolsMenu()
clock.tick(30)
sinon.assert.notCalled(symbols["_requestSymbols"])
// action
onFilterTextChanged.dispatch("mock query")
// confirm
clock.tick(24)
sinon.assert.notCalled(symbols["_requestSymbols"])
clock.tick(1)
sinon.assert.calledWithExactly(
symbols["_requestSymbols"],
"mock buffer",
"workspace/symbol",
menu,
{ query: "mock query" },
)
await waitForPromiseResolution()
assertCommon()
})
})
describe("openDocumentSymbolsMenu", () => {
it("requests document symbols when completion menu is opened", async () => {
// action
await symbols.openDocumentSymbolsMenu()
// confirm
sinon.assert.calledWithExactly(
symbols["_requestSymbols"],
"mock buffer",
"textDocument/documentSymbol",
menu,
)
assertCommon()
})
})
const assertCommon = () => {
assert.deepEqual(symbols["_symbolInfoToMenuItem"].args, [
["first symbol"],
["second symbol"],
])
sinon.assert.calledWithExactly(menu.setItems.lastCall, [
"first transformed",
"second transformed",
])
}
}) // End describe open menus
describe("_requestSymbols", () => {
let menu: any
let buffer: any
beforeEach(() => {
menu = sinon.createStubInstance(Menu)
menu.isOpen.returns(true)
buffer = sinon.stub()
buffer.language = "mocklang"
buffer.filePath = path.join("mock", "path")
})
it("throws on unknown errors", async () => {
// setup
const error = new Error()
languageManager.sendLanguageServerRequest.throws(error)
try {
// action
await symbols["_requestSymbols"](buffer, "mock command", menu)
// confirm
assert.fail("Expected exception to be thrown")
} catch (e) {
assert.strictEqual(e, error)
}
})
it("retries whilst server is initialising", async () => {
// setup
const error: any = new Error()
error.code = ErrorCodes.ServerNotInitialized
languageManager.sendLanguageServerRequest.onCall(0).throws(error)
languageManager.sendLanguageServerRequest.onCall(1).throws(error)
languageManager.sendLanguageServerRequest.onCall(2).returns("mock result")
// action
const request: Promise<any> = symbols["_requestSymbols"](buffer, "mock command", menu, {
mock: "option",
})
// confirm
sinon.assert.callCount(languageManager.sendLanguageServerRequest, 1)
clock.tick(999)
await waitForPromiseResolution()
sinon.assert.callCount(languageManager.sendLanguageServerRequest, 1)
clock.tick(1)
await waitForPromiseResolution()
sinon.assert.callCount(languageManager.sendLanguageServerRequest, 2)
clock.tick(1000)
await waitForPromiseResolution()
sinon.assert.callCount(languageManager.sendLanguageServerRequest, 3)
clock.tick(1000)
await waitForPromiseResolution()
sinon.assert.callCount(languageManager.sendLanguageServerRequest, 3)
clock.tick(1000)
await waitForPromiseResolution()
sinon.assert.alwaysCalledWith(
languageManager.sendLanguageServerRequest,
"mocklang",
buffer.filePath,
"mock command",
{ mock: "option", textDocument: { uri: wrapPathInFileUri(buffer.filePath) } },
)
assert.equal(await request, "mock result")
})
it("gives up retrying if menu is closed", async () => {
// setup
const error: any = new Error()
error.code = ErrorCodes.ServerNotInitialized
languageManager.sendLanguageServerRequest.throws(error)
// action
const request: Promise<any> = symbols["_requestSymbols"](buffer, "mock command", menu)
// confirm
sinon.assert.callCount(languageManager.sendLanguageServerRequest, 1)
clock.tick(1000)
await waitForPromiseResolution()
sinon.assert.callCount(languageManager.sendLanguageServerRequest, 2)
menu.isOpen.returns(false)
clock.tick(1000)
await waitForPromiseResolution()
sinon.assert.callCount(languageManager.sendLanguageServerRequest, 2)
const result = await request
assert.deepEqual(result, [])
})
})
})

View File

@ -16,6 +16,12 @@ export class MockNeovimInstance {
private _requests: NeovimRequest[] = []
private _pendingPromises: Array<Utility.ICompletablePromise<any>> = []
public get onColorsChanged() {
return {
subscribe: (fn: (args?: any) => any) => fn(),
}
}
public request(requestName: string, args: any[]) {
this._requests.push({ requestName, args })
const promise = Utility.createCompletablePromise()

View File

@ -0,0 +1,60 @@
import * as assert from "assert"
import { CompletionItem } from "vscode-languageserver-types"
import { filterCompletionOptions } from "../../../src/Services/Completion/CompletionSelectors"
describe("filterCompletionOptions", () => {
it("strips duplicates and sorts in order of abbreviation=>label=>sortText", () => {
const item1: CompletionItem = {
label: "mock duplicate",
detail: "mock detail",
sortText: "c",
}
const item2: CompletionItem = {
label: "mock duplicate",
detail: "mock detail",
sortText: "b",
}
const item3: CompletionItem = {
label: "mock duplicate",
detail: "mock not duplicate detail",
sortText: "a",
}
const item4: CompletionItem = {
label: "maaaaoaaaacaaaak abbreviation",
}
const item5: CompletionItem = {
label: "mock cherry",
filterText: "mock cherry",
}
const item6: CompletionItem = {
label: "mock cherry",
filterText: "mock banana",
}
const item7: CompletionItem = {
label: "mock apple",
}
const item8: CompletionItem = {
label: "doesnt match",
}
const item9: CompletionItem = {
label: "mock apple",
filterText: "doesnt match either",
}
const items: CompletionItem[] = [
item1,
item2,
item3,
item4,
item5,
item6,
item7,
item8,
item9,
]
const filteredItems = filterCompletionOptions(items, "mock")
assert.deepStrictEqual(filteredItems, [item7, item6, item5, item3, item2, item4])
})
})

View File

@ -0,0 +1,66 @@
import * as assert from "assert"
import * as _ from "lodash"
import { DefaultCompletionResults } from "./../../../src/Services/Completion/CompletionState"
import { completionResultsReducer } from "./../../../src/Services/Completion/CompletionStore"
describe("completionResultsReducer", () => {
let oldState: any
beforeEach(() => {
oldState = _.cloneDeep(DefaultCompletionResults)
})
describe("GET_COMPLETION_ITEM_DETAILS_RESULT", () => {
beforeEach(() => {
oldState.completions = [
{ label: "other" },
{ label: "matchme", detail: "matchme(signature)" },
{ label: "matchme", detail: "other(signature)" },
{ label: "matchme" },
]
})
it("updates relevant items with detailed version by label field", () => {
const completionItemWithDetails = {
label: "matchme",
}
const newState = completionResultsReducer(oldState, {
type: "GET_COMPLETION_ITEM_DETAILS_RESULT",
completionItemWithDetails,
})
assert.deepStrictEqual(newState, {
...oldState,
completions: [
{ label: "other" },
completionItemWithDetails,
completionItemWithDetails,
completionItemWithDetails,
],
})
})
it("updates relevant items with detailed version by detail field", () => {
const completionItemWithDetails = {
label: "matchme",
detail: "matchme(signature)",
}
const newState = completionResultsReducer(oldState, {
type: "GET_COMPLETION_ITEM_DETAILS_RESULT",
completionItemWithDetails,
})
assert.deepStrictEqual(newState, {
...oldState,
completions: [
{ label: "other" },
completionItemWithDetails,
{ label: "matchme", detail: "other(signature)" },
completionItemWithDetails,
],
})
})
})
})

View File

@ -0,0 +1,60 @@
import * as assert from "assert"
import * as sinon from "sinon"
import { LanguageServiceCompletionsRequestor } from "./../../../src/Services/Completion/CompletionsRequestor"
import { LanguageManager } from "./../../../src/Services/Language"
describe("LanguageServiceCompletionsRequestor", () => {
describe("getCompletionDetails", () => {
let completionItem: any
let completionDetails: any
let languageManager: any
let requestor: any
beforeEach(() => {
completionItem = sinon.stub()
completionDetails = sinon.stub()
languageManager = sinon.createStubInstance(LanguageManager)
languageManager.sendLanguageServerRequest.resolves(completionDetails)
requestor = new LanguageServiceCompletionsRequestor(languageManager)
})
it("doesn't send the request if the server is incapable", async () => {
languageManager.getCapabilitiesForLanguage.resolves({
completionProvider: { resolveProvider: false },
})
const returnedDetails = await requestor.getCompletionDetails(
"mocklang",
"mockpath",
completionItem as any,
)
sinon.assert.calledWithExactly(languageManager.getCapabilitiesForLanguage, "mocklang")
sinon.assert.notCalled(languageManager.sendLanguageServerRequest)
assert.strictEqual(returnedDetails, completionItem)
})
it("requests completion details if server is capable", async () => {
languageManager.getCapabilitiesForLanguage.resolves({
completionProvider: { resolveProvider: true },
})
const returnedDetails = await requestor.getCompletionDetails(
"mocklang",
"mockpath",
completionItem as any,
)
sinon.assert.calledWithExactly(languageManager.getCapabilitiesForLanguage, "mocklang")
sinon.assert.calledWithExactly(
languageManager.sendLanguageServerRequest,
"mocklang",
"mockpath",
"completionItem/resolve",
completionItem,
)
assert.strictEqual(returnedDetails, completionDetails)
})
})
})

View File

@ -0,0 +1,48 @@
import * as assert from "assert"
import * as sinon from "sinon"
import { createReducer } from "../../../src/Services/Menu/MenuReducer"
import { createDefaultState } from "../../../src/Services/Menu/MenuState"
describe("MenuReducer", () => {
let reducer: any
let oldState: any
beforeEach(() => {
reducer = createReducer<any, any>()
oldState = createDefaultState<any, any>()
oldState.menu = {}
})
describe("popupMenuReducer", () => {
describe("FILTER_MENU", () => {
let filteredOptions: any
let filterFunction: any
beforeEach(() => {
filteredOptions = {}
filterFunction = sinon.stub().returns(filteredOptions)
oldState.menu.filterFunction = filterFunction
})
it("resets selectedIndex to zero", () => {
oldState.menu.selectedIndex = 123
const newState = reducer(oldState, {
type: "FILTER_MENU",
payload: { filter: "mock filter" },
})
assert.deepStrictEqual(newState, {
...oldState,
menu: {
filter: "mock filter",
filterFunction,
filteredOptions,
selectedIndex: 0,
},
})
})
})
})
})

View File

@ -25,7 +25,7 @@ describe("SyntaxHighlightReconciler", () => {
mockTokenColors = new Mocks.MockTokenColors([
{
scope: "scope.test",
scope: ["scope.test"],
settings: {
background: COLOR_BLACK,
foreground: COLOR_WHITE,
@ -90,7 +90,7 @@ describe("SyntaxHighlightReconciler", () => {
{
range: types.Range.create(0, 0, 0, 5),
tokenColor: {
scope: "scope.test",
scope: ["scope.test"],
settings: {
background: COLOR_BLACK,
foreground: COLOR_WHITE,
@ -151,7 +151,7 @@ describe("SyntaxHighlightReconciler", () => {
{
range: types.Range.create(0, 0, 0, 5),
tokenColor: {
scope: "scope.test",
scope: ["scope.test"],
settings: {
background: COLOR_BLACK,
foreground: COLOR_WHITE,

View File

@ -9,7 +9,7 @@ import { TokenColor } from "./../../src/Services/TokenColors"
import { MockNeovimInstance } from "./../Mocks/neovim"
const createTokenColor = (scope: string): TokenColor => ({
const createTokenColor = (...scope: string[]): TokenColor => ({
scope,
settings: {
foreground: "#FFFFFF",

View File

@ -343,13 +343,18 @@ export class QuickOpen {
private getDefaultOpenMode(): Oni.FileOpenMode {
const legacy = this._oni.configuration.getValue("editor.quickOpen.defaultOpenMode", null)
if (legacy) {
// the value of the defaultOpenMode is a numerical enum that includes 0 so we check that the value
// is a number and that number is an option in the file open mode enum
if (!isNaN(legacy) && legacy in Oni.FileOpenMode) {
return legacy
}
return this._oni.configuration.getValue(
const defaultOpenMode = this._oni.configuration.getValue(
"quickOpen.defaultOpenMode",
Oni.FileOpenMode.NewTab,
)
return defaultOpenMode
}
private isInstallDirectoryOrHome() {

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"author": "",
"email": "bryphe@outlook.com",
"homepage": "https://www.onivim.io",
"version": "0.3.7",
"version": "0.3.8",
"description": "Code editor with a modern twist on modal editing - powered by neovim.",
"keywords": [
"vim",
@ -873,7 +873,7 @@
"msgpack-lite": "0.1.26",
"ocaml-language-server": "^1.0.27",
"oni-api": "0.0.49",
"oni-fontkit": "^0.0.4",
"oni-fontkit": "^0.0.5",
"oni-neovim-binaries": "0.1.3",
"oni-ripgrep": "0.0.4",
"oni-types": "^0.0.8",
@ -882,16 +882,16 @@
"react-dnd-html5-backend": "^2.5.4",
"react-dom": "^16.3.2",
"redux-batched-subscribe": "^0.1.6",
"shell-env": "^0.3.0",
"shell-env": "^2.1.0",
"shelljs": "0.7.7",
"simple-git": "^1.92.0",
"styled-components": "^3.4.4",
"typescript": "^2.8.1",
"typescript": "^3.1.1",
"vscode-css-languageserver-bin": "^1.2.1",
"vscode-html-languageserver-bin": "^1.1.0",
"vscode-jsonrpc": "3.5.0",
"vscode-languageserver": "3.5.0",
"vscode-languageserver-types": "3.5.0",
"vscode-languageserver-types": "3.13.0",
"vscode-textmate": "3.2.0"
},
"devDependencies": {
@ -928,7 +928,7 @@
"@types/redux-mock-store": "^1.0.0",
"@types/rimraf": "^2.0.2",
"@types/shelljs": "^0.7.7",
"@types/sinon": "1.16.32",
"@types/sinon": "5.0.5",
"@types/webgl2": "^0.0.3",
"autoprefixer": "6.4.0",
"aws-sdk": "^2.202.0",
@ -944,7 +944,7 @@
"detect-indent": "^5.0.0",
"electron": "2.0.8",
"electron-builder": "^20.5.1",
"electron-devtools-installer": "^2.2.3",
"electron-devtools-installer": "^2.2.4",
"electron-mocha": "5.0.0",
"electron-rebuild": "^1.7.3",
"enzyme": "^3.3.0",
@ -992,13 +992,13 @@
"remap-istanbul": "^0.10.1",
"reselect": "3.0.1",
"rxjs": "5.5.8",
"sinon": "1.17.6",
"sinon": "5.0.5",
"spectron": "^3.8.0",
"style-loader": "0.18.2",
"sudo-prompt": "7.1.1",
"ts-jest": "^23.0.0",
"ts-loader": "^4.2.0",
"tslint": "5.9.1",
"tslint": "^5.11.0",
"typescript-plugin-styled-components": "^0.0.6",
"vscode-snippet-parser": "0.0.5",
"wcwidth": "1.0.1",

View File

@ -18,7 +18,7 @@ export const test = async (oni: Oni.Plugin.Api) => {
oni.automation.sendKeys("window.a")
// Wait for completion popup to show
await oni.automation.waitFor(() => getCompletionElement() !== null)
await oni.automation.waitFor(() => getCompletionElement() !== null, 120000)
// Check for 'alert' as an available completion
const completionElement = getCompletionElement()

View File

@ -44,7 +44,7 @@ export const test = async (oni: Oni.Plugin.Api) => {
const hasCompletionElement = () =>
getCompletionElement() && getCompletionElement().textContent.indexOf("automation") >= 0
await oni.automation.waitFor(hasCompletionElement)
await oni.automation.waitFor(hasCompletionElement, 120000)
assert.ok(hasCompletionElement(), "Got completion element!")
}

View File

@ -29,7 +29,7 @@ export const test = async (oni: Oni.Plugin.Api) => {
errors[filePath]["language-typescript"] &&
errors[filePath]["language-typescript"].length === 3
)
})
}, 120000)
// nextError jumps to 1st error
oni.commands.executeCommand("oni.editor.nextError")

View File

@ -29,7 +29,7 @@ export const test = async (oni: Oni.Plugin.Api) => {
types.Position.create(0, 0),
)
return tokens && tokens.scopes && tokens.scopes.length > 0
}, 10000)
}, 120000)
oni.automation.sendKeys("w")
@ -37,7 +37,7 @@ export const test = async (oni: Oni.Plugin.Api) => {
await oni.automation.waitFor(() => {
element = getElementByClassName("quick-info-debug-scopes")
return !!element
})
}, 120000)
await oni.automation.waitFor(() => {
const items = element.getElementsByTagName("li")

View File

@ -25,6 +25,7 @@
"check-format"
],
"whitespace": false,
"no-trailing-whitespace": [true, "ignore-template-strings"],
"trailing-comma": [
true,
{

View File

@ -93,7 +93,7 @@ declare var require: any
const tagsToCollect = ["a", "input", "textarea"]
tagsToCollect.forEach(tag => {
const elems = document.getElementsByTagName(tag) as NodeListOf<HTMLElement>
const elems = document.getElementsByTagName(tag) as HTMLCollectionOf<HTMLElement>
for (let i = 0; i < elems.length; i++) {
addElement(elems[i])

217
yarn.lock
View File

@ -50,6 +50,24 @@
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
"@sinonjs/formatio@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.0.0.tgz#9d282d81030a03a03fa0c5ce31fd8786a4da311a"
dependencies:
"@sinonjs/samsam" "2.1.0"
"@sinonjs/formatio@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2"
dependencies:
samsam "1.3.0"
"@sinonjs/samsam@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.1.0.tgz#b8b8f5b819605bd63601a6ede459156880f38ea3"
dependencies:
array-from "^2.1.1"
"@types/cheerio@*":
version "0.22.7"
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.7.tgz#4a92eafedfb2b9f4437d3a4410006d81114c66ce"
@ -269,9 +287,9 @@
"@types/glob" "*"
"@types/node" "*"
"@types/sinon@1.16.32":
version "1.16.32"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-1.16.32.tgz#b3246844902d7cb33f376b369b176a65a144af7e"
"@types/sinon@5.0.5":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.5.tgz#de600fa07eb1ec9d5f55669d5bac46a75fc88115"
"@types/tough-cookie@*":
version "2.3.2"
@ -630,6 +648,10 @@ array-flatten@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
array-from@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
array-includes@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
@ -2304,8 +2326,8 @@ class-utils@^0.3.5:
static-extend "^0.1.1"
classnames@^2.2.3, classnames@^2.2.5:
version "2.2.5"
resolved "https://codeload.github.com/JedWatson/classnames/tar.gz/34a05a53d31d35879ec7088949ae5a1b2224043a"
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
clean-css@4.1.x:
version "4.1.11"
@ -2816,7 +2838,7 @@ cross-spawn@^3.0.1:
lru-cache "^4.0.1"
which "^1.2.9"
cross-spawn@^4, cross-spawn@^4.0.0:
cross-spawn@^4:
version "4.0.2"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
dependencies:
@ -2831,7 +2853,7 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^6.0.4, cross-spawn@^6.0.5:
cross-spawn@^6.0.0, cross-spawn@^6.0.4, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
dependencies:
@ -3133,7 +3155,7 @@ default-require-extensions@^2.0.0:
dependencies:
strip-bom "^3.0.0"
default-shell@^1.0.0:
default-shell@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/default-shell/-/default-shell-1.0.1.tgz#752304bddc6174f49eb29cb988feea0b8813c8bc"
@ -3249,14 +3271,14 @@ diff@3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
diff@^3.1.0, diff@^3.3.1, diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
diff@^3.2.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
diff@^3.3.1, diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
diffie-hellman@^5.0.0:
version "5.0.2"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
@ -3923,18 +3945,6 @@ exec-sh@^0.2.0:
dependencies:
merge "^1.1.3"
execa@^0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36"
dependencies:
cross-spawn "^4.0.0"
get-stream "^2.2.0"
is-stream "^1.1.0"
npm-run-path "^2.0.0"
p-finally "^1.0.0"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
execa@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
@ -3959,6 +3969,18 @@ execa@^0.8.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
execa@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
dependencies:
cross-spawn "^6.0.0"
get-stream "^4.0.0"
is-stream "^1.1.0"
npm-run-path "^2.0.0"
p-finally "^1.0.0"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
exit-hook@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
@ -4368,12 +4390,6 @@ form-data@~2.3.1:
combined-stream "^1.0.5"
mime-types "^2.1.12"
formatio@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9"
dependencies:
samsam "~1.1"
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@ -4539,12 +4555,11 @@ get-stream@3.0.0, get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
get-stream@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
dependencies:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
pump "^3.0.0"
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
@ -6466,6 +6481,10 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
just-extend@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-3.0.0.tgz#cee004031eaabf6406da03a7b84e4fe9d78ef288"
keyboard-layout@^2.0.13:
version "2.0.13"
resolved "https://registry.yarnpkg.com/keyboard-layout/-/keyboard-layout-2.0.13.tgz#5b4f5c25835e5d221a7b9da897663100d897487d"
@ -6812,14 +6831,14 @@ lokijs@1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.2.tgz#75d43df21232f1d5479d191a69b6ebf61754a873"
lolex@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31"
lolex@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.1.tgz#3d2319894471ea0950ef64692ead2a5318cff362"
lolex@^2.2.0, lolex@^2.3.2:
version "2.7.5"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.5.tgz#113001d56bfc7e02d56e36291cc5c413d1aa0733"
longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
@ -7315,6 +7334,16 @@ nice-try@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
nise@^1.2.0:
version "1.4.5"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.5.tgz#979a97a19c48d627bb53703726ae8d53ce8d4b3e"
dependencies:
"@sinonjs/formatio" "3.0.0"
just-extend "^3.0.0"
lolex "^2.3.2"
path-to-regexp "^1.7.0"
text-encoding "^0.6.4"
no-case@^2.2.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
@ -7725,9 +7754,9 @@ oni-core-logging@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/oni-core-logging/-/oni-core-logging-1.0.0.tgz#7ad6c0ad8b06c23255202f97e229c2b0947dcf0b"
oni-fontkit@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/oni-fontkit/-/oni-fontkit-0.0.4.tgz#4bc91a2c9802c0910fcd93b409ba56e45fc202cf"
oni-fontkit@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/oni-fontkit/-/oni-fontkit-0.0.5.tgz#06b9400d549de3395bb7d88266b4703c09ba407f"
dependencies:
babel-runtime "^6.11.6"
brotli "^1.2.0"
@ -8079,6 +8108,12 @@ path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
path-to-regexp@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
dependencies:
isarray "0.0.1"
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@ -8609,6 +8644,13 @@ pump@^2.0.0, pump@^2.0.1:
end-of-stream "^1.1.0"
once "^1.3.1"
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
pumpify@^1.3.3:
version "1.4.0"
resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb"
@ -9445,13 +9487,9 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
samsam@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
samsam@~1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621"
samsam@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
sane@^2.0.0:
version "2.4.1"
@ -9632,13 +9670,13 @@ shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
shell-env@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/shell-env/-/shell-env-0.3.0.tgz#2250339022989165bda4eb7bf383afeaaa92dc34"
shell-env@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/shell-env/-/shell-env-2.1.0.tgz#11c472dd59c9ebe3fdeca115bd98ce0c9cbbcc7d"
dependencies:
default-shell "^1.0.0"
execa "^0.5.0"
strip-ansi "^3.0.0"
default-shell "^1.0.1"
execa "^1.0.0"
strip-ansi "^4.0.0"
shell-quote@^1.6.1:
version "1.6.1"
@ -9691,14 +9729,17 @@ single-line-log@^1.1.2:
dependencies:
string-width "^1.0.1"
sinon@1.17.6:
version "1.17.6"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.6.tgz#a43116db59577c8296356afee13fafc2332e58e1"
sinon@5.0.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.0.5.tgz#d89609395962322ac2fee7f9286abaa3b8fe6957"
dependencies:
formatio "1.1.1"
lolex "1.3.2"
samsam "1.1.2"
util ">=0.10.3 <1"
"@sinonjs/formatio" "^2.0.0"
diff "^3.1.0"
lodash.get "^4.4.2"
lolex "^2.2.0"
nise "^1.2.0"
supports-color "^5.1.0"
type-detect "^4.0.5"
sisteransi@^0.1.1:
version "0.1.1"
@ -10379,6 +10420,10 @@ test-exclude@^4.2.1:
read-pkg-up "^1.0.1"
require-main-filename "^1.0.1"
text-encoding@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -10547,9 +10592,9 @@ tslib@^1.8.0, tslib@^1.8.1:
version "1.9.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
tslint@5.9.1:
version "5.9.1"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae"
tslint@^5.11.0:
version "5.11.0"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed"
dependencies:
babel-code-frame "^6.22.0"
builtin-modules "^1.1.1"
@ -10562,11 +10607,11 @@ tslint@5.9.1:
resolve "^1.3.2"
semver "^5.3.0"
tslib "^1.8.0"
tsutils "^2.12.1"
tsutils "^2.27.2"
tsutils@^2.12.1:
version "2.19.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.19.1.tgz#76d7ebdea9d7a7bf4a05f50ead3701b0168708d7"
tsutils@^2.27.2:
version "2.29.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
dependencies:
tslib "^1.8.1"
@ -10590,6 +10635,10 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
type-is@~1.6.15:
version "1.6.15"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
@ -10614,9 +10663,9 @@ typescript-plugin-styled-components@^0.0.6:
dependencies:
ts-is-kind "^1.0.0"
typescript@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624"
typescript@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.1.tgz#3362ba9dd1e482ebb2355b02dfe8bcd19a2c7c96"
ua-parser-js@^0.7.9:
version "0.7.17"
@ -10851,7 +10900,7 @@ util.promisify@^1.0.0:
define-properties "^1.1.2"
object.getownpropertydescriptors "^2.0.3"
util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3:
util@0.10.3, util@^0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
dependencies:
@ -10989,17 +11038,35 @@ vscode-languageserver-protocol@3.5.0, vscode-languageserver-protocol@^3.5.0:
vscode-jsonrpc "^3.5.0"
vscode-languageserver-types "^3.5.0"
vscode-languageserver-types@3.5.0, vscode-languageserver-types@^3.5.0:
vscode-languageserver-protocol@3.5.1:
version "3.5.1"
resolved "http://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
dependencies:
vscode-jsonrpc "3.5.0"
vscode-languageserver-types "3.5.0"
vscode-languageserver-types@3.13.0, vscode-languageserver-types@^3.5.0:
version "3.13.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.13.0.tgz#b704b024cef059f7b326611c99b9c8753c0a18b4"
vscode-languageserver-types@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
vscode-languageserver@3.5.0, vscode-languageserver@^3.5.0:
vscode-languageserver@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-3.5.0.tgz#d28099bc6ddda8c1dd16b707e454e1b1ddae0dba"
dependencies:
vscode-languageserver-protocol "^3.5.0"
vscode-uri "^1.0.1"
vscode-languageserver@^3.5.0:
version "3.5.1"
resolved "http://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz#e0044b7df4d2447ce12632dfc98f1ab0afacbdff"
dependencies:
vscode-languageserver-protocol "3.5.1"
vscode-uri "^1.0.1"
vscode-nls@^2.0.1, vscode-nls@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da"