diff --git a/src/components/molecules/Monaco/Monaco.tsx b/src/components/molecules/Monaco/Monaco.tsx index 1669a2f2a3..ef2058772f 100644 --- a/src/components/molecules/Monaco/Monaco.tsx +++ b/src/components/molecules/Monaco/Monaco.tsx @@ -23,7 +23,7 @@ import useCodeIntel from './useCodeIntel'; import useEditorKeybindings from './useEditorKeybindings'; import useResourceYamlSchema from './useResourceYamlSchema'; import useDebouncedCodeSave from './useDebouncedCodeSave'; -import useEditorUiState from './useEditorUiState'; +import useMonacoUiState from './useMonacoUiState'; import * as S from './Monaco.styled'; // @ts-ignore @@ -97,7 +97,7 @@ const Monaco = (props: {editorHeight: string; diffSelectedResource: () => void; selectedPath, setOrgCode ); - const {onEditorFocus} = useEditorUiState(editor, selectedResourceId); + const {onEditorFocus} = useMonacoUiState(editor, selectedResourceId, selectedPath); const onDidChangeMarkers = (e: monaco.Uri[]) => { const flag = monaco.editor.getModelMarkers({}).length > 0; diff --git a/src/components/molecules/Monaco/useEditorUiState.ts b/src/components/molecules/Monaco/useMonacoUiState.ts similarity index 68% rename from src/components/molecules/Monaco/useEditorUiState.ts rename to src/components/molecules/Monaco/useMonacoUiState.ts index 608c0a7139..75ba651419 100644 --- a/src/components/molecules/Monaco/useEditorUiState.ts +++ b/src/components/molecules/Monaco/useMonacoUiState.ts @@ -3,7 +3,11 @@ import {setMonacoEditor} from '@redux/reducers/ui'; import {useCallback, useEffect} from 'react'; import {monaco} from 'react-monaco-editor'; -function useEditorUiState(editor: monaco.editor.IStandaloneCodeEditor | null, selectedResourceId: string | undefined) { +function useMonacoUiState( + editor: monaco.editor.IStandaloneCodeEditor | null, + selectedResourceId: string | undefined, + selectedPath: string | undefined +) { const dispatch = useAppDispatch(); const monacoEditor = useAppSelector(state => state.ui.monacoEditor); @@ -26,6 +30,21 @@ function useEditorUiState(editor: monaco.editor.IStandaloneCodeEditor | null, se }; }, []); + useEffect(() => { + const selection = monacoEditor.selection; + if (!selection || !editor) { + return; + } + if ( + (selection.type === 'file' && selection.filePath === selectedPath) || + (selection.type === 'resource' && selection.resourceId === selectedResourceId) + ) { + editor.setSelection(selection.range); + editor.revealLineInCenter(selection.range.startLineNumber); + dispatch(setMonacoEditor({selection: undefined})); + } + }, [monacoEditor, selectedPath, selectedResourceId]); + useEffect(() => { if (!monacoEditor.focused) { return; @@ -52,4 +71,4 @@ function useEditorUiState(editor: monaco.editor.IStandaloneCodeEditor | null, se return {onEditorFocus}; } -export default useEditorUiState; +export default useMonacoUiState; diff --git a/src/components/molecules/ResourceRefsIconPopover/RefsPopoverContent.tsx b/src/components/molecules/ResourceRefsIconPopover/RefsPopoverContent.tsx index 09b770b719..1639ba81ed 100644 --- a/src/components/molecules/ResourceRefsIconPopover/RefsPopoverContent.tsx +++ b/src/components/molecules/ResourceRefsIconPopover/RefsPopoverContent.tsx @@ -1,8 +1,12 @@ import React from 'react'; -import {ResourceRef} from '@models/k8sresource'; +import {K8sResource, ResourceRef, ResourceRefType} from '@models/k8sresource'; import {ResourceMapType} from '@models/appstate'; import styled from 'styled-components'; import {Typography, Divider} from 'antd'; +import {useAppDispatch, useAppSelector} from '@redux/hooks'; +import {selectFile, selectK8sResource} from '@redux/reducers/main'; +import {setMonacoEditor} from '@redux/reducers/ui'; +import {MonacoRange} from '@models/ui'; import RefLink from './RefLink'; const {Text} = Typography; @@ -35,21 +39,96 @@ const getRefKind = (ref: ResourceRef, resourceMap: ResourceMapType) => { } }; +const getRefRange = (ref: ResourceRef) => { + if (!ref.position) { + return undefined; + } + return { + startLineNumber: ref.position.line, + endLineNumber: ref.position.line, + startColumn: ref.position.column, + endColumn: ref.position.column + ref.position.length, + }; +}; + const ResourceRefsPopover = (props: { children: React.ReactNode; + resource: K8sResource; resourceRefs: ResourceRef[]; - resourceMap: ResourceMapType; - selectResource: (selectedResource: string) => void; - selectFilePath: (filePath: string) => void; }) => { - const {children, resourceRefs, resourceMap, selectResource, selectFilePath} = props; + const {children, resourceRefs, resource} = props; + const dispatch = useAppDispatch(); + const resourceMap = useAppSelector(state => state.main.resourceMap); + const fileMap = useAppSelector(state => state.main.fileMap); + const selectedResourceId = useAppSelector(state => state.main.selectedResourceId); + const selectedPath = useAppSelector(state => state.main.selectedPath); + + const selectResource = (selectedId: string) => { + if (resourceMap[selectedId]) { + dispatch(selectK8sResource({resourceId: selectedId})); + } + }; + + const selectFilePath = (filePath: string) => { + if (fileMap[filePath]) { + dispatch(selectFile({filePath})); + } + }; + + const makeMonacoSelection = (type: 'resource' | 'file', target: string, range: MonacoRange) => { + const selection = + type === 'resource' + ? { + type, + resourceId: target, + range, + } + : {type, filePath: target, range}; + dispatch( + setMonacoEditor({ + selection, + }) + ); + }; const onLinkClick = (ref: ResourceRef) => { - if (ref.target?.type === 'resource' && ref.target.resourceId) { - selectResource(ref.target.resourceId); + if (ref.type !== ResourceRefType.Incoming) { + if (selectedResourceId !== resource.id) { + selectResource(resource.id); + } + const refRange = getRefRange(ref); + if (refRange) { + makeMonacoSelection('resource', resource.id, refRange); + } + return; + } + + if (ref.target?.type === 'resource') { + if (!ref.target.resourceId) { + return; + } + const targetResource = resourceMap[ref.target.resourceId]; + if (!targetResource) { + return; + } + if (selectedResourceId !== targetResource.id) { + selectResource(targetResource.id); + } + const targetOutgoingRef = targetResource.refs?.find( + r => r.type === ResourceRefType.Outgoing && r.target?.type === 'resource' && r.target.resourceId === resource.id + ); + if (!targetOutgoingRef) { + return; + } + const targetOutgoingRefRange = getRefRange(targetOutgoingRef); + if (targetOutgoingRefRange) { + makeMonacoSelection('resource', targetResource.id, targetOutgoingRefRange); + } } if (ref.target?.type === 'file') { - selectFilePath(ref.target.filePath); + if (selectedPath !== ref.target.filePath) { + selectFilePath(ref.target.filePath); + } } }; diff --git a/src/components/molecules/ResourceRefsIconPopover/ResourceRefsIconPopover.tsx b/src/components/molecules/ResourceRefsIconPopover/ResourceRefsIconPopover.tsx index fb679b94d8..91f8dc6dfe 100644 --- a/src/components/molecules/ResourceRefsIconPopover/ResourceRefsIconPopover.tsx +++ b/src/components/molecules/ResourceRefsIconPopover/ResourceRefsIconPopover.tsx @@ -1,7 +1,5 @@ import MonoIcon, {MonoIconTypes} from '@components/atoms/MonoIcon'; import {K8sResource} from '@models/k8sresource'; -import {useAppDispatch, useAppSelector} from '@redux/hooks'; -import {selectFile, selectK8sResource} from '@redux/reducers/main'; import {isIncomingRef, isOutgoingRef, isUnsatisfiedRef} from '@redux/services/resourceRefs'; import {Popover} from 'antd'; import {useMemo} from 'react'; @@ -16,9 +14,7 @@ const StyledIconsContainer = styled.span` const ResourceRefsIconPopover = (props: {resource: K8sResource; type: 'incoming' | 'outgoing'}) => { const {resource, type} = props; - const dispatch = useAppDispatch(); - const resourceMap = useAppSelector(state => state.main.resourceMap); - const fileMap = useAppSelector(state => state.main.fileMap); + const resourceRefs = useMemo( () => resource.refs?.filter(r => { @@ -36,18 +32,6 @@ const ResourceRefsIconPopover = (props: {resource: K8sResource; type: 'incoming' return resourceRefs?.some(r => isUnsatisfiedRef(r.type)); }, [resourceRefs, type]); - const selectResource = (selectedId: string) => { - if (resourceMap[selectedId]) { - dispatch(selectK8sResource({resourceId: selectedId})); - } - }; - - const selectFilePath = (filePath: string) => { - if (fileMap[filePath]) { - dispatch(selectFile({filePath})); - } - }; - if (!resourceRefs || resourceRefs.length === 0) { return null; } @@ -57,12 +41,7 @@ const ResourceRefsIconPopover = (props: {resource: K8sResource; type: 'incoming' mouseEnterDelay={0.5} placement="rightTop" content={ - + {type === 'incoming' ? ( <> Incoming Links diff --git a/src/models/ui.ts b/src/models/ui.ts index a714477429..d08aa037b8 100644 --- a/src/models/ui.ts +++ b/src/models/ui.ts @@ -8,6 +8,38 @@ export type NewResourceWizardInput = { selectedResourceId?: string; }; +export type MonacoRange = { + startLineNumber: number; + endLineNumber: number; + startColumn: number; + endColumn: number; +}; + +export type MonacoSelectionResource = { + type: 'resource'; + resourceId: string; + range: MonacoRange; +}; + +export type MonacoSelectionFile = { + type: 'file'; + filePath: string; + range: MonacoRange; +}; + +export type MonacoUiSelection = MonacoSelectionResource | MonacoSelectionFile; + +export type MonacoUiState = { + focused: boolean; + undo: boolean; + redo: boolean; + find: boolean; + replace: boolean; + apply: boolean; + diff: boolean; + selection?: MonacoUiSelection; +}; + export type UiState = { isSettingsOpen: boolean; newResourceWizard: { @@ -37,15 +69,7 @@ export type UiState = { folderExplorer: { isOpen: boolean; }; - monacoEditor: { - focused: boolean; - undo: boolean; - redo: boolean; - find: boolean; - replace: boolean; - apply: boolean; - diff: boolean; - }; + monacoEditor: MonacoUiState; paneConfiguration: PaneConfiguration; shouldExpandAllNodes: boolean; resetLayout: boolean; diff --git a/src/redux/reducers/ui.ts b/src/redux/reducers/ui.ts index c0a14739aa..964023952b 100644 --- a/src/redux/reducers/ui.ts +++ b/src/redux/reducers/ui.ts @@ -1,6 +1,6 @@ import {createAsyncThunk, createSlice, Draft, PayloadAction} from '@reduxjs/toolkit'; import {setRootFolder} from '@redux/thunks/setRootFolder'; -import {PaneConfiguration, UiState, NewResourceWizardInput} from '@models/ui'; +import {PaneConfiguration, UiState, NewResourceWizardInput, MonacoUiState} from '@models/ui'; import initialState from '@redux/initialState'; import {ResourceValidationError} from '@models/k8sresource'; import electronStore from '@utils/electronStore'; @@ -110,7 +110,7 @@ export const uiSlice = createSlice({ closeFolderExplorer: (state: Draft) => { state.folderExplorer = {isOpen: false}; }, - setMonacoEditor: (state: Draft, action: PayloadAction) => { + setMonacoEditor: (state: Draft, action: PayloadAction>) => { state.monacoEditor = { ...state.monacoEditor, ...action.payload,