From 2d2746ef24ca21d1fda2b92510a9cd297d132354 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Mon, 15 Jul 2024 10:13:34 +0100 Subject: [PATCH] Streamline the project metadata context provider (#19384) GitOrigin-RevId: 0b75635cb9141983827dfd0fa6a58b6182d47f22 --- .../ide-react/context/metadata-context.tsx | 75 +++---- .../source-editor/extensions/language.ts | 11 +- .../hooks/use-codemirror-scope.ts | 10 +- .../languages/latex/completions/labels.ts | 8 +- .../languages/latex/completions/packages.ts | 32 +-- .../web/frontend/stories/decorators/scope.tsx | 9 - .../codemirror-editor-autocomplete.spec.tsx | 211 +++--------------- .../frontend/helpers/editor-providers.jsx | 8 - services/web/types/metadata.ts | 19 -- 9 files changed, 88 insertions(+), 295 deletions(-) delete mode 100644 services/web/types/metadata.ts diff --git a/services/web/frontend/js/features/ide-react/context/metadata-context.tsx b/services/web/frontend/js/features/ide-react/context/metadata-context.tsx index 7118f5a1f1..b9d939ad48 100644 --- a/services/web/frontend/js/features/ide-react/context/metadata-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/metadata-context.tsx @@ -11,7 +11,6 @@ import { import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context' import { useConnectionContext } from '@/features/ide-react/context/connection-context' import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context' -import _ from 'lodash' import { getJSON, postJSON } from '@/infrastructure/fetch-json' import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context' import { useEditorContext } from '@/shared/context/editor-context' @@ -22,28 +21,31 @@ import { usePermissionsContext } from '@/features/ide-react/context/permissions- import { useTranslation } from 'react-i18next' import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter' -type DocumentMetadata = { +export type Command = { + caption: string + snippet: string + meta: string + score: number +} + +export type DocumentMetadata = { labels: string[] - packages: Record + packages: Record + packageNames: string[] } type DocumentsMetadata = Record -type MetadataContextValue = { - metadata: { - state: { - documents: DocumentsMetadata - } - getAllLabels: () => DocumentMetadata['labels'] - getAllPackages: () => DocumentMetadata['packages'] - } -} - type DocMetadataResponse = { docId: string; meta: DocumentMetadata } -export const MetadataContext = createContext( - undefined -) +export const MetadataContext = createContext< + | { + commands: Command[] + labels: Set + packageNames: Set + } + | undefined +>(undefined) export const MetadataProvider: FC = ({ children }) => { const { t } = useTranslation() @@ -65,7 +67,8 @@ export const MetadataProvider: FC = ({ children }) => { }: CustomEvent) => { if (entity.type === 'doc') { setDocuments(documents => { - return _.omit(documents, entity.entity._id) + delete documents[entity.entity._id] + return { ...documents } }) } } @@ -90,23 +93,6 @@ export const MetadataProvider: FC = ({ children }) => { } }, []) - const getAllLabels = useCallback( - () => _.flattenDeep(Object.values(documents).map(meta => meta.labels)), - [documents] - ) - - const getAllPackages = useCallback(() => { - const packageCommandMapping: Record = {} - for (const meta of Object.values(documents)) { - for (const [packageName, commandSnippets] of Object.entries( - meta.packages - )) { - packageCommandMapping[packageName] = commandSnippets - } - } - return packageCommandMapping - }, [documents]) - const loadProjectMetaFromServer = useCallback(() => { getJSON(`/project/${projectId}/metadata`).then( (response: { projectMeta: DocumentsMetadata }) => { @@ -212,16 +198,15 @@ export const MetadataProvider: FC = ({ children }) => { } }, [eventEmitter, loadProjectMetaFromServer, showGenericMessageModal, t]) - const value = useMemo( - () => ({ - metadata: { - state: { documents }, - getAllLabels, - getAllPackages, - }, - }), - [documents, getAllLabels, getAllPackages] - ) + const value = useMemo(() => { + const docs = Object.values(documents) + + return { + commands: docs.flatMap(doc => Object.values(doc.packages).flat()), + labels: new Set(docs.flatMap(doc => doc.labels)), + packageNames: new Set(docs.flatMap(doc => doc.packageNames)), + } + }, [documents]) return ( @@ -230,7 +215,7 @@ export const MetadataProvider: FC = ({ children }) => { ) } -export function useMetadataContext(): MetadataContextValue { +export function useMetadataContext() { const context = useContext(MetadataContext) if (!context) { diff --git a/services/web/frontend/js/features/source-editor/extensions/language.ts b/services/web/frontend/js/features/source-editor/extensions/language.ts index 3eabeb3540..da61b4c09b 100644 --- a/services/web/frontend/js/features/source-editor/extensions/language.ts +++ b/services/web/frontend/js/features/source-editor/extensions/language.ts @@ -7,8 +7,9 @@ import { import { languages } from '../languages' import { ViewPlugin } from '@codemirror/view' import { indentUnit, LanguageDescription } from '@codemirror/language' -import { Metadata } from '../../../../../types/metadata' import { updateHasEffect } from '../utils/effects' +import { Folder } from '../../../../../types/folder' +import { Command } from '@/features/ide-react/context/metadata-context' export const languageLoadedEffect = StateEffect.define() export const hasLanguageLoadedEffect = updateHasEffect(languageLoadedEffect) @@ -19,6 +20,14 @@ type Options = { syntaxValidation: boolean } +type Metadata = { + labels: Set + packageNames: Set + commands: Command[] + references: string[] + fileTreeData: Folder +} + /** * A state field that stores the metadata parsed from a project on the server. */ diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts index 8955d49ce9..853633916c 100644 --- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts +++ b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts @@ -73,7 +73,7 @@ function useCodeMirrorScope(view: EditorView) { const { reviewPanelOpen, miniReviewPanelVisible } = useLayoutContext() - const { metadata } = useMetadataContext() + const metadata = useMetadataContext() const [loadingThreads] = useScopeValue('loadingThreads') @@ -213,16 +213,16 @@ function useCodeMirrorScope(view: EditorView) { // set the project metadata, mostly for use in autocomplete // TODO: read this data from the scope? const metadataRef = useRef({ - documents: metadata.state.documents, + ...metadata, references: references.keys, fileTreeData, }) - // listen to project metadata (docs + packages) updates + // listen to project metadata (commands, labels and package names) updates useEffect(() => { - metadataRef.current.documents = metadata.state.documents + metadataRef.current = { ...metadataRef.current, ...metadata } view.dispatch(setMetadata(metadataRef.current)) - }, [view, metadata.state.documents]) + }, [view, metadata]) // listen to project reference keys updates useEffect(() => { diff --git a/services/web/frontend/js/features/source-editor/languages/latex/completions/labels.ts b/services/web/frontend/js/features/source-editor/languages/latex/completions/labels.ts index d1de93137a..610d1ebe75 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/completions/labels.ts +++ b/services/web/frontend/js/features/source-editor/languages/latex/completions/labels.ts @@ -16,13 +16,7 @@ export function buildLabelCompletions( return } - const uniqueLabels = new Set( - Object.values(metadata.documents) - .map(doc => doc.labels) - .flat(1) - ) - - for (const label of uniqueLabels) { + for (const label of metadata.labels) { completions.labels.push({ type: 'label', label, diff --git a/services/web/frontend/js/features/source-editor/languages/latex/completions/packages.ts b/services/web/frontend/js/features/source-editor/languages/latex/completions/packages.ts index fc2541a006..f045ff3db2 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/completions/packages.ts +++ b/services/web/frontend/js/features/source-editor/languages/latex/completions/packages.ts @@ -21,24 +21,24 @@ export function buildPackageCompletions( return } - const uniquePackageNames = new Set(packageNames) - - // package names and commands from packages in the project - for (const doc of Object.values(metadata.documents)) { - for (const [packageName, commands] of Object.entries(doc.packages)) { - uniquePackageNames.add(packageName) - - for (const item of commands) { - completions.commands.push({ - type: item.meta, - label: item.caption, - apply: applySnippet(item.snippet), - extend: extendOverUnpairedClosingBrace, - }) - } - } + // commands from packages in the project + for (const command of metadata.commands) { + completions.commands.push({ + type: command.meta, + label: command.caption, + apply: applySnippet(command.snippet), + extend: extendOverUnpairedClosingBrace, + }) } + const uniquePackageNames = new Set(packageNames) + + // package names from packages in the project + for (const packageName of metadata.packageNames) { + uniquePackageNames.add(packageName) + } + + // exclude package names that are already in this document const existingPackageNames = findExistingPackageNames(context) for (const item of uniquePackageNames) { diff --git a/services/web/frontend/stories/decorators/scope.tsx b/services/web/frontend/stories/decorators/scope.tsx index 80cca49933..5529a762d2 100644 --- a/services/web/frontend/stories/decorators/scope.tsx +++ b/services/web/frontend/stories/decorators/scope.tsx @@ -133,15 +133,6 @@ const initialize = () => { console.log('open doc', id, options) }, }, - metadataManager: { - metadata: { - state: { - documents: { - 'test-file-id': { labels: ['sec:section-label'], packages: [] }, - }, - }, - }, - }, } // window.metaAttributesCache is reset in preview.tsx diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx index a9ce2aa369..2350a90a3e 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx @@ -1,6 +1,5 @@ import { Folder } from '../../../../../types/folder' import { docId, mockDocContent } from '../helpers/mock-doc' -import { Metadata } from '../../../../../types/metadata' import { mockScope } from '../helpers/mock-scope' import { EditorProviders } from '../../../helpers/editor-providers' import CodeMirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor' @@ -62,42 +61,13 @@ describe('autocomplete', { scrollBehavior: false }, function () { }, ] - const metadataManager: { metadata: { state: Metadata } } = { - metadata: { - state: { - documents: { - [docId]: { - labels: ['fig:frog'], - // TODO: add tests for packages and referencesKeys autocompletions - packages: { - foo: [ - { - caption: 'a caption', - meta: 'foo-cmd', - score: 0.1, - snippet: 'a caption{$1}', - }, - ], - }, - }, - }, - references: [], - fileTreeData: rootFolder[0], - }, - }, - } - const scope = mockScope() scope.$root._references.keys = ['foo'] scope.project.rootFolder = rootFolder cy.mount( - + @@ -238,41 +208,12 @@ describe('autocomplete', { scrollBehavior: false }, function () { }, ] - const metadataManager: { metadata: { state: Metadata } } = { - metadata: { - state: { - documents: { - [docId]: { - labels: ['fig:frog'], - // TODO: add tests for packages and referencesKeys autocompletions - packages: { - foo: [ - { - caption: 'a caption', - meta: 'foo-cmd', - score: 0.1, - snippet: 'a caption{$1}', - }, - ], - }, - }, - }, - references: [], - fileTreeData: rootFolder[0], - }, - }, - } - const scope = mockScope() scope.$root._references.keys = ['foo'] cy.mount( - + @@ -358,33 +299,15 @@ describe('autocomplete', { scrollBehavior: false }, function () { }, ] + const metadata = { + commands: [], + labels: new Set(), + packageNames: new Set(['foo']), + } + const MetadataProvider: FC = ({ children }) => { return ( - [], - getAllPackages: () => ({ foo: {} }), - }, - }} - > + {children} ) @@ -452,36 +375,9 @@ describe('autocomplete', { scrollBehavior: false }, function () { const scope = mockScope() scope.$root._references.keys = ['ref-1', 'ref-2', 'ref-3'] - const MetadataProvider: FC = ({ children }) => { - return ( - [], - getAllPackages: () => ({}), - }, - }} - > - {children} - - ) - } - cy.mount( - + @@ -535,31 +431,9 @@ describe('autocomplete', { scrollBehavior: false }, function () { scope.$root._references.keys = ['foo'] scope.project.rootFolder = rootFolder - const MetadataProvider: FC = ({ children }) => { - return ( - [], - getAllPackages: () => ({}), - }, - }} - > - {children} - - ) - } - cy.mount( - + @@ -824,33 +698,22 @@ describe('autocomplete', { scrollBehavior: false }, function () { it('displays unique completions for commands', function () { const scope = mockScope() + const metadata = { + commands: [ + { + caption: '\\label{}', // label{} is also included in top-hundred-snippets + meta: 'amsmath-cmd', + score: 1, + snippet: '\\label{$1}', + }, + ], + labels: new Set(), + packageNames: new Set('amsmath'), + } + const MetadataProvider: FC = ({ children }) => { return ( - [], - getAllPackages: () => ({}), - }, - }} - > + {children} ) @@ -1033,31 +896,9 @@ describe('autocomplete', { scrollBehavior: false }, function () { scope.$root._references.keys = ['foo'] scope.project.rootFolder = rootFolder - const MetadataProvider: FC = ({ children }) => { - return ( - [], - getAllPackages: () => ({}), - }, - }} - > - {children} - - ) - } - cy.mount( - + diff --git a/services/web/test/frontend/helpers/editor-providers.jsx b/services/web/test/frontend/helpers/editor-providers.jsx index b85d0ed6f5..adfca9c2fc 100644 --- a/services/web/test/frontend/helpers/editor-providers.jsx +++ b/services/web/test/frontend/helpers/editor-providers.jsx @@ -95,13 +95,6 @@ export function EditorProviders({ getCurrentDocValue: () => {}, openDoc: sinon.stub(), }, - metadataManager = { - metadata: { - state: { - documents: {}, - }, - }, - }, userSettings = {}, providers = {}, }) { @@ -153,7 +146,6 @@ export function EditorProviders({ clsiServerId, editorManager, fileTreeManager, - metadataManager, } // Add details for useUserContext diff --git a/services/web/types/metadata.ts b/services/web/types/metadata.ts deleted file mode 100644 index e1b3f35338..0000000000 --- a/services/web/types/metadata.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Folder } from './folder' - -export type Package = { - caption: string - meta: string - score: number - snippet: string -} - -export type MetadataDocument = { - labels: string[] - packages: Record -} - -export type Metadata = { - documents: Record - references: string[] - fileTreeData: Folder -}