From 72405778dae6d71c37c145b25478298ac35fcf67 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Wed, 15 Aug 2018 11:51:16 +0100 Subject: [PATCH 01/17] add initial implementation of import cost layer --- browser/src/Plugins/Api/Oni.ts | 8 ++ .../oni-plugin-import-cost/package.json | 26 +++++ .../oni-plugin-import-cost/src/index.tsx | 101 ++++++++++++++++++ .../oni-plugin-import-cost/tsconfig.json | 16 +++ package.json | 6 ++ 5 files changed, 157 insertions(+) create mode 100644 extensions/oni-plugin-import-cost/package.json create mode 100644 extensions/oni-plugin-import-cost/src/index.tsx create mode 100644 extensions/oni-plugin-import-cost/tsconfig.json diff --git a/browser/src/Plugins/Api/Oni.ts b/browser/src/Plugins/Api/Oni.ts index 8f43d40583..d4c1924570 100644 --- a/browser/src/Plugins/Api/Oni.ts +++ b/browser/src/Plugins/Api/Oni.ts @@ -22,6 +22,10 @@ import { commandManager } from "./../../Services/CommandManager" import { getInstance as getCompletionProvidersInstance } from "./../../Services/Completion/CompletionProviders" import { configuration } from "./../../Services/Configuration" import { getInstance as getDiagnosticsInstance } from "./../../Services/Diagnostics" +import { + default as getBufferLayerInstance, + BufferLayerManager, +} from "./../../Editor/NeovimEditor/BufferLayerManager" import { editorManager } from "./../../Services/EditorManager" import { inputManager } from "./../../Services/InputManager" import * as LanguageManager from "./../../Services/Language" @@ -140,6 +144,10 @@ export class Oni implements OniApi.Plugin.Api { return getOverlayInstance() } + public get bufferLayers(): BufferLayerManager { + return getBufferLayerInstance() + } + public get process(): OniApi.Process { return Process } diff --git a/extensions/oni-plugin-import-cost/package.json b/extensions/oni-plugin-import-cost/package.json new file mode 100644 index 0000000000..6ce5ea7518 --- /dev/null +++ b/extensions/oni-plugin-import-cost/package.json @@ -0,0 +1,26 @@ +{ + "name": "oni-plugin-import-cost", + "version": "1.0.0", + "description": "An import cost buffer layer for Oni", + "main": "lib/index.js", + "author": "A. Sowemimo", + "license": "MIT", + "engines": { + "oni": "^0.2.6" + }, + "scripts": { + "build": "rimraf lib && tsc", + "test": "tsc -p tsconfig.test.json && mocha --recursive ./lib_test/test" + }, + "dependencies": { + "import-cost": "^1.5.0", + "oni-api": "^0.0.46", + "oni-types": "^0.0.8", + "react": "^16.4.2", + "styled-components": "^3.4.2" + }, + "devDependencies": { + "rimraf": "^2.6.2", + "typescript": "^2.9.2" + } +} diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx new file mode 100644 index 0000000000..121553d99b --- /dev/null +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -0,0 +1,101 @@ +import * as Oni from "oni-api" +import * as path from "path" +import * as React from "react" +import { importCost, cleanup, JAVASCRIPT, TYPESCRIPT } from "import-cost" + +interface Props { + buffer: Oni.Buffer + context: Oni.BufferLayerRenderContext +} + +interface State { + status: "error" | "calculating" | "done" | null + packages: string[] +} + +class ImportCosts extends React.Component { + emitter: any + state = { + status: null, + packages: [], + } + + async componentDidMount() { + const { filePath } = this.props.buffer + const contentsArray = await this.props.buffer.getLines(0) + const fileContents = contentsArray.join("\n") + const filetype = path.extname(filePath).includes(".ts") ? TYPESCRIPT : JAVASCRIPT + this.setupEmitter(filePath, filetype, fileContents) + } + + componentWillUnmount() { + this.emitter.removeAllListeners() + } + + setupEmitter(filePath: string, filetype: string, fileContents: string) { + this.emitter = importCost(filePath, fileContents, filetype) + this.emitter.on("error", e => this.setState({ status: "error" })) + this.emitter.on("start", packages => this.setState({ status: "calculating" })) + this.emitter.on("calculated", pkg => + this.setState({ packages: [...this.state.packages, pkg] }), + ) + this.emitter.on("done", packages => this.setState({ packages, status: "done" })) + } + + getPosition = (line: number) => { + const lineContent = this.props.context.visibleLines[line - 1] + const character = lineContent.length || 0 + console.log("line: ", line) + console.log("lineContent: ", lineContent) + console.log("character: ", character) + const pos = this.props.context.bufferToPixel({ character, line: line + 1 }) + return pos ? pos.pixelX : null + } + + render() { + const { status, packages } = this.state + switch (status) { + case "calculating": + return
calculating...
+ case "done": + return ( +
+ {packages.map(pkg => ( + + {pkg.name} {pkg.gzip} {pkg.line} + + ))} +
+ ) + default: + return null + } + } +} + +export class ImportCostLayer implements Oni.BufferLayer { + constructor(private _oni: Oni.Plugin.Api, private _buffer) {} + get id() { + return "import-costs" + } + + render(context: Oni.BufferLayerRenderContext) { + return + } +} + +export interface OniWithLayers extends Oni.Plugin.Api { + bufferLayers: any +} + +export const activate = async (oni: OniWithLayers) => { + const isCompatible = (buf: Oni.Buffer) => { + const ext = path.extname(buf.filePath) + return ext.includes(".ts") || ext.includes(".js") + } + oni.bufferLayers.addBufferLayer(isCompatible, buf => { + return new ImportCostLayer(oni, buf) + }) +} diff --git a/extensions/oni-plugin-import-cost/tsconfig.json b/extensions/oni-plugin-import-cost/tsconfig.json new file mode 100644 index 0000000000..c71a5d2969 --- /dev/null +++ b/extensions/oni-plugin-import-cost/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "commonjs", + "moduleResolution": "node", + "preserveConstEnums": true, + "outDir": "./lib", + "jsx": "react", + "lib": ["dom", "es2017"], + "declaration": true, + "sourceMap": true, + "target": "es2015", + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/package.json b/package.json index 508efe83a4..c7d442118e 100644 --- a/package.json +++ b/package.json @@ -600,6 +600,8 @@ "build:cli": "cd cli && tsc -p tsconfig.json", "build:plugin:oni-plugin-typescript": "cd vim/core/oni-plugin-typescript && yarn run build", "build:plugin:oni-plugin-git": "cd vim/core/oni-plugin-git && yarn run build", + "build:plugin:oni-plugin-import-cost": + "cd extensions/oni-plugin-import-cost && yarn run build", "build:plugin:oni-plugin-markdown-preview": "cd extensions/oni-plugin-markdown-preview && yarn run build", "build:plugin:oni-plugin-quickopen": "cd extensions/oni-plugin-quickopen && yarn run build", @@ -674,12 +676,16 @@ "webpack-dev-server --config browser/webpack.development.config.js --host localhost --port 8191", "watch:plugins": "run-p watch:plugins:*", "watch:plugins:oni-plugin-typescript": "cd vim/core/oni-plugin-typescript && tsc --watch", + "watch:plugins:oni-plugin-import-cost": + "cd extensions/oni-plugin-import-cost && tsc --watch", "watch:plugins:oni-plugin-markdown-preview": "cd extensions/oni-plugin-markdown-preview && tsc --watch", "watch:plugins:oni-plugin-git": "cd vim/core/oni-plugin-git && tsc --watch", "install:plugins": "run-s install:plugins:*", "install:plugins:oni-plugin-markdown-preview": "cd extensions/oni-plugin-markdown-preview && yarn install --prod", + "install:plugins:oni-plugin-import-cost": + "cd extensions/oni-plugin-import-cost && yarn install --prod", "install:plugins:oni-plugin-prettier": "cd extensions/oni-plugin-prettier && yarn install --prod", "install:plugins:oni-plugin-git": "cd vim/core/oni-plugin-git && yarn install --prod", From d23e7325ba1a66b5ab3294b1917865bddb9fcec6 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Wed, 15 Aug 2018 13:05:14 +0100 Subject: [PATCH 02/17] add styled components and fix rendering --- .../oni-plugin-import-cost/package.json | 6 +- .../oni-plugin-import-cost/src/index.tsx | 96 ++++++++++++++----- 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/extensions/oni-plugin-import-cost/package.json b/extensions/oni-plugin-import-cost/package.json index 6ce5ea7518..357c9af4f3 100644 --- a/extensions/oni-plugin-import-cost/package.json +++ b/extensions/oni-plugin-import-cost/package.json @@ -16,11 +16,13 @@ "import-cost": "^1.5.0", "oni-api": "^0.0.46", "oni-types": "^0.0.8", - "react": "^16.4.2", - "styled-components": "^3.4.2" + "react": "^16.4.2" }, "devDependencies": { "rimraf": "^2.6.2", "typescript": "^2.9.2" + }, + "peerDependencies": { + "styled-components": "^3.2.6" } } diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index 121553d99b..25217c388b 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -1,8 +1,50 @@ import * as Oni from "oni-api" import * as path from "path" import * as React from "react" +import styled, { ThemedStyledFunction } from "styled-components" import { importCost, cleanup, JAVASCRIPT, TYPESCRIPT } from "import-cost" +interface PackageProps { + left: number + top: number + hide: boolean +} + +interface IPackage { + name: string + size: number + gzip: number + line: number +} + +export type StyledFunction = ThemedStyledFunction + +export function withProps( + styledFunction: StyledFunction>, +): StyledFunction> { + return styledFunction +} + +const Gzip = styled.span` + color: green; +` + +const Package = withProps(styled.div).attrs({ + style: (props: PackageProps) => ({ + left: `${props.left}px`, + top: `${props.top}px`, + visibility: props.hide ? "hidden" : "visible", + }), +})` + color: white; + padding-left: 5px; + position: absolute; +` + +const Packages = styled.div` + position: relative; +` + interface Props { buffer: Oni.Buffer context: Oni.BufferLayerRenderContext @@ -20,20 +62,21 @@ class ImportCosts extends React.Component { packages: [], } - async componentDidMount() { - const { filePath } = this.props.buffer - const contentsArray = await this.props.buffer.getLines(0) - const fileContents = contentsArray.join("\n") - const filetype = path.extname(filePath).includes(".ts") ? TYPESCRIPT : JAVASCRIPT - this.setupEmitter(filePath, filetype, fileContents) + componentDidMount() { + this.setupEmitter() } componentWillUnmount() { this.emitter.removeAllListeners() } - setupEmitter(filePath: string, filetype: string, fileContents: string) { - this.emitter = importCost(filePath, fileContents, filetype) + setupEmitter() { + const { filePath } = this.props.buffer + const { visibleLines } = this.props.context + const fileContents = visibleLines.join("\n") + const fileType = path.extname(filePath).includes(".ts") ? TYPESCRIPT : JAVASCRIPT + + this.emitter = importCost(filePath, fileContents, fileType) this.emitter.on("error", e => this.setState({ status: "error" })) this.emitter.on("start", packages => this.setState({ status: "calculating" })) this.emitter.on("calculated", pkg => @@ -45,29 +88,29 @@ class ImportCosts extends React.Component { getPosition = (line: number) => { const lineContent = this.props.context.visibleLines[line - 1] const character = lineContent.length || 0 - console.log("line: ", line) - console.log("lineContent: ", lineContent) - console.log("character: ", character) - const pos = this.props.context.bufferToPixel({ character, line: line + 1 }) - return pos ? pos.pixelX : null + const pos = this.props.context.bufferToPixel({ character, line: line - 1 }) + return pos + ? { left: pos.pixelX, top: pos.pixelY, hide: false } + : { left: null, top: null, hide: true } } + getSize = (num: number) => Math.round(num / 1000 * 10) / 10 + render() { const { status, packages } = this.state switch (status) { case "calculating": - return
calculating...
+ return calculating... case "done": return ( -
+ {packages.map(pkg => ( - - {pkg.name} {pkg.gzip} {pkg.line} - + + {this.getSize(pkg.size)}kb{" "} + (gzipped: {this.getSize(pkg.gzip)}kb) + ))} -
+ ) default: return null @@ -87,7 +130,12 @@ export class ImportCostLayer implements Oni.BufferLayer { } export interface OniWithLayers extends Oni.Plugin.Api { - bufferLayers: any + bufferLayers: { + addBufferLayer: ( + compat: (buf: Oni.Buffer) => boolean, + layer: (buf: Oni.Buffer) => Oni.BufferLayer, + ) => void + } } export const activate = async (oni: OniWithLayers) => { @@ -95,7 +143,5 @@ export const activate = async (oni: OniWithLayers) => { const ext = path.extname(buf.filePath) return ext.includes(".ts") || ext.includes(".js") } - oni.bufferLayers.addBufferLayer(isCompatible, buf => { - return new ImportCostLayer(oni, buf) - }) + oni.bufferLayers.addBufferLayer(isCompatible, buf => new ImportCostLayer(oni, buf)) } From df808e05e800c56fa8f68282f08a1802b6df18a1 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Wed, 15 Aug 2018 13:45:17 +0100 Subject: [PATCH 03/17] add width to packages - for text overflow --- .../oni-plugin-import-cost/src/index.tsx | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index 25217c388b..e755bbb15a 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -7,6 +7,7 @@ import { importCost, cleanup, JAVASCRIPT, TYPESCRIPT } from "import-cost" interface PackageProps { left: number top: number + width: number hide: boolean } @@ -17,6 +18,8 @@ interface IPackage { line: number } +const px = (s: string | number) => `${s}px` + export type StyledFunction = ThemedStyledFunction export function withProps( @@ -31,14 +34,18 @@ const Gzip = styled.span` const Package = withProps(styled.div).attrs({ style: (props: PackageProps) => ({ - left: `${props.left}px`, - top: `${props.top}px`, + left: px(props.left), + top: px(props.top), + width: px(props.width), visibility: props.hide ? "hidden" : "visible", }), })` color: white; padding-left: 5px; position: absolute; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; ` const Packages = styled.div` @@ -66,6 +73,13 @@ class ImportCosts extends React.Component { this.setupEmitter() } + componentDidUpdate({ context: { visibleLines: prevLines } }: Props) { + const { context } = this.props + if (context.visibleLines !== prevLines) { + this.setupEmitter() + } + } + componentWillUnmount() { this.emitter.removeAllListeners() } @@ -86,12 +100,15 @@ class ImportCosts extends React.Component { } getPosition = (line: number) => { - const lineContent = this.props.context.visibleLines[line - 1] + const { context } = this.props + const width = context.dimensions.width * context.fontPixelWidth + const lineContent = context.visibleLines[line - 1] const character = lineContent.length || 0 const pos = this.props.context.bufferToPixel({ character, line: line - 1 }) + return pos - ? { left: pos.pixelX, top: pos.pixelY, hide: false } - : { left: null, top: null, hide: true } + ? { left: pos.pixelX, top: pos.pixelY, width: width - pos.pixelX, hide: false } + : { left: null, top: null, width: null, hide: true } } getSize = (num: number) => Math.round(num / 1000 * 10) / 10 @@ -104,12 +121,15 @@ class ImportCosts extends React.Component { case "done": return ( - {packages.map(pkg => ( - - {this.getSize(pkg.size)}kb{" "} - (gzipped: {this.getSize(pkg.gzip)}kb) - - ))} + {packages.map(pkg => { + const position = this.getPosition(pkg.line) + return ( + + {this.getSize(pkg.size)}kb{" "} + (gzipped: {this.getSize(pkg.gzip)}kb) + + ) + })} ) default: From bff02d8fc2b1924c05fc24fb3b8420967e105837 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Wed, 15 Aug 2018 16:29:30 +0100 Subject: [PATCH 04/17] tidy up color rendering for package sizes --- .../oni-plugin-import-cost/src/index.tsx | 80 +++++++++++++++---- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index e755bbb15a..0d7580c06a 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -4,7 +4,7 @@ import * as React from "react" import styled, { ThemedStyledFunction } from "styled-components" import { importCost, cleanup, JAVASCRIPT, TYPESCRIPT } from "import-cost" -interface PackageProps { +interface PackageProps extends SizeProps { left: number top: number width: number @@ -18,6 +18,10 @@ interface IPackage { line: number } +interface SizeProps { + size: "small" | "medium" | "large" +} + const px = (s: string | number) => `${s}px` export type StyledFunction = ThemedStyledFunction @@ -28,8 +32,21 @@ export function withProps( return styledFunction } -const Gzip = styled.span` - color: green; +const getColorForSize = (size: SizeProps["size"]) => { + switch (size) { + case "small": + return "green" + case "medium": + return "yellow" + case "large": + return "red" + default: + return "white" + } +} + +const Gzip = styled("span")` + color: ${p => getColorForSize(p.size)}; ` const Package = withProps(styled.div).attrs({ @@ -40,7 +57,7 @@ const Package = withProps(styled.div).attrs({ visibility: props.hide ? "hidden" : "visible", }), })` - color: white; + color: ${p => getColorForSize(p.size)}; padding-left: 5px; position: absolute; white-space: nowrap; @@ -54,17 +71,19 @@ const Packages = styled.div` interface Props { buffer: Oni.Buffer + largeSize: number + smallSize: number context: Oni.BufferLayerRenderContext } interface State { status: "error" | "calculating" | "done" | null - packages: string[] + packages: IPackage[] } class ImportCosts extends React.Component { emitter: any - state = { + state: State = { status: null, packages: [], } @@ -91,12 +110,18 @@ class ImportCosts extends React.Component { const fileType = path.extname(filePath).includes(".ts") ? TYPESCRIPT : JAVASCRIPT this.emitter = importCost(filePath, fileContents, fileType) - this.emitter.on("error", e => this.setState({ status: "error" })) + this.emitter.on("error", error => { + console.warn("Oni import-cost error:", error) + this.setState({ status: "error" }) + }) this.emitter.on("start", packages => this.setState({ status: "calculating" })) this.emitter.on("calculated", pkg => - this.setState({ packages: [...this.state.packages, pkg] }), + this.setState({ packages: [...this.state.packages, pkg], status: "done" }), ) - this.emitter.on("done", packages => this.setState({ packages, status: "done" })) + this.emitter.on("done", packages => { + this.setState({ packages, status: "done" }) + cleanup() + }) } getPosition = (line: number) => { @@ -111,7 +136,19 @@ class ImportCosts extends React.Component { : { left: null, top: null, width: null, hide: true } } - getSize = (num: number) => Math.round(num / 1000 * 10) / 10 + getSize = (num: number): { kb: number; size: SizeProps["size"] } => { + const sizeInKbs = Math.round(num / 1024 * 10) / 10 + const sizeDescription = + sizeInKbs >= this.props.largeSize + ? "large" + : this.props.smallSize >= sizeInKbs + ? "small" + : "medium" + return { + kb: sizeInKbs, + size: sizeDescription, + } + } render() { const { status, packages } = this.state @@ -123,12 +160,19 @@ class ImportCosts extends React.Component { {packages.map(pkg => { const position = this.getPosition(pkg.line) - return ( - - {this.getSize(pkg.size)}kb{" "} - (gzipped: {this.getSize(pkg.gzip)}kb) + const gzipSize = this.getSize(pkg.gzip) + const pkgSize = this.getSize(pkg.size) + return pkg.size ? ( + + {pkgSize.kb}kb + (gzipped: {gzipSize.kb}kb) - ) + ) : null })} ) @@ -144,8 +188,12 @@ export class ImportCostLayer implements Oni.BufferLayer { return "import-costs" } + get friendlyName() { + return "Package sizes buffer layer" + } + render(context: Oni.BufferLayerRenderContext) { - return + return } } From d38671d5e38891da85fc195c8c631ecbd2ba026a Mon Sep 17 00:00:00 2001 From: Akin909 Date: Wed, 15 Aug 2018 23:04:54 +0100 Subject: [PATCH 05/17] pass theme colors to component also render status per package --- .../oni-plugin-import-cost/src/index.tsx | 73 ++++++++++++++----- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index 0d7580c06a..223d9bdb8f 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -9,13 +9,17 @@ interface PackageProps extends SizeProps { top: number width: number hide: boolean + background: string } +type Status = "calculating" | "done" | null | "error" + interface IPackage { name: string size: number gzip: number line: number + status: Status } interface SizeProps { @@ -58,11 +62,13 @@ const Package = withProps(styled.div).attrs({ }), })` color: ${p => getColorForSize(p.size)}; + background-color: ${p => p.background}; padding-left: 5px; position: absolute; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; + z-index: 3; ` const Packages = styled.div` @@ -74,10 +80,13 @@ interface Props { largeSize: number smallSize: number context: Oni.BufferLayerRenderContext + colors: { + [color: string]: string + } } interface State { - status: "error" | "calculating" | "done" | null + status: Status packages: IPackage[] } @@ -114,12 +123,20 @@ class ImportCosts extends React.Component { console.warn("Oni import-cost error:", error) this.setState({ status: "error" }) }) - this.emitter.on("start", packages => this.setState({ status: "calculating" })) - this.emitter.on("calculated", pkg => - this.setState({ packages: [...this.state.packages, pkg], status: "done" }), + this.emitter.on("start", packages => + this.setState({ packages: packages.map(pkg => ({ ...pkg, status: "calculating" })) }), ) + this.emitter.on("calculated", pkg => { + const packages = this.state.packages.map( + p => (pkg.line === p.line ? { ...pkg, status: "done" } : p), + ) + this.setState({ packages }) + }) this.emitter.on("done", packages => { - this.setState({ packages, status: "done" }) + const updated = this.state.packages.map( + pkg => (pkg.status !== "done" ? { ...pkg, status: null } : pkg), + ) + this.setState({ packages: updated }) cleanup() }) } @@ -130,9 +147,15 @@ class ImportCosts extends React.Component { const lineContent = context.visibleLines[line - 1] const character = lineContent.length || 0 const pos = this.props.context.bufferToPixel({ character, line: line - 1 }) + const PADDING = 5 return pos - ? { left: pos.pixelX, top: pos.pixelY, width: width - pos.pixelX, hide: false } + ? { + left: pos.pixelX, + top: pos.pixelY, + width: width - pos.pixelX - PADDING, + hide: false, + } : { left: null, top: null, width: null, hide: true } } @@ -152,13 +175,13 @@ class ImportCosts extends React.Component { render() { const { status, packages } = this.state - switch (status) { - case "calculating": - return calculating... - case "done": - return ( - - {packages.map(pkg => { + return ( + + {packages.map(pkg => { + switch (pkg.status) { + case "calculating": + return calculating... + case "done": const position = this.getPosition(pkg.line) const gzipSize = this.getSize(pkg.gzip) const pkgSize = this.getSize(pkg.size) @@ -168,17 +191,18 @@ class ImportCosts extends React.Component { key={pkg.line} size={pkgSize.size} data-id="import-cost" + background={this.props.colors["editor.background"]} > {pkgSize.kb}kb (gzipped: {gzipSize.kb}kb) ) : null - })} - - ) - default: - return null - } + default: + return null + } + })} + + ) } } @@ -193,7 +217,16 @@ export class ImportCostLayer implements Oni.BufferLayer { } render(context: Oni.BufferLayerRenderContext) { - return + const colors = (this._oni.colors as any).getColors() + return ( + + ) } } From 6eb1d78e8dea52d44487385594e8f34697dcfa52 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Wed, 15 Aug 2018 23:19:58 +0100 Subject: [PATCH 06/17] cover blame if overlapping, pass required props to package components --- .../oni-plugin-import-cost/src/index.tsx | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index 223d9bdb8f..6c1b2fcda5 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -10,6 +10,7 @@ interface PackageProps extends SizeProps { width: number hide: boolean background: string + height: number } type Status = "calculating" | "done" | null | "error" @@ -62,6 +63,7 @@ const Package = withProps(styled.div).attrs({ }), })` color: ${p => getColorForSize(p.size)}; + height: ${p => px(p.height)}; background-color: ${p => p.background}; padding-left: 5px; position: absolute; @@ -160,6 +162,12 @@ class ImportCosts extends React.Component { } getSize = (num: number): { kb: number; size: SizeProps["size"] } => { + if (!num) { + return { + kb: null, + size: null, + } + } const sizeInKbs = Math.round(num / 1024 * 10) / 10 const sizeDescription = sizeInKbs >= this.props.largeSize @@ -175,12 +183,25 @@ class ImportCosts extends React.Component { render() { const { status, packages } = this.state + const { + context: { fontPixelHeight }, + } = this.props return ( {packages.map(pkg => { switch (pkg.status) { case "calculating": - return calculating... + return ( + + calculating... + + ) case "done": const position = this.getPosition(pkg.line) const gzipSize = this.getSize(pkg.gzip) @@ -189,6 +210,7 @@ class ImportCosts extends React.Component { Date: Thu, 16 Aug 2018 13:56:45 +0100 Subject: [PATCH 07/17] improve typing, using status enum --- .../oni-plugin-import-cost/src/index.tsx | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index 6c1b2fcda5..f009d88683 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -13,7 +13,11 @@ interface PackageProps extends SizeProps { height: number } -type Status = "calculating" | "done" | null | "error" +enum Status { + calculating = "calculating", + done = "done", + error = "error", +} interface IPackage { name: string @@ -93,7 +97,7 @@ interface State { } class ImportCosts extends React.Component { - emitter: any + emitter: NodeJS.EventEmitter state: State = { status: null, packages: [], @@ -123,23 +127,25 @@ class ImportCosts extends React.Component { this.emitter = importCost(filePath, fileContents, fileType) this.emitter.on("error", error => { console.warn("Oni import-cost error:", error) - this.setState({ status: "error" }) + this.setState({ status: Status.error }) }) - this.emitter.on("start", packages => - this.setState({ packages: packages.map(pkg => ({ ...pkg, status: "calculating" })) }), + this.emitter.on("start", (packages: IPackage[]) => + this.setState({ + packages: packages.map(pkg => ({ ...pkg, status: Status.calculating })), + }), ) - this.emitter.on("calculated", pkg => { + this.emitter.on("calculated", (pkg: IPackage) => { const packages = this.state.packages.map( - p => (pkg.line === p.line ? { ...pkg, status: "done" } : p), + loaded => (pkg.line === loaded.line ? { ...pkg, status: Status.done } : loaded), ) this.setState({ packages }) }) - this.emitter.on("done", packages => { + this.emitter.on("done", (packages: IPackage[]) => { const updated = this.state.packages.map( - pkg => (pkg.status !== "done" ? { ...pkg, status: null } : pkg), + pkg => (pkg.status !== Status.done ? { ...pkg, status: null } : pkg), ) - this.setState({ packages: updated }) cleanup() + this.setState({ packages: updated }) }) } @@ -183,21 +189,22 @@ class ImportCosts extends React.Component { render() { const { status, packages } = this.state - const { - context: { fontPixelHeight }, - } = this.props + const { colors, context } = this.props + const background = colors["editor.background"] + const height = context.fontPixelHeight return ( {packages.map(pkg => { switch (pkg.status) { case "calculating": + const linePos = this.getPosition(pkg.line) return ( calculating... @@ -210,10 +217,10 @@ class ImportCosts extends React.Component { {pkgSize.kb}kb (gzipped: {gzipSize.kb}kb) From b3f32dbc2616e693f4e4b9a50a4486602093be40 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Thu, 16 Aug 2018 17:17:55 +0100 Subject: [PATCH 08/17] fix type errors and simplify Size type --- .../oni-plugin-import-cost/src/index.tsx | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index f009d88683..c4979f2746 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -4,19 +4,22 @@ import * as React from "react" import styled, { ThemedStyledFunction } from "styled-components" import { importCost, cleanup, JAVASCRIPT, TYPESCRIPT } from "import-cost" -interface PackageProps extends SizeProps { +enum Status { + calculating = "calculating", + done = "done", + error = "error", +} + +type Size = "small" | "medium" | "large" + +interface PackageProps { left: number top: number width: number hide: boolean background: string height: number -} - -enum Status { - calculating = "calculating", - done = "done", - error = "error", + packageSize?: Size } interface IPackage { @@ -28,7 +31,7 @@ interface IPackage { } interface SizeProps { - size: "small" | "medium" | "large" + size: Size } const px = (s: string | number) => `${s}px` @@ -41,7 +44,7 @@ export function withProps( return styledFunction } -const getColorForSize = (size: SizeProps["size"]) => { +const getColorForSize = (size: Size) => { switch (size) { case "small": return "green" @@ -58,15 +61,18 @@ const Gzip = styled("span")` color: ${p => getColorForSize(p.size)}; ` +const hidden: VisibilityState = "hidden" +const visible: VisibilityState = "visible" + const Package = withProps(styled.div).attrs({ style: (props: PackageProps) => ({ left: px(props.left), top: px(props.top), width: px(props.width), - visibility: props.hide ? "hidden" : "visible", + visibility: props.hide ? hidden : visible, }), })` - color: ${p => getColorForSize(p.size)}; + color: ${p => getColorForSize(p.packageSize)}; height: ${p => px(p.height)}; background-color: ${p => p.background}; padding-left: 5px; @@ -167,7 +173,7 @@ class ImportCosts extends React.Component { : { left: null, top: null, width: null, hide: true } } - getSize = (num: number): { kb: number; size: SizeProps["size"] } => { + getSize = (num: number): { kb: number; size: Size } => { if (!num) { return { kb: null, @@ -218,9 +224,9 @@ class ImportCosts extends React.Component { {...position} key={pkg.line} height={height} - size={pkgSize.size} - data-id="import-cost" background={background} + packageSize={pkgSize.size} + data-id="import-cost" > {pkgSize.kb}kb (gzipped: {gzipSize.kb}kb) From 848c8e4607b89071d2cf88ed3af272b792f653c4 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Fri, 17 Aug 2018 05:38:58 +0100 Subject: [PATCH 09/17] fix lint error and import default correctly for buffer layer manager --- browser/src/Plugins/Api/Oni.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/browser/src/Plugins/Api/Oni.ts b/browser/src/Plugins/Api/Oni.ts index d4c1924570..945e4bacda 100644 --- a/browser/src/Plugins/Api/Oni.ts +++ b/browser/src/Plugins/Api/Oni.ts @@ -16,16 +16,15 @@ import { Ui } from "./Ui" import { getInstance as getPluginsManagerInstance } from "./../PluginManager" +import getBufferLayerInstance, { + BufferLayerManager, +} from "./../../Editor/NeovimEditor/BufferLayerManager" import { automation } from "./../../Services/Automation" import { Colors, getInstance as getColors } from "./../../Services/Colors" import { commandManager } from "./../../Services/CommandManager" import { getInstance as getCompletionProvidersInstance } from "./../../Services/Completion/CompletionProviders" import { configuration } from "./../../Services/Configuration" import { getInstance as getDiagnosticsInstance } from "./../../Services/Diagnostics" -import { - default as getBufferLayerInstance, - BufferLayerManager, -} from "./../../Editor/NeovimEditor/BufferLayerManager" import { editorManager } from "./../../Services/EditorManager" import { inputManager } from "./../../Services/InputManager" import * as LanguageManager from "./../../Services/Language" From 5f440c12bb8d91ed5b8d450300af6902c8fac7dd Mon Sep 17 00:00:00 2001 From: Akin909 Date: Fri, 17 Aug 2018 22:05:29 +0100 Subject: [PATCH 10/17] refactor import cost to use user config and priorities add prioritisation of layers based on user config setting --- .../Configuration/DefaultConfiguration.ts | 1 + .../Configuration/IConfigurationValues.ts | 5 + .../VersionControlBlameLayer.tsx | 25 +- .../oni-plugin-import-cost/package.json | 3 +- .../oni-plugin-import-cost/src/index.tsx | 252 ++++++++++-------- 5 files changed, 176 insertions(+), 110 deletions(-) diff --git a/browser/src/Services/Configuration/DefaultConfiguration.ts b/browser/src/Services/Configuration/DefaultConfiguration.ts index 4384530edb..79b394f3d1 100644 --- a/browser/src/Services/Configuration/DefaultConfiguration.ts +++ b/browser/src/Services/Configuration/DefaultConfiguration.ts @@ -61,6 +61,7 @@ const BaseConfiguration: IConfigurationValues = { "experimental.vcs.blame.mode": "auto", "experimental.vcs.blame.timeout": 800, + "layers.priority": ["import-costs", "vcs.blame"], "experimental.colorHighlight.enabled": false, "experimental.colorHighlight.filetypes": [ ".css", diff --git a/browser/src/Services/Configuration/IConfigurationValues.ts b/browser/src/Services/Configuration/IConfigurationValues.ts index 354eb8b235..7699c411a4 100644 --- a/browser/src/Services/Configuration/IConfigurationValues.ts +++ b/browser/src/Services/Configuration/IConfigurationValues.ts @@ -46,6 +46,11 @@ export interface IConfigurationValues { // - textMateHighlighting "editor.textMateHighlighting.enabled": boolean + // A list of oni bufferlayer IDs, the ids order in this array determines their priority, + // the first element has the highest priority so if there is + // a clash the first buffer layer element will render + "layers.priority": string[] + // Whether or not the learning pane is available "experimental.particles.enabled": boolean diff --git a/browser/src/Services/VersionControl/VersionControlBlameLayer.tsx b/browser/src/Services/VersionControl/VersionControlBlameLayer.tsx index 0f21be600f..a288ee6241 100644 --- a/browser/src/Services/VersionControl/VersionControlBlameLayer.tsx +++ b/browser/src/Services/VersionControl/VersionControlBlameLayer.tsx @@ -17,6 +17,7 @@ interface IBlamePosition { top: number left: number hide: boolean + leftOffset: number } interface ICanFit { @@ -32,6 +33,7 @@ interface ILineDetails { export interface IProps extends LayerContextWithCursor { getBlame: (lineOne: number, lineTwo: number) => Promise + priority: number timeout: number cursorScreenLine: number cursorBufferLine: number @@ -55,7 +57,9 @@ interface IContainerProps { left: number fontFamily: string hide: boolean + priority: number timeout: number + leftOffset: number animationState: TransitionStates } @@ -69,9 +73,10 @@ const getOpacity = (state: TransitionStates) => { } export const BlameContainer = withProps(styled.div).attrs({ - style: ({ top, left }: IContainerProps) => ({ + style: ({ top, left, leftOffset }: IContainerProps) => ({ top: pixel(top), left: pixel(left), + paddingLeft: pixel(leftOffset), }), })` ${p => p.hide && `visibility: hidden`}; @@ -86,6 +91,7 @@ export const BlameContainer = withProps(styled.div).attrs({ height: ${p => pixel(p.height)}; line-height: ${p => pixel(p.height)}; right: 3em; + z-index: ${p => p.priority}; ${textOverflow} ` @@ -201,10 +207,10 @@ export class Blame extends React.PureComponent { public calculatePosition(canFit: boolean) { const { cursorLine, cursorScreenLine, visibleLines } = this.props const currentLine = visibleLines[cursorScreenLine] - const character = currentLine && currentLine.length + this.LEFT_OFFSET + const character = currentLine && currentLine.length if (canFit) { - return this.getPosition({ line: cursorLine, character }) + return this.getPosition({ line: cursorLine, character }, canFit) } const { lastEmptyLine, nextSpacing } = this.getLastEmptyLine() @@ -228,16 +234,19 @@ export class Blame extends React.PureComponent { return new Date(parseInt(timestamp, 10) * 1000) } - public getPosition(positionToRender?: Position): IBlamePosition { + public getPosition(positionToRender?: Position, canFit: boolean = false): IBlamePosition { const emptyPosition: IBlamePosition = { hide: true, top: null, left: null, + leftOffset: null, } if (!positionToRender) { return emptyPosition } + const position = this.props.bufferToPixel(positionToRender) + if (!position) { return emptyPosition } @@ -245,6 +254,7 @@ export class Blame extends React.PureComponent { hide: false, top: position.pixelY, left: position.pixelX, + leftOffset: canFit ? this.LEFT_OFFSET * this.props.fontPixelWidth : 0, } } @@ -309,6 +319,7 @@ export class Blame extends React.PureComponent { data-id="vcs.blame" timeout={this.DURATION} animationState={state} + priority={this.props.priority} height={this.props.fontPixelHeight} fontFamily={this.props.fontFamily} > @@ -354,8 +365,11 @@ export default class VersionControlBlameLayer implements BufferLayer { const fontFamily = this._configuration.getValue("editor.fontFamily") const timeout = this._configuration.getValue("experimental.vcs.blame.timeout") const mode = this._configuration.getValue<"auto" | "manual">("experimental.vcs.blame.mode") + const priorities = this._configuration.getValue("layers.priority", []) + const index = priorities.indexOf(this.id) + const priority = index >= 0 ? priorities.length - index : 0 - return { timeout, mode, fontFamily } + return { timeout, mode, fontFamily, priority } } public render(context: LayerContextWithCursor) { @@ -368,6 +382,7 @@ export default class VersionControlBlameLayer implements BufferLayer { `${s}px` -export type StyledFunction = ThemedStyledFunction - -export function withProps( - styledFunction: StyledFunction>, -): StyledFunction> { - return styledFunction -} - -const getColorForSize = (size: Size) => { - switch (size) { - case "small": - return "green" - case "medium": - return "yellow" - case "large": - return "red" - default: - return "white" - } -} - -const Gzip = styled("span")` - color: ${p => getColorForSize(p.size)}; +const Gzip = styled("span")` + color: ${p => p.color}; ` const hidden: VisibilityState = "hidden" const visible: VisibilityState = "visible" -const Package = withProps(styled.div).attrs({ - style: (props: PackageProps) => ({ +const Package = styled.div.attrs({ + style: (props: IPackageProps) => ({ left: px(props.left), top: px(props.top), width: px(props.width), visibility: props.hide ? hidden : visible, }), })` - color: ${p => getColorForSize(p.packageSize)}; + color: ${p => p.sizeColors[p.packageSize]}; height: ${p => px(p.height)}; background-color: ${p => p.background}; - padding-left: 5px; + line-height: ${p => px(p.height + 1)}; position: absolute; + padding-left: 5px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; - z-index: 3; + z-index: ${p => p.priority}; ` const Packages = styled.div` position: relative; ` -interface Props { +interface IPkgDetails { + kb: number + size: Size +} + +interface Props extends ImportSettings { buffer: Oni.Buffer - largeSize: number - smallSize: number + priority: number context: Oni.BufferLayerRenderContext + log: (...args: any[]) => void colors: { [color: string]: string } } interface State { - status: Status + error: string packages: IPackage[] } class ImportCosts extends React.Component { emitter: NodeJS.EventEmitter state: State = { - status: null, + error: null, packages: [], } @@ -113,8 +100,8 @@ class ImportCosts extends React.Component { this.setupEmitter() } - componentDidUpdate({ context: { visibleLines: prevLines } }: Props) { - const { context } = this.props + componentDidUpdate({ context: { visibleLines: prevLines } }) { + const { context, buffer } = this.props if (context.visibleLines !== prevLines) { this.setupEmitter() } @@ -124,6 +111,10 @@ class ImportCosts extends React.Component { this.emitter.removeAllListeners() } + componentDidCatch(error: Error) { + this.setState({ error: error.message }) + } + setupEmitter() { const { filePath } = this.props.buffer const { visibleLines } = this.props.context @@ -132,11 +123,12 @@ class ImportCosts extends React.Component { this.emitter = importCost(filePath, fileContents, fileType) this.emitter.on("error", error => { - console.warn("Oni import-cost error:", error) - this.setState({ status: Status.error }) + this.props.log("Oni import-cost error:", error) + this.setState({ error }) }) this.emitter.on("start", (packages: IPackage[]) => this.setState({ + error: null, packages: packages.map(pkg => ({ ...pkg, status: Status.calculating })), }), ) @@ -147,10 +139,10 @@ class ImportCosts extends React.Component { this.setState({ packages }) }) this.emitter.on("done", (packages: IPackage[]) => { - const updated = this.state.packages.map( - pkg => (pkg.status !== Status.done ? { ...pkg, status: null } : pkg), - ) - cleanup() + const updated = this.state.packages.map(pkg => ({ + ...pkg, + status: pkg.status === Status.done ? pkg.status : null, + })) this.setState({ packages: updated }) }) } @@ -163,21 +155,19 @@ class ImportCosts extends React.Component { const pos = this.props.context.bufferToPixel({ character, line: line - 1 }) const PADDING = 5 - return pos - ? { - left: pos.pixelX, - top: pos.pixelY, - width: width - pos.pixelX - PADDING, - hide: false, - } - : { left: null, top: null, width: null, hide: true } + return { + left: pos ? pos.pixelX : null, + top: pos ? pos.pixelY : null, + width: pos ? width - pos.pixelX - PADDING : null, + hide: !pos, + } } - getSize = (num: number): { kb: number; size: Size } => { + getSize = (num: number): IPkgDetails => { if (!num) { return { kb: null, - size: null, + size: "medium", } } const sizeInKbs = Math.round(num / 1024 * 10) / 10 @@ -193,56 +183,86 @@ class ImportCosts extends React.Component { } } + getSizeText = (gzip: IPkgDetails, pkg: IPkgDetails) => ( + <> + {pkg.kb}kb + (gzipped: {gzip.kb}kb) + + ) + render() { - const { status, packages } = this.state - const { colors, context } = this.props + const { packages, error } = this.state + const { colors, context, priority } = this.props const background = colors["editor.background"] const height = context.fontPixelHeight return ( - - {packages.map(pkg => { - switch (pkg.status) { - case "calculating": - const linePos = this.getPosition(pkg.line) - return ( - - calculating... - - ) - case "done": - const position = this.getPosition(pkg.line) - const gzipSize = this.getSize(pkg.gzip) - const pkgSize = this.getSize(pkg.size) - return pkg.size ? ( - - {pkgSize.kb}kb - (gzipped: {gzipSize.kb}kb) - - ) : null - default: - return null - } - })} - + !error && ( + + {packages.map(pkg => { + const position = this.getPosition(pkg.line) + const gzipSize = this.getSize(pkg.gzip) + const pkgSize = this.getSize(pkg.size) + return pkg.size && pkg.status ? ( + + {pkg.status === Status.calculating + ? "calculating..." + : this.getSizeText(gzipSize, pkgSize)} + + ) : null + })} + + ) ) } } +interface ImportSettings { + enabled: boolean + largeSize: number + smallSize: number + sizeColors: { + small: string + large: string + regular: string + } +} + export class ImportCostLayer implements Oni.BufferLayer { - constructor(private _oni: Oni.Plugin.Api, private _buffer) {} + private _config: ImportSettings + private defaultConfig = { + enabled: false, + largeSize: 50, + smallSize: 5, + sizeColors: { + small: "green", + large: "red", + medium: "yellow", + }, + } + + private readonly PLUGIN_NAME = "oni.plugins.importCost" + + constructor(private _oni: OniWithLayers, private _buffer) { + this._config = this._getConfig() + + this._oni.configuration.onConfigurationChanged.subscribe(configChanges => { + if (this.PLUGIN_NAME in configChanges) { + this._config = configChanges[this.PLUGIN_NAME] + if (!this._config.enabled) { + cleanup() + } + } + }) + } get id() { return "import-costs" } @@ -251,21 +271,45 @@ export class ImportCostLayer implements Oni.BufferLayer { return "Package sizes buffer layer" } + log = (...args) => { + this._oni.log.warn(...args) + } + + private _getConfig() { + const userConfig = this._oni.configuration.getValue(this.PLUGIN_NAME) + return { ...this.defaultConfig, ...userConfig } + } + + getPriority() { + const priorities = this._oni.configuration.getValue("layers.priority", []) + const index = priorities.indexOf(this.id) + return index >= 0 ? priorities.length - index : 0 + } + render(context: Oni.BufferLayerRenderContext) { - const colors = (this._oni.colors as any).getColors() + const colors = this._oni.colors.getColors() + const priority = this.getPriority() return ( - + this._config.enabled && ( + + ) ) } } +// TODO: Add to API export interface OniWithLayers extends Oni.Plugin.Api { + colors: { + getColor(color: string): string + getColors(): { [key: string]: string } + } bufferLayers: { addBufferLayer: ( compat: (buf: Oni.Buffer) => boolean, From d9b975661a61a23cc891cd6e3a8f6ebeda95ea31 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Fri, 17 Aug 2018 23:32:32 +0100 Subject: [PATCH 11/17] fix line positioning for mid file imports make calculation decoration toggleable --- .../oni-plugin-import-cost/src/index.tsx | 75 +++++++++++-------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index 2fc0348165..e3c24fa76a 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -21,7 +21,6 @@ interface IPackageProps { height: number priority: number packageSize?: Size - sizeColors: ImportSettings["sizeColors"] } interface IPackage { @@ -32,13 +31,13 @@ interface IPackage { status: Status } -interface IGzip { +interface ISizeDetail { color: string } const px = (s: string | number) => `${s}px` -const Gzip = styled("span")` +const SizeDetail = styled("span")` color: ${p => p.color}; ` @@ -53,7 +52,6 @@ const Package = styled.div.attrs({ visibility: props.hide ? hidden : visible, }), })` - color: ${p => p.sizeColors[p.packageSize]}; height: ${p => px(p.height)}; background-color: ${p => p.background}; line-height: ${p => px(p.height + 1)}; @@ -133,10 +131,14 @@ class ImportCosts extends React.Component { }), ) this.emitter.on("calculated", (pkg: IPackage) => { - const packages = this.state.packages.map( - loaded => (pkg.line === loaded.line ? { ...pkg, status: Status.done } : loaded), - ) - this.setState({ packages }) + // Ignore packages with no size values + if (pkg.size) { + const packages = this.state.packages.map( + loaded => (pkg.name === loaded.name ? { ...pkg, status: Status.done } : loaded), + ) + + this.setState({ packages }) + } }) this.emitter.on("done", (packages: IPackage[]) => { const updated = this.state.packages.map(pkg => ({ @@ -149,17 +151,20 @@ class ImportCosts extends React.Component { getPosition = (line: number) => { const { context } = this.props + const zeroBasedLine = line - 1 const width = context.dimensions.width * context.fontPixelWidth - const lineContent = context.visibleLines[line - 1] + const lineContent = context.visibleLines[zeroBasedLine] const character = lineContent.length || 0 - const pos = this.props.context.bufferToPixel({ character, line: line - 1 }) + // line is the non-zero indexed line, topBufferLine is also non-zero indexed + const bufferline = this.props.context.topBufferLine - 1 + zeroBasedLine + const position = this.props.context.bufferToPixel({ character, line: bufferline }) const PADDING = 5 return { - left: pos ? pos.pixelX : null, - top: pos ? pos.pixelY : null, - width: pos ? width - pos.pixelX - PADDING : null, - hide: !pos, + left: position ? position.pixelX : null, + top: position ? position.pixelY : null, + width: position ? width - position.pixelX - PADDING : null, + hide: !position, } } @@ -167,7 +172,7 @@ class ImportCosts extends React.Component { if (!num) { return { kb: null, - size: "medium", + size: null, } } const sizeInKbs = Math.round(num / 1024 * 10) / 10 @@ -183,12 +188,21 @@ class ImportCosts extends React.Component { } } - getSizeText = (gzip: IPkgDetails, pkg: IPkgDetails) => ( - <> - {pkg.kb}kb - (gzipped: {gzip.kb}kb) - - ) + getCalculation = () => { + return this.props.showCalculating ? ( + calculating... + ) : null + } + + getSizeText = (gzip: IPkgDetails, pkg: IPkgDetails) => { + const { sizeColors } = this.props + return ( + <> + {pkg.kb}kb + (gzipped: {gzip.kb}kb) + + ) + } render() { const { packages, error } = this.state @@ -198,23 +212,22 @@ class ImportCosts extends React.Component { return ( !error && ( - {packages.map(pkg => { + {packages.map((pkg, idx) => { const position = this.getPosition(pkg.line) const gzipSize = this.getSize(pkg.gzip) const pkgSize = this.getSize(pkg.size) - return pkg.size && pkg.status ? ( + return pkg.status ? ( {pkg.status === Status.calculating - ? "calculating..." + ? this.getCalculation() : this.getSizeText(gzipSize, pkgSize)} ) : null @@ -229,19 +242,21 @@ interface ImportSettings { enabled: boolean largeSize: number smallSize: number + showCalculating: boolean sizeColors: { small: string large: string - regular: string + medium: string } } export class ImportCostLayer implements Oni.BufferLayer { private _config: ImportSettings - private defaultConfig = { + private defaultConfig: ImportSettings = { enabled: false, largeSize: 50, smallSize: 5, + showCalculating: false, sizeColors: { small: "green", large: "red", @@ -271,7 +286,7 @@ export class ImportCostLayer implements Oni.BufferLayer { return "Package sizes buffer layer" } - log = (...args) => { + log = (...args: any[]) => { this._oni.log.warn(...args) } From faa061342db29f548e2f06241a170ba22361e943 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sat, 18 Aug 2018 07:04:29 +0100 Subject: [PATCH 12/17] fix blame layer tests --- ui-tests/VersionControlBlameLayer.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-tests/VersionControlBlameLayer.test.tsx b/ui-tests/VersionControlBlameLayer.test.tsx index 1fa626449a..5c0e1e7fec 100644 --- a/ui-tests/VersionControlBlameLayer.test.tsx +++ b/ui-tests/VersionControlBlameLayer.test.tsx @@ -119,12 +119,12 @@ describe("", () => { }) it("should correctly return a position if the component is able to fit", () => { const position = instance.calculatePosition(true) - expect(position).toEqual({ hide: false, top: 20, left: 20 }) + expect(position).toEqual({ hide: false, top: 20, left: 20, leftOffset: 12 }) }) it("should return a position even if can't fit BUT there is an available empty line", () => { const canFit = false const position = instance.calculatePosition(canFit) - expect(position).toEqual({ hide: false, top: 20, left: 20 }) + expect(position).toEqual({ hide: false, top: 20, left: 20, leftOffset: 0 }) }) it("Should correctly determine if a line is out of bounds", () => { const outOfBounds = instance.isOutOfBounds(50, 10) From 3517ee8f0c53b9533cce6eb79b2bdbc1ffe7d208 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sat, 18 Aug 2018 07:50:09 +0100 Subject: [PATCH 13/17] fix compatibility check in import cost layer move jest to peer dependencies --- .../Editor/NeovimEditor/BufferLayerManager.ts | 12 +++++------- extensions/oni-plugin-import-cost/package.json | 2 +- extensions/oni-plugin-import-cost/src/index.tsx | 16 +++++++++++----- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/browser/src/Editor/NeovimEditor/BufferLayerManager.ts b/browser/src/Editor/NeovimEditor/BufferLayerManager.ts index c3f504a996..65f28a9552 100644 --- a/browser/src/Editor/NeovimEditor/BufferLayerManager.ts +++ b/browser/src/Editor/NeovimEditor/BufferLayerManager.ts @@ -69,13 +69,6 @@ export class BufferLayerManager { } } -const getInstance = (() => { - const instance = new BufferLayerManager() - return () => instance -})() - -export default getInstance - export const wrapReactComponentWithLayer = ( id: string, component: JSX.Element, @@ -85,3 +78,8 @@ export const wrapReactComponentWithLayer = ( render: (context: Oni.BufferLayerRenderContext) => (context.isActive ? component : null), } } + +const instance = new BufferLayerManager() +const getInstance = () => instance + +export default getInstance diff --git a/extensions/oni-plugin-import-cost/package.json b/extensions/oni-plugin-import-cost/package.json index 4badad4885..fb6572f552 100644 --- a/extensions/oni-plugin-import-cost/package.json +++ b/extensions/oni-plugin-import-cost/package.json @@ -19,11 +19,11 @@ "react": "^16.4.2" }, "devDependencies": { - "jest": "^23.5.0", "rimraf": "^2.6.2", "typescript": "^2.9.2" }, "peerDependencies": { + "jest": "^23.5.0", "styled-components": "^3.2.6" } } diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index e3c24fa76a..1f33db7889 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -266,7 +266,7 @@ export class ImportCostLayer implements Oni.BufferLayer { private readonly PLUGIN_NAME = "oni.plugins.importCost" - constructor(private _oni: OniWithLayers, private _buffer) { + constructor(private _oni: OniWithLayers, private _buffer: Oni.Buffer) { this._config = this._getConfig() this._oni.configuration.onConfigurationChanged.subscribe(configChanges => { @@ -333,10 +333,16 @@ export interface OniWithLayers extends Oni.Plugin.Api { } } -export const activate = async (oni: OniWithLayers) => { - const isCompatible = (buf: Oni.Buffer) => { - const ext = path.extname(buf.filePath) - return ext.includes(".ts") || ext.includes(".js") +const isCompatible = (buf: Oni.Buffer | Oni.EditorBufferEventArgs) => { + const ext = path.extname(buf.filePath) + const allowedExtensions = [".js", ".jsx", ".ts", ".tsx"] + const isCompat = allowedExtensions.includes(ext) + if (!isCompat) { + cleanup() // kill worker processes if layer isn't to be rendered } + return isCompat +} + +export const activate = async (oni: OniWithLayers) => { oni.bufferLayers.addBufferLayer(isCompatible, buf => new ImportCostLayer(oni, buf)) } From e822556be6fa17b8e354ab32d309c8b416c0463e Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sat, 18 Aug 2018 12:25:11 +0100 Subject: [PATCH 14/17] remove unnecessary async from activate fn --- extensions/oni-plugin-import-cost/src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index 1f33db7889..2c4f0e2c7a 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -343,6 +343,6 @@ const isCompatible = (buf: Oni.Buffer | Oni.EditorBufferEventArgs) => { return isCompat } -export const activate = async (oni: OniWithLayers) => { +export const activate = (oni: OniWithLayers) => { oni.bufferLayers.addBufferLayer(isCompatible, buf => new ImportCostLayer(oni, buf)) } From 658f656192b1de957e9e76a8863bdd656a5aa3b7 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sat, 18 Aug 2018 18:46:43 +0100 Subject: [PATCH 15/17] remove additional bufferlayers from api and use buffer.addlayer fix typing to match this change --- browser/src/Plugins/Api/Oni.ts | 7 ---- .../oni-plugin-import-cost/src/index.tsx | 39 ++++++++----------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/browser/src/Plugins/Api/Oni.ts b/browser/src/Plugins/Api/Oni.ts index 945e4bacda..8f43d40583 100644 --- a/browser/src/Plugins/Api/Oni.ts +++ b/browser/src/Plugins/Api/Oni.ts @@ -16,9 +16,6 @@ import { Ui } from "./Ui" import { getInstance as getPluginsManagerInstance } from "./../PluginManager" -import getBufferLayerInstance, { - BufferLayerManager, -} from "./../../Editor/NeovimEditor/BufferLayerManager" import { automation } from "./../../Services/Automation" import { Colors, getInstance as getColors } from "./../../Services/Colors" import { commandManager } from "./../../Services/CommandManager" @@ -143,10 +140,6 @@ export class Oni implements OniApi.Plugin.Api { return getOverlayInstance() } - public get bufferLayers(): BufferLayerManager { - return getBufferLayerInstance() - } - public get process(): OniApi.Process { return Process } diff --git a/extensions/oni-plugin-import-cost/src/index.tsx b/extensions/oni-plugin-import-cost/src/index.tsx index 2c4f0e2c7a..149668afd2 100644 --- a/extensions/oni-plugin-import-cost/src/index.tsx +++ b/extensions/oni-plugin-import-cost/src/index.tsx @@ -48,7 +48,6 @@ const Package = styled.div.attrs({ style: (props: IPackageProps) => ({ left: px(props.left), top: px(props.top), - width: px(props.width), visibility: props.hide ? hidden : visible, }), })` @@ -73,13 +72,11 @@ interface IPkgDetails { } interface Props extends ImportSettings { - buffer: Oni.Buffer priority: number + buffer: Oni.EditorBufferEventArgs context: Oni.BufferLayerRenderContext + colors: { [color: string]: string } log: (...args: any[]) => void - colors: { - [color: string]: string - } } interface State { @@ -99,7 +96,7 @@ class ImportCosts extends React.Component { } componentDidUpdate({ context: { visibleLines: prevLines } }) { - const { context, buffer } = this.props + const { context } = this.props if (context.visibleLines !== prevLines) { this.setupEmitter() } @@ -266,7 +263,7 @@ export class ImportCostLayer implements Oni.BufferLayer { private readonly PLUGIN_NAME = "oni.plugins.importCost" - constructor(private _oni: OniWithLayers, private _buffer: Oni.Buffer) { + constructor(private _oni: OniWithColor, private _buffer: Oni.EditorBufferEventArgs) { this._config = this._getConfig() this._oni.configuration.onConfigurationChanged.subscribe(configChanges => { @@ -320,29 +317,27 @@ export class ImportCostLayer implements Oni.BufferLayer { } // TODO: Add to API -export interface OniWithLayers extends Oni.Plugin.Api { +export interface OniWithColor extends Oni.Plugin.Api { colors: { getColor(color: string): string getColors(): { [key: string]: string } } - bufferLayers: { - addBufferLayer: ( - compat: (buf: Oni.Buffer) => boolean, - layer: (buf: Oni.Buffer) => Oni.BufferLayer, - ) => void - } } -const isCompatible = (buf: Oni.Buffer | Oni.EditorBufferEventArgs) => { +const isCompatible = (buf: Oni.EditorBufferEventArgs) => { const ext = path.extname(buf.filePath) const allowedExtensions = [".js", ".jsx", ".ts", ".tsx"] - const isCompat = allowedExtensions.includes(ext) - if (!isCompat) { - cleanup() // kill worker processes if layer isn't to be rendered - } - return isCompat + return allowedExtensions.includes(ext) } -export const activate = (oni: OniWithLayers) => { - oni.bufferLayers.addBufferLayer(isCompatible, buf => new ImportCostLayer(oni, buf)) +export const activate = (oni: OniWithColor) => { + oni.editors.activeEditor.onBufferEnter.subscribe(buf => { + const layer = new ImportCostLayer(oni, buf) + if (isCompatible(buf)) { + oni.editors.activeEditor.activeBuffer.addLayer(layer) + } else { + cleanup() // kill worker processes if layer isn't to be rendered + oni.editors.activeEditor.activeBuffer.removeLayer(layer) + } + }) } From 8bbfe9281abac467c968b8fbb28da0cc66d562b9 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sun, 19 Aug 2018 14:24:53 +0100 Subject: [PATCH 16/17] increase default max listeners --- browser/src/App.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/browser/src/App.ts b/browser/src/App.ts index 7cc3ad5fe7..00750147a1 100644 --- a/browser/src/App.ts +++ b/browser/src/App.ts @@ -8,6 +8,7 @@ import { ipcRenderer, remote } from "electron" import * as fs from "fs" import * as minimist from "minimist" import * as path from "path" +import { EventEmitter } from "events" import { IDisposable } from "oni-types" @@ -17,6 +18,9 @@ import * as Utility from "./Utility" import { IConfigurationValues } from "./Services/Configuration/IConfigurationValues" +// Increase default max listeners +EventEmitter.defaultMaxListeners = 30 + const editorManagerPromise = import("./Services/EditorManager") const sharedNeovimInstancePromise = import("./neovim/SharedNeovimInstance") From e0f8225c8a6c9a1581076be882d17248fc72347e Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sun, 19 Aug 2018 14:27:44 +0100 Subject: [PATCH 17/17] fix lint error --- browser/src/App.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/src/App.ts b/browser/src/App.ts index 00750147a1..221a96f556 100644 --- a/browser/src/App.ts +++ b/browser/src/App.ts @@ -5,10 +5,10 @@ */ import { ipcRenderer, remote } from "electron" +import { EventEmitter } from "events" import * as fs from "fs" import * as minimist from "minimist" import * as path from "path" -import { EventEmitter } from "events" import { IDisposable } from "oni-types"