From 2ef2a6c14d7cee2c601e6aaeade980dc976e8b7b Mon Sep 17 00:00:00 2001 From: Dmytro Hryshyn Date: Mon, 29 Jan 2024 14:32:18 +0200 Subject: [PATCH] finalize --- .../codemodList/CodemodArguments/index.tsx | 22 +++--- .../CodemodNodeRenderer/Codemod.tsx | 2 - .../codemodList/CodemodNodeRenderer/index.tsx | 6 +- .../components/DirectorySelector.tsx | 78 ++++++++++--------- .../codemodList/components/style.module.css | 2 + pnpm-lock.yaml | 40 ++++++++-- src/components/webview/MainProvider.ts | 24 ++++-- src/selectors/selectCodemodTree.ts | 23 +++++- src/selectors/selectMainWebviewViewProps.ts | 6 +- 9 files changed, 130 insertions(+), 73 deletions(-) diff --git a/intuita-webview/src/codemodList/CodemodArguments/index.tsx b/intuita-webview/src/codemodList/CodemodArguments/index.tsx index ce863b06..bfcd44b7 100644 --- a/intuita-webview/src/codemodList/CodemodArguments/index.tsx +++ b/intuita-webview/src/codemodList/CodemodArguments/index.tsx @@ -18,19 +18,26 @@ type Props = Readonly<{ hashDigest: CodemodNodeHashDigest; arguments: ReadonlyArray; autocompleteItems: ReadonlyArray; - rootPath: string | null; executionPath: T.These<{ message: string }, string>; }>; -const buildTargetPath = (path: string, rootPath: string) => { - return path.replace(rootPath, ''); +const updatePath = (value: string, codemodHash: CodemodHash) => { + vscode.postMessage({ + kind: 'webview.codemodList.updatePathToExecute', + value: { + newPath: value, + codemodHash, + errorMessage: '', + warningMessage: '', + revertToPrevExecutionIfInvalid: false, + }, + }); }; const CodemodArguments = ({ hashDigest, arguments: args, autocompleteItems, - rootPath, executionPath, }: Props) => { const onChangeFormField = (fieldName: string) => (value: string) => { @@ -54,15 +61,12 @@ const CodemodArguments = ({ ), ); - const targetPath = - rootPath !== null ? buildTargetPath(path, rootPath) : '/'; - return (
updatePath(value, hashDigest as unknown as CodemodHash )} autocompleteItems={autocompleteItems} /> {args.map((props) => ( diff --git a/intuita-webview/src/codemodList/CodemodNodeRenderer/Codemod.tsx b/intuita-webview/src/codemodList/CodemodNodeRenderer/Codemod.tsx index 38c0d03f..564f3df1 100644 --- a/intuita-webview/src/codemodList/CodemodNodeRenderer/Codemod.tsx +++ b/intuita-webview/src/codemodList/CodemodNodeRenderer/Codemod.tsx @@ -17,8 +17,6 @@ type Props = Omit & progress: Progress | null; screenWidth: number | null; focused: boolean; - rootPath: string | null; - autocompleteItems: ReadonlyArray; argumentsExpanded: boolean; }>; diff --git a/intuita-webview/src/codemodList/CodemodNodeRenderer/index.tsx b/intuita-webview/src/codemodList/CodemodNodeRenderer/index.tsx index 98ac792b..44b9a4b6 100644 --- a/intuita-webview/src/codemodList/CodemodNodeRenderer/index.tsx +++ b/intuita-webview/src/codemodList/CodemodNodeRenderer/index.tsx @@ -25,7 +25,6 @@ const getIndent = (depth: number) => { type Deps = { progress: Progress | null; screenWidth: number | null; - rootPath: string | null; autocompleteItems: ReadonlyArray; }; @@ -53,7 +52,7 @@ const renderProgressBar = (progress: Progress | null) => { }; const getCodemodNodeRenderer = - ({ rootPath, autocompleteItems, progress, screenWidth }: Deps) => + ({ autocompleteItems, progress, screenWidth }: Deps) => ({ nodeDatum, onFlip }: Props) => { const { node, focused, expanded, argumentsExpanded } = nodeDatum; const { hashDigest, label } = node; @@ -105,8 +104,6 @@ const getCodemodNodeRenderer = screenWidth={screenWidth} permalink={node.permalink} executionPath={node.executionPath} - autocompleteItems={autocompleteItems} - rootPath={rootPath} argumentsExpanded={argumentsExpanded} args={node.args} /> @@ -122,7 +119,6 @@ const getCodemodNodeRenderer = > { - vscode.postMessage({ - kind: 'webview.codemodList.updatePathToExecute', - value: { - newPath: value, - codemodHash, - errorMessage: '', - warningMessage: '', - revertToPrevExecutionIfInvalid: false, - }, - }); -}; - type Props = { - defaultValue: string; - codemodHash: CodemodHash; + initialValue: string; autocompleteItems: ReadonlyArray; + onChange: (value: string) => void; }; +const AUTOCOMPLETE_OPTIONS_LENGTH = 20; + +const getFilteredOptions = (allOptions: ReadonlyArray, value: string) => { + + // ignores slashes at the beginning, ignores whitespaces + const trimmedLowerCaseValue = value + .replace(/^[/\\]+/, '') + .trim() + .toLocaleLowerCase(); + + return allOptions + .filter((i) => + i.toLocaleLowerCase().startsWith(trimmedLowerCaseValue), + ) + .slice(0, AUTOCOMPLETE_OPTIONS_LENGTH); +} export const DirectorySelector = ({ - defaultValue, - codemodHash, + initialValue, + onChange, autocompleteItems, }: Props) => { - const [value, setValue] = useState(defaultValue); - const [focusedOptionIdx, setFocusedOptionIdx] = useState(0); + const [value, setValue] = useState(initialValue); + const [focusedOptionIdx, setFocusedOptionIdx] = useState(null); const [showOptions, setShowOptions] = useState(false); - console.log(defaultValue, '?'); useEffect(() => { - setValue(defaultValue); - }, [defaultValue]); + setValue(initialValue); + }, [initialValue]); - const autocompleteOptions = autocompleteItems - .filter((i) => - i.toLocaleLowerCase().includes(value.trim().toLocaleLowerCase()), - ) - .slice(0, 5); + const autocompleteOptions = getFilteredOptions(autocompleteItems, value); const handleChange = (e: Event | React.FormEvent) => { setValue((e.target as HTMLInputElement)?.value); }; const handleFocus = () => { + setFocusedOptionIdx(-1); setShowOptions(true); }; @@ -64,26 +61,36 @@ export const DirectorySelector = ({ } if (e.key === 'Enter') { - const nextValue = autocompleteOptions[focusedOptionIdx] ?? ''; setShowOptions(false); + + if(focusedOptionIdx === null) { + return; + } + + const nextValue = autocompleteOptions[focusedOptionIdx] ?? ''; + + onChange(nextValue); setValue(nextValue); - updatePath(nextValue, codemodHash); } if (e.key === 'ArrowUp') { - setFocusedOptionIdx((focusedOptionIdx - 1 + maxLength) % maxLength); + const nextValue = focusedOptionIdx === null ? maxLength : (focusedOptionIdx - 1 + maxLength) % maxLength; + setFocusedOptionIdx(nextValue); e.stopPropagation(); e.preventDefault(); } if (e.key === 'ArrowDown') { - setFocusedOptionIdx((focusedOptionIdx + 1) % maxLength); + const nextValue = focusedOptionIdx === null ? 0 : (focusedOptionIdx + 1) % maxLength; + + setFocusedOptionIdx(nextValue); e.stopPropagation(); e.preventDefault(); } if (e.key === 'Tab') { - setFocusedOptionIdx((focusedOptionIdx + 1) % maxLength); + const nextValue = focusedOptionIdx === null ? 0 : (focusedOptionIdx + 1) % maxLength; + setFocusedOptionIdx(nextValue); e.stopPropagation(); e.preventDefault(); } @@ -123,10 +130,9 @@ export const DirectorySelector = ({ id={`option_${i}`} className={styles.option} onClick={() => { - console.log(item); setShowOptions(false); setValue(item); - updatePath(item, codemodHash); + onChange(item); }} > {item} diff --git a/intuita-webview/src/codemodList/components/style.module.css b/intuita-webview/src/codemodList/components/style.module.css index 47f99e5e..6866eef2 100644 --- a/intuita-webview/src/codemodList/components/style.module.css +++ b/intuita-webview/src/codemodList/components/style.module.css @@ -17,4 +17,6 @@ .autocompleteItems { background-color: var(--vscode-editor-background); padding: 4px; + max-height: 250px; + overflow-y: auto; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04b039fe..b27dba22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -779,6 +779,18 @@ packages: - fast-check dev: false + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -2228,6 +2240,10 @@ packages: is-obj: 2.0.0 dev: true + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /effect@2.0.0-next.59: resolution: {integrity: sha512-EE87vFl0/zIN5lKDtFccU3YCnbPqjxg9rY72obNN65/GE4JOJsXciyX8XC4pIDr3lE6KeJ0le8IXf+A7d92ntQ==} dev: false @@ -3300,17 +3316,17 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + /minipass@7.0.3: + resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} requiresBuild: true dev: false optional: true - /minipass@7.0.3: - resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==} - engines: {node: '>=16 || 14 >=14.17'} - dev: true - /mkdirp@2.1.6: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} engines: {node: '>=10'} @@ -3947,6 +3963,11 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} requiresBuild: true @@ -4019,6 +4040,13 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} diff --git a/src/components/webview/MainProvider.ts b/src/components/webview/MainProvider.ts index e5cdf9d4..186804ee 100644 --- a/src/components/webview/MainProvider.ts +++ b/src/components/webview/MainProvider.ts @@ -1,5 +1,5 @@ import areEqual from 'fast-deep-equal'; -import { relative, join } from 'node:path'; +import { relative, join, sep } from 'node:path'; import { glob } from 'glob'; import { @@ -25,6 +25,7 @@ import axios from 'axios'; import { UserService } from '../userService'; import { CodemodNodeHashDigest, + relativeToAbsolutePath, selectCodemodArguments, } from '../../selectors/selectCodemodTree'; import { isNeitherNullNorUndefined } from '../../utilities'; @@ -135,7 +136,7 @@ export class MainViewProvider implements WebviewViewProvider { private __view: WebviewView | null = null; private __webviewResolver: WebviewResolver; private __executionQueue: ReadonlyArray = []; - private __directoryPaths: ReadonlyArray = []; + private __directoryPaths: ReadonlyArray | null = null; constructor( context: ExtensionContext, @@ -188,6 +189,11 @@ export class MainViewProvider implements WebviewViewProvider { let prevProps = this.__buildProps(); this.__store.subscribe(async () => { + + if(this.__directoryPaths === null) { + await this.__getDirectoryPaths(); + } + const nextProps = this.__buildProps(); if (areEqual(prevProps, nextProps)) { return; @@ -211,8 +217,6 @@ export class MainViewProvider implements WebviewViewProvider { props: nextProps, }); }); - - this.__getDirectoryPaths(); } public isVisible(): boolean { @@ -249,13 +253,13 @@ export class MainViewProvider implements WebviewViewProvider { } this.__directoryPaths = ( - (await glob(`${this.__rootUri?.fsPath}/**`, { + (await glob(`${basePath}/**`, { fs: workspace.fs, nodir: false, // ignore node_modules and files, match only directories ignore: ['**/node_modules/**', '**/*.*'], })) ?? [] - ).map((p) => relative(basePath, p)); + ); } private __postMessage(message: WebviewMessage) { @@ -565,6 +569,10 @@ export class MainViewProvider implements WebviewViewProvider { if ( message.kind === 'webview.global.setCodemodArgumentsPopupHashDigest' ) { + if(this.__directoryPaths === null) { + + } + this.__store.dispatch( actions.setCodemodArgumentsPopupHashDigest(message.hashDigest), ); @@ -605,7 +613,7 @@ export class MainViewProvider implements WebviewViewProvider { const persistedExecutionPath = state.executionPaths[codemodHash]; const oldExecutionPath = persistedExecutionPath ?? null; - const newPathAbsolute = join(this.__rootUri.fsPath, newPath); + const newPathAbsolute = relativeToAbsolutePath(newPath, this.__rootUri.fsPath) try { await workspace.fs.stat(Uri.file(newPathAbsolute)); @@ -616,7 +624,7 @@ export class MainViewProvider implements WebviewViewProvider { }), ); - if (newPathAbsolute !== oldExecutionPath && !fromVSCodeCommand) { + if (!fromVSCodeCommand) { window.showInformationMessage( 'Successfully updated the execution path.', ); diff --git a/src/selectors/selectCodemodTree.ts b/src/selectors/selectCodemodTree.ts index 9254747f..86b34bc8 100644 --- a/src/selectors/selectCodemodTree.ts +++ b/src/selectors/selectCodemodTree.ts @@ -4,6 +4,7 @@ import { RootState } from '../data'; import * as t from 'io-ts'; import * as T from 'fp-ts/These'; import { CodemodHash } from '../packageJsonAnalyzer/types'; +import { sep, relative, join } from 'node:path'; const IntuitaCertifiedCodemods = [ 'next/13/app-directory-boilerplate', @@ -69,6 +70,17 @@ export const buildDirectoryNode = (name: string, path: string) => label: name, } as const); +export const absoluteToRelativePath = (absolutePath: string, rootPath: string) => { + const basePathWithoutLastDir = rootPath.split(sep).slice(0, -1).join(sep); + return relative(basePathWithoutLastDir, absolutePath) +} + +export const relativeToAbsolutePath = (relativePath: string, rootPath: string) => { + const basePathWithoutLastDir = rootPath.split(sep).slice(0, -1).join(sep); + + return join(basePathWithoutLastDir, relativePath); +} + export const buildCodemodNode = ( codemod: CodemodEntry | PrivateCodemodEntry, name: string, @@ -116,7 +128,9 @@ export const selectPrivateCodemods = ( const { executionPaths } = state.codemodDiscoveryView; const executionPath = - executionPaths[codemod.hashDigest] ?? rootPath ?? '/'; + executionPaths[codemod.hashDigest] ?? rootPath ?? ''; + + const executionRelativePath = absoluteToRelativePath(executionPath, rootPath ?? ''); const args = selectCodemodArguments( state, @@ -126,7 +140,7 @@ export const selectPrivateCodemods = ( const node = buildCodemodNode( codemod, name, - executionPath, + executionRelativePath, executionQueue.includes(codemod.hashDigest as CodemodHash), true, args, @@ -166,7 +180,6 @@ export const selectCodemodTree = ( codemods.sort((a, b) => a.name.localeCompare(b.name)); const { executionPaths, searchPhrase } = state.codemodDiscoveryView; - console.log(executionPaths, '?'); const nodes: Record = {}; const children: Record = {}; @@ -210,6 +223,8 @@ export const selectCodemodTree = ( if (idx === pathParts.length - 1) { const executionPath = executionPaths[codemod.hashDigest] ?? rootPath ?? '/'; + + const executionRelativePath = absoluteToRelativePath(executionPath, rootPath ?? ''); const args = selectCodemodArguments( state, @@ -219,7 +234,7 @@ export const selectCodemodTree = ( currNode = buildCodemodNode( codemod, part, - executionPath, + executionRelativePath, executionQueue.includes(codemod.hashDigest as CodemodHash), false, args, diff --git a/src/selectors/selectMainWebviewViewProps.ts b/src/selectors/selectMainWebviewViewProps.ts index bbabfecb..3c1cb58e 100644 --- a/src/selectors/selectMainWebviewViewProps.ts +++ b/src/selectors/selectMainWebviewViewProps.ts @@ -1,7 +1,7 @@ import type { Uri } from 'vscode'; import type { RootState } from '../data'; import { selectCodemodRunsTree } from './selectCodemodRunsTree'; -import { selectCodemodTree, selectPrivateCodemods } from './selectCodemodTree'; +import { absoluteToRelativePath, selectCodemodTree, selectPrivateCodemods } from './selectCodemodTree'; import { selectExplorerTree } from './selectExplorerTree'; import { CodemodHash } from '../packageJsonAnalyzer/types'; import { selectSourceControlTabProps } from './selectSourceControlTabProps'; @@ -9,7 +9,7 @@ import { selectSourceControlTabProps } from './selectSourceControlTabProps'; export const selectMainWebviewViewProps = ( state: RootState, rootUri: Uri | null, - autocompleteItems: ReadonlyArray, + autocompleteItems: ReadonlyArray | null, executionQueue: ReadonlyArray, ) => { if (rootUri === null) { @@ -21,7 +21,7 @@ export const selectMainWebviewViewProps = ( activeTabId: state.activeTabId, toaster: state.toaster, searchPhrase: state.codemodDiscoveryView.searchPhrase, - autocompleteItems, + autocompleteItems: (autocompleteItems ?? []).map((item) => absoluteToRelativePath(item, rootUri.fsPath ?? '')), codemodTree: selectCodemodTree( state, rootUri?.fsPath ?? null,