Commit 2e3dada5 authored by tom's avatar tom

go to file

parent 082b62ab
...@@ -50,7 +50,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -50,7 +50,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
null; null;
return ( return (
<section title="Source code"> <section>
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }> <Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
{ heading } { heading }
{ diagramLink } { diagramLink }
......
import type { SystemStyleObject } from '@chakra-ui/react';
import { Box, useColorMode, Flex } from '@chakra-ui/react'; import { Box, useColorMode, Flex } from '@chakra-ui/react';
import type { EditorProps } from '@monaco-editor/react'; import type { EditorProps } from '@monaco-editor/react';
import MonacoEditor from '@monaco-editor/react'; import MonacoEditor from '@monaco-editor/react';
...@@ -13,6 +14,8 @@ import CodeEditorBreadcrumbs from './CodeEditorBreadcrumbs'; ...@@ -13,6 +14,8 @@ import CodeEditorBreadcrumbs from './CodeEditorBreadcrumbs';
import CodeEditorLoading from './CodeEditorLoading'; import CodeEditorLoading from './CodeEditorLoading';
import CodeEditorSideBar, { CONTAINER_WIDTH as SIDE_BAR_WIDTH } from './CodeEditorSideBar'; import CodeEditorSideBar, { CONTAINER_WIDTH as SIDE_BAR_WIDTH } from './CodeEditorSideBar';
import CodeEditorTabs from './CodeEditorTabs'; import CodeEditorTabs from './CodeEditorTabs';
import addFileImportDecorations from './utils/addFileImportDecorations';
import getFullPathOfImportedFile from './utils/getFullPathOfImportedFile';
import * as themes from './utils/themes'; import * as themes from './utils/themes';
import useThemeColors from './utils/useThemeColors'; import useThemeColors from './utils/useThemeColors';
...@@ -22,6 +25,7 @@ const EDITOR_OPTIONS: EditorProps['options'] = { ...@@ -22,6 +25,7 @@ const EDITOR_OPTIONS: EditorProps['options'] = {
scrollbar: { scrollbar: {
alwaysConsumeMouseWheel: true, alwaysConsumeMouseWheel: true,
}, },
dragAndDrop: false,
}; };
const TABS_HEIGHT = 35; const TABS_HEIGHT = 35;
...@@ -58,12 +62,13 @@ const CodeEditor = ({ data }: Props) => { ...@@ -58,12 +62,13 @@ const CodeEditor = ({ data }: Props) => {
monaco.editor.defineTheme('blockscout-dark', themes.dark); monaco.editor.defineTheme('blockscout-dark', themes.dark);
monaco.editor.setTheme(colorMode === 'light' ? 'blockscout-light' : 'blockscout-dark'); monaco.editor.setTheme(colorMode === 'light' ? 'blockscout-light' : 'blockscout-dark');
const loadedModelsPaths = monaco.editor.getModels().map((model) => model.uri.path); const loadedModels = monaco.editor.getModels();
data.slice(1) const loadedModelsPaths = loadedModels.map((model) => model.uri.path);
const newModels = data.slice(1)
.filter((file) => !loadedModelsPaths.includes(file.file_path)) .filter((file) => !loadedModelsPaths.includes(file.file_path))
.forEach((file) => { .map((file) => monaco.editor.createModel(file.source_code, 'sol', monaco.Uri.parse(file.file_path)));
monaco.editor.createModel(file.source_code, 'sol', monaco.Uri.parse(file.file_path));
}); loadedModels.concat(newModels).forEach(addFileImportDecorations);
// componentDidMount // componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ ]); }, [ ]);
...@@ -103,7 +108,20 @@ const CodeEditor = ({ data }: Props) => { ...@@ -103,7 +108,20 @@ const CodeEditor = ({ data }: Props) => {
}); });
}, [ data, index ]); }, [ data, index ]);
const containerSx = React.useMemo(() => ({ const handleClick = React.useCallback((event: React.MouseEvent) => {
const target = event.target as HTMLSpanElement;
const isImportLink = target.classList.contains('import-link');
if (isImportLink) {
const path = target.innerText;
const fullPath = getFullPathOfImportedFile(data[index].file_path, path);
const fileIndex = data.findIndex((file) => file.file_path === fullPath);
if (fileIndex > -1) {
handleSelectFile(fileIndex);
}
}
}, [ data, handleSelectFile, index ]);
const containerSx: SystemStyleObject = React.useMemo(() => ({
'.editor-container': { '.editor-container': {
position: 'absolute', position: 'absolute',
top: 0, top: 0,
...@@ -114,6 +132,13 @@ const CodeEditor = ({ data }: Props) => { ...@@ -114,6 +132,13 @@ const CodeEditor = ({ data }: Props) => {
'.highlight': { '.highlight': {
backgroundColor: themeColors['custom.findMatchHighlightBackground'], backgroundColor: themeColors['custom.findMatchHighlightBackground'],
}, },
'.import-link': {
_hover: {
color: themeColors['custom.fileLink.hoverForeground'],
textDecoration: 'underline',
cursor: 'pointer',
},
},
}), [ editorWidth, themeColors ]); }), [ editorWidth, themeColors ]);
if (data.length === 1) { if (data.length === 1) {
...@@ -140,6 +165,7 @@ const CodeEditor = ({ data }: Props) => { ...@@ -140,6 +165,7 @@ const CodeEditor = ({ data }: Props) => {
position="relative" position="relative"
ref={ containerNodeRef } ref={ containerNodeRef }
sx={ containerSx } sx={ containerSx }
onClick={ handleClick }
> >
<Box flexGrow={ 1 }> <Box flexGrow={ 1 }>
<CodeEditorTabs tabs={ tabs } activeTab={ data[index].file_path } onTabSelect={ handleTabSelect } onTabClose={ handleTabClose }/> <CodeEditorTabs tabs={ tabs } activeTab={ data[index].file_path } onTabSelect={ handleTabSelect } onTabClose={ handleTabClose }/>
......
import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
export default function addFileImportDecorations(model: monaco.editor.ITextModel) {
const matches = model.findMatches('^import "((\\/|\\.)(\\w|\\.|\\/|-)+)"', false, true, false, null, true);
const decorations: Array<monaco.editor.IModelDeltaDecoration> = matches.map(({ range }) => ({
range: {
...range,
startColumn: range.startColumn + 8,
endColumn: range.endColumn - 1,
},
options: {
inlineClassName: 'import-link',
hoverMessage: {
value: 'Click to open file',
},
},
}));
model.deltaDecorations([], decorations);
}
import getFullPathOfImportedFile from './getFullPathOfImportedFile';
it('construct correct absolute path', () => {
const result = getFullPathOfImportedFile(
'/foo/bar/baz/index.sol',
'./.././../abc/contract.sol',
);
expect(result).toBe('/foo/abc/contract.sol');
});
it('returns undefined if imported file is outside the base file folder', () => {
const result = getFullPathOfImportedFile(
'/index.sol',
'../../abc/contract.sol',
);
expect(result).toBeUndefined();
});
it('returns unmodified path if it is already absolute', () => {
const result = getFullPathOfImportedFile(
'/index.sol',
'/abc/contract.sol',
);
expect(result).toBe('/abc/contract.sol');
});
it('returns undefined for external path', () => {
const result = getFullPathOfImportedFile(
'/index.sol',
'https://github.com/ethereum/dapp/contract.sol',
);
expect(result).toBeUndefined();
});
import stripLeadingSlash from 'lib/stripLeadingSlash';
export default function getFullPathOfImportedFile(baseFilePath: string, importedFilePath: string) {
if (importedFilePath[0] === '/') {
return importedFilePath;
}
if (importedFilePath[0] !== '.') {
return;
}
const baseFileChunks = stripLeadingSlash(baseFilePath).split('/');
const importedFileChunks = importedFilePath.split('/');
const result: Array<string> = baseFileChunks.slice(0, -1);
for (let index = 0; index < importedFileChunks.length - 1; index++) {
const element = importedFileChunks[index];
if (element === '.') {
continue;
}
if (element === '..') {
if (result.length === 0) {
break;
}
result.pop();
continue;
}
result.push(element);
}
if (result.length === 0) {
return;
}
result.push(importedFileChunks[importedFileChunks.length - 1]);
return '/' + result.join('/');
}
...@@ -32,6 +32,9 @@ export const light = { ...@@ -32,6 +32,9 @@ export const light = {
'custom.findMatchHighlightBackground': 'rgba(234,92,0,0.33)', 'custom.findMatchHighlightBackground': 'rgba(234,92,0,0.33)',
'custom.inputOption.activeBackground': 'rgba(0, 144, 241, 0.2)', 'custom.inputOption.activeBackground': 'rgba(0, 144, 241, 0.2)',
'custom.inputOption.hoverBackground': 'rgba(184, 184, 184, 0.31)', 'custom.inputOption.hoverBackground': 'rgba(184, 184, 184, 0.31)',
// don't know the name of this variables in vscode
'custom.fileLink.hoverForeground': '#4299E1', // blue.400
} as const, } as const,
}; };
...@@ -69,5 +72,8 @@ export const dark = { ...@@ -69,5 +72,8 @@ export const dark = {
'custom.findMatchHighlightBackground': 'rgba(234,92,0,0.33)', 'custom.findMatchHighlightBackground': 'rgba(234,92,0,0.33)',
'custom.inputOption.activeBackground': 'rgba(0, 127, 212, 0.4)', 'custom.inputOption.activeBackground': 'rgba(0, 127, 212, 0.4)',
'custom.inputOption.hoverBackground': 'rgba(90, 93, 94, 0.31)', 'custom.inputOption.hoverBackground': 'rgba(90, 93, 94, 0.31)',
// don't know the name of this variables in vscode
'custom.fileLink.hoverForeground': '#4299E1', // blue.400
} as const, } as const,
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment