From c249290522445f5b2c5d523fe2612c8df8487712 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sat, 6 Oct 2018 21:24:50 +0100 Subject: [PATCH 1/4] add moize to memoize indent lines --- .../OniEditor/IndentGuideBufferLayer.tsx | 351 +++++++++--------- package.json | 1 + yarn.lock | 20 + 3 files changed, 200 insertions(+), 172 deletions(-) diff --git a/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx b/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx index 06f60e2957..f1af05a9f1 100644 --- a/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx +++ b/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx @@ -3,12 +3,14 @@ import * as React from "react" import * as detectIndent from "detect-indent" import * as flatten from "lodash/flatten" import * as last from "lodash/last" -import * as memoize from "lodash/memoize" import * as Oni from "oni-api" +import moize from "moize" import { IBuffer } from "../BufferManager" import styled, { pixel, withProps } from "./../../UI/components/common" +type IContext = Oni.BufferLayerRenderContext + interface IWrappedLine { start: number end: number @@ -54,200 +56,205 @@ const IndentLine = withProps(styled.span).attrs({ position: absolute; ` -interface IndentLayerArgs { - buffer: IBuffer - configuration: Oni.Configuration +interface IIndentsPerLine { + guidePositions: IndentLinesProps[] + options: ConfigOptions } -class IndentGuideBufferLayer implements Oni.BufferLayer { - public render = memoize((bufferLayerContext: Oni.BufferLayerRenderContext) => { - return {this._renderIndentLines(bufferLayerContext)} - }) - - private _buffer: IBuffer - private _userSpacing: number - private _configuration: Oni.Configuration - - constructor({ buffer, configuration }: IndentLayerArgs) { - this._buffer = buffer - this._configuration = configuration - this._userSpacing = this._buffer.shiftwidth || this._buffer.tabstop - } - get id() { - return "indent-guides" - } - - get friendlyName() { - return "Indent Guide Lines" - } - - private _getIndentLines = (guidePositions: IndentLinesProps[], options: ConfigOptions) => { - return flatten( - guidePositions.map((props, idx) => { - const indents: JSX.Element[] = [] - // Create a line per indentation - for ( - let levelOfIndentation = 0; - levelOfIndentation < props.indentBy; - levelOfIndentation++ - ) { - const lineProps = { ...props, levelOfIndentation } - const adjustedLeft = this._calculateLeftPosition(lineProps) - const shouldSkip = this._determineIfShouldSkip(lineProps, options) - const key = `${props.line.trim()}-${idx}-${levelOfIndentation}` - indents.push( - !shouldSkip && ( - - ), - ) - } - return indents - }), - ) - } - - private _determineIfShouldSkip(props: LinePropsWithLevels, options: ConfigOptions) { - const skipFirstIndentLine = - options.skipFirst && props.levelOfIndentation === props.indentBy - 1 +const IndentsPerLine: React.SFC = ({ guidePositions, options }) => { + const indents = guidePositions.map((props, idx) => + // Create a line per indentation + Array.from({ length: props.indentBy }, (_, levelOfIndentation) => { + const lineProps = { ...props, levelOfIndentation } + const adjustedLeft = calculateLeftPosition(lineProps) + const shouldSkip = determineIfShouldSkip(lineProps, options) + const key = `${props.line.trim()}-${idx}-${levelOfIndentation}` + return ( + !shouldSkip && ( + + ) + ) + }), + ) + return <>{flatten(indents)} +} - return skipFirstIndentLine - } +const MemoizedIndentsPerLine = moize.react(IndentsPerLine, { + isDeepEqual: true, +}) - /** - * Remove one indent from left positioning and move lines slightly inwards - - * by a third of a character for a better visual appearance - */ - private _calculateLeftPosition(props: LinePropsWithLevels) { - const adjustedLeft = - props.left - - props.indentSize - - props.levelOfIndentation * props.indentSize + - props.characterWidth / 3 - - return adjustedLeft - } +interface IGuideLines { + context: IContext + userSpacing: number + configuration: ConfigOptions +} - private _getWrappedLines(context: Oni.BufferLayerRenderContext): IWrappedLine[] { - const { lines } = context.visibleLines.reduce( - (acc, line, index) => { - const currentLine = context.topBufferLine + index - const bufferInfo = context.bufferToScreen({ line: currentLine, character: 0 }) - - if (bufferInfo && bufferInfo.screenY) { - const { screenY: screenLine } = bufferInfo - if (acc.expectedLine !== screenLine) { - acc.lines.push({ - start: acc.expectedLine, - end: screenLine, - line, - }) - acc.expectedLine = screenLine + 1 - } else { - acc.expectedLine += 1 - } - } +/** + * Calculates the position of each indent guide element using shiftwidth or tabstop if no + * shift width available + */ +const IndentGuideLines: React.SFC = ({ context, ...props }) => { + const wrappedLines = getWrappedLines(context) + const { visibleLines, fontPixelHeight, fontPixelWidth, topBufferLine } = context + const indentSize = props.userSpacing * fontPixelWidth + + // TODO: If the beginning of the visible lines is wrapping no lines are drawn + const { allIndentations } = visibleLines.reduce( + (acc, line, currentLineNumber) => { + const rawIndentation = detectIndent(line) + const regularisedIndent = regulariseIndentation(rawIndentation, props.userSpacing) + const previous = last(acc.allIndentations) + const height = Math.ceil(fontPixelHeight) + + // start position helps determine the initial indent offset + const startPosition = context.bufferToScreen({ + line: topBufferLine, + character: regularisedIndent, + }) + + const wrappedLine = wrappedLines.find(wrapped => wrapped.line === line) + const levelsOfWrapping = wrappedLine ? wrappedLine.end - wrappedLine.start : 1 + const adjustedHeight = height * levelsOfWrapping + + if (!startPosition) { return acc - }, - { lines: [], expectedLine: 1 }, - ) - return lines - } - - private _regulariseIndentation(indentation: detectIndent.IndentInfo) { - const isOddBy = indentation.amount % this._userSpacing - const amountToIndent = isOddBy ? indentation.amount - isOddBy : indentation.amount - return amountToIndent - } - - /** - * Calculates the position of each indent guide element using shiftwidth or tabstop if no - * shift width available - * @name _renderIndentLines - * @function - * @param {Oni.BufferLayerRenderContext} bufferLayerContext The buffer layer context - * @returns {JSX.Element[]} An array of react elements - */ - private _renderIndentLines = (bufferLayerContext: Oni.BufferLayerRenderContext) => { - // TODO: If the beginning of the visible lines is wrapping no lines are drawn - const wrappedScreenLines = this._getWrappedLines(bufferLayerContext) - - const options = { - color: this._configuration.getValue("experimental.indentLines.color"), - skipFirst: this._configuration.getValue("experimental.indentLines.skipFirst"), - } - - const { visibleLines, fontPixelHeight, fontPixelWidth, topBufferLine } = bufferLayerContext - const indentSize = this._userSpacing * fontPixelWidth - - const { allIndentations } = visibleLines.reduce( - (acc, line, currenLineNumber) => { - const rawIndentation = detectIndent(line) + } - const regularisedIndent = this._regulariseIndentation(rawIndentation) + const { pixelX: left, pixelY: top } = context.screenToPixel({ + screenX: startPosition.screenX, + screenY: currentLineNumber, + }) - const previous = last(acc.allIndentations) - const height = Math.ceil(fontPixelHeight) + const adjustedTop = top + acc.wrappedHeightAdjustment - // start position helps determine the initial indent offset - const startPosition = bufferLayerContext.bufferToScreen({ - line: topBufferLine, - character: regularisedIndent, - }) - - const wrappedLine = wrappedScreenLines.find(wrapped => wrapped.line === line) - const levelsOfWrapping = wrappedLine ? wrappedLine.end - wrappedLine.start : 1 - const adjustedHeight = height * levelsOfWrapping - - if (!startPosition) { - return acc - } + // Only adjust height for Subsequent lines! + if (wrappedLine) { + acc.wrappedHeightAdjustment += adjustedHeight + } - const { pixelX: left, pixelY: top } = bufferLayerContext.screenToPixel({ - screenX: startPosition.screenX, - screenY: currenLineNumber, + if (!line && previous) { + acc.allIndentations.push({ + ...previous, + line, + top: adjustedTop, }) + return acc + } + + const indent = { + left, + line, + indentSize, + top: adjustedTop, + height: adjustedHeight, + characterWidth: fontPixelWidth, + indentBy: regularisedIndent / props.userSpacing, + } + + acc.allIndentations.push(indent) + + return acc + }, + { allIndentations: [], wrappedHeightAdjustment: 0 }, + ) + + return +} - const adjustedTop = top + acc.wrappedHeightAdjustment +const determineIfShouldSkip = (props: LinePropsWithLevels, options: ConfigOptions) => { + const skipFirstIndentLine = options.skipFirst && props.levelOfIndentation === props.indentBy - 1 + return skipFirstIndentLine +} - // Only adjust height for Subsequent lines! - if (wrappedLine) { - acc.wrappedHeightAdjustment += adjustedHeight - } +/** + * Remove one indent from left positioning and move lines slightly inwards - + * by a third of a character for a better visual appearance + */ +const calculateLeftPosition = (props: LinePropsWithLevels) => { + const adjustedLeft = + props.left - + props.indentSize - + props.levelOfIndentation * props.indentSize + + props.characterWidth / 3 + + return adjustedLeft +} - if (!line && previous) { - acc.allIndentations.push({ - ...previous, +const getWrappedLines = (ctx: Partial): IWrappedLine[] => { + const { lines } = ctx.visibleLines.reduce( + (acc, line, index) => { + const currentLine = ctx.topBufferLine + index + const bufferInfo = ctx.bufferToScreen({ line: currentLine, character: 0 }) + + if (bufferInfo && bufferInfo.screenY) { + const { screenY: screenLine } = bufferInfo + if (acc.expectedLine !== screenLine) { + acc.lines.push({ + start: acc.expectedLine, + end: screenLine, line, - top: adjustedTop, }) + acc.expectedLine = screenLine + 1 return acc } + acc.expectedLine += 1 + } + return acc + }, + { lines: [], expectedLine: 1 }, + ) + return lines +} - const indent = { - left, - line, - indentSize, - top: adjustedTop, - height: adjustedHeight, - characterWidth: fontPixelWidth, - indentBy: regularisedIndent / this._userSpacing, - } +const regulariseIndentation = (indentation: detectIndent.IndentInfo, userSpacing: number) => { + const isOddBy = indentation.amount % userSpacing + const amountToIndent = isOddBy ? indentation.amount - isOddBy : indentation.amount + return amountToIndent +} - acc.allIndentations.push(indent) +interface IndentLayerArgs { + buffer: IBuffer + configuration: Oni.Configuration +} - return acc - }, - { allIndentations: [], wrappedHeightAdjustment: 0 }, +class IndentGuideBufferLayer implements Oni.BufferLayer { + public render = (bufferLayerContext: IContext) => { + return ( + + + ) + } - return this._getIndentLines(allIndentations, options) + private _buffer: IBuffer + private _userSpacing: number + private _configuration: ConfigOptions + + constructor({ buffer, configuration }: IndentLayerArgs) { + this._buffer = buffer + this._configuration = { + color: configuration.getValue("experimental.indentLines.color"), + skipFirst: configuration.getValue("experimental.indentLines.skipFirst"), + } + this._userSpacing = this._buffer.shiftwidth || this._buffer.tabstop + } + get id() { + return "indent-guides" + } + + get friendlyName() { + return "Indent Guide Lines" } } diff --git a/package.json b/package.json index 5526ff7554..c55e1aa2b2 100644 --- a/package.json +++ b/package.json @@ -870,6 +870,7 @@ "keyboard-layout": "^2.0.13", "marked": "^0.4.0", "minimist": "1.2.0", + "moize": "^5.4.1", "msgpack-lite": "0.1.26", "ocaml-language-server": "^1.0.27", "oni-api": "0.0.49", diff --git a/yarn.lock b/yarn.lock index d799ed8f1f..342c272151 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4142,6 +4142,10 @@ fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" +fast-equals@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-1.6.1.tgz#66cc5a0922ea747599f41aedf44a76ec2908adc0" + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -4154,6 +4158,10 @@ fast-plist@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" +fast-stringify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-stringify/-/fast-stringify-1.1.0.tgz#6caf0af2456296adac9d0c9c6f0522d18297f57a" + fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" @@ -7007,6 +7015,10 @@ methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" +micro-memoize@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micro-memoize/-/micro-memoize-2.1.1.tgz#5092f1ef475546b28f8da0e11f061850d5549ae4" + micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" @@ -7199,6 +7211,14 @@ mocha@^4.0.1: mkdirp "0.5.1" supports-color "4.4.0" +moize@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/moize/-/moize-5.4.1.tgz#664a1a6856bd8dfe0ceda72f17e6765ff5910243" + dependencies: + fast-equals "^1.6.0" + fast-stringify "^1.1.0" + micro-memoize "^2.1.1" + moment@^2.11.2: version "2.19.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" From b82b631bfa9d58b59470bad2550b38c462d3ba62 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sat, 6 Oct 2018 22:10:30 +0100 Subject: [PATCH 2/4] reposition utility functions --- .../OniEditor/IndentGuideBufferLayer.tsx | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx b/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx index f1af05a9f1..ddcdf88a6a 100644 --- a/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx +++ b/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx @@ -56,6 +56,25 @@ const IndentLine = withProps(styled.span).attrs({ position: absolute; ` +const determineIfShouldSkip = (props: LinePropsWithLevels, options: ConfigOptions) => { + const skipFirstIndentLine = options.skipFirst && props.levelOfIndentation === props.indentBy - 1 + return skipFirstIndentLine +} + +/** + * Remove one indent from left positioning and move lines slightly inwards - + * by a third of a character for a better visual appearance + */ +const calculateLeftPosition = (props: LinePropsWithLevels) => { + const adjustedLeft = + props.left - + props.indentSize - + props.levelOfIndentation * props.indentSize + + props.characterWidth / 3 + + return adjustedLeft +} + interface IIndentsPerLine { guidePositions: IndentLinesProps[] options: ConfigOptions @@ -168,25 +187,6 @@ const IndentGuideLines: React.SFC = ({ context, ...props }) => { return } -const determineIfShouldSkip = (props: LinePropsWithLevels, options: ConfigOptions) => { - const skipFirstIndentLine = options.skipFirst && props.levelOfIndentation === props.indentBy - 1 - return skipFirstIndentLine -} - -/** - * Remove one indent from left positioning and move lines slightly inwards - - * by a third of a character for a better visual appearance - */ -const calculateLeftPosition = (props: LinePropsWithLevels) => { - const adjustedLeft = - props.left - - props.indentSize - - props.levelOfIndentation * props.indentSize + - props.characterWidth / 3 - - return adjustedLeft -} - const getWrappedLines = (ctx: Partial): IWrappedLine[] => { const { lines } = ctx.visibleLines.reduce( (acc, line, index) => { From 62bccfca9f7c0eb019dabba7cc7f43fcc4ac4cca Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sat, 6 Oct 2018 22:56:26 +0100 Subject: [PATCH 3/4] fix lint errors --- .../OniEditor/IndentGuideBufferLayer.tsx | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx b/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx index ddcdf88a6a..a432306cd0 100644 --- a/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx +++ b/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx @@ -3,8 +3,8 @@ import * as React from "react" import * as detectIndent from "detect-indent" import * as flatten from "lodash/flatten" import * as last from "lodash/last" -import * as Oni from "oni-api" import moize from "moize" +import * as Oni from "oni-api" import { IBuffer } from "../BufferManager" import styled, { pixel, withProps } from "./../../UI/components/common" @@ -225,22 +225,18 @@ interface IndentLayerArgs { } class IndentGuideBufferLayer implements Oni.BufferLayer { - public render = (bufferLayerContext: IContext) => { - return ( - - - - ) - } - private _buffer: IBuffer private _userSpacing: number private _configuration: ConfigOptions + get id() { + return "indent-guides" + } + + get friendlyName() { + return "Indent Guide Lines" + } + constructor({ buffer, configuration }: IndentLayerArgs) { this._buffer = buffer this._configuration = { @@ -249,12 +245,17 @@ class IndentGuideBufferLayer implements Oni.BufferLayer { } this._userSpacing = this._buffer.shiftwidth || this._buffer.tabstop } - get id() { - return "indent-guides" - } - get friendlyName() { - return "Indent Guide Lines" + public render = (bufferLayerContext: IContext) => { + return ( + + + + ) } } From 1b2511627e2401bf54eccf6c924ceeaf4f55a6f8 Mon Sep 17 00:00:00 2001 From: Akin909 Date: Sat, 6 Oct 2018 22:58:02 +0100 Subject: [PATCH 4/4] use moize react simple --- browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx b/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx index a432306cd0..3834269e4d 100644 --- a/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx +++ b/browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx @@ -105,7 +105,7 @@ const IndentsPerLine: React.SFC = ({ guidePositions, options }) return <>{flatten(indents)} } -const MemoizedIndentsPerLine = moize.react(IndentsPerLine, { +const MemoizedIndentsPerLine = moize.reactSimple(IndentsPerLine, { isDeepEqual: true, })