Skip to content

Commit 7b3aaf9

Browse files
authored
Merge pull request #1979 from oasisprotocol/kaja/syntax-highlighting
Add Monaco Editor for contract source code syntax highlighting
2 parents 0afd904 + 6be725b commit 7b3aaf9

File tree

8 files changed

+203
-82
lines changed

8 files changed

+203
-82
lines changed

.changelog/1979.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add Monaco Editor for contract source code syntax highlighting

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"@fontsource-variable/figtree": "^5.0.19",
6363
"@fontsource-variable/roboto-mono": "^5.0.17",
6464
"@metamask/jazzicon": "2.0.0",
65+
"@monaco-editor/react": "^4.7.0",
6566
"@mui/base": "5.0.0-beta.61",
6667
"@mui/icons-material": "5.16.7",
6768
"@mui/material": "5.16.7",
@@ -74,6 +75,7 @@
7475
"bip39": "^3.1.0",
7576
"date-fns": "3.6.0",
7677
"i18next": "23.16.8",
78+
"monaco-editor": "^0.52.2",
7779
"react": "18.3.1",
7880
"react-dom": "18.3.1",
7981
"react-i18next": "14.1.3",
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Generated with: import('monaco-editor').then(a => console.log(a.languages.getLanguages().map(l=>JSON.stringify(l.id)).join('|')))
2+
// Note: `import { EditorLanguage } from 'monaco-editor/esm/metadata'` is wrong. It lists labels, not ids.
3+
export type MonacoLanguages =
4+
| 'plaintext'
5+
| 'abap'
6+
| 'apex'
7+
| 'azcli'
8+
| 'bat'
9+
| 'bicep'
10+
| 'cameligo'
11+
| 'clojure'
12+
| 'coffeescript'
13+
| 'c'
14+
| 'cpp'
15+
| 'csharp'
16+
| 'csp'
17+
| 'css'
18+
| 'cypher'
19+
| 'dart'
20+
| 'dockerfile'
21+
| 'ecl'
22+
| 'elixir'
23+
| 'flow9'
24+
| 'fsharp'
25+
| 'freemarker2'
26+
| 'freemarker2.tag-angle.interpolation-dollar'
27+
| 'freemarker2.tag-bracket.interpolation-dollar'
28+
| 'freemarker2.tag-angle.interpolation-bracket'
29+
| 'freemarker2.tag-bracket.interpolation-bracket'
30+
| 'freemarker2.tag-auto.interpolation-dollar'
31+
| 'freemarker2.tag-auto.interpolation-bracket'
32+
| 'go'
33+
| 'graphql'
34+
| 'handlebars'
35+
| 'hcl'
36+
| 'html'
37+
| 'ini'
38+
| 'java'
39+
| 'javascript'
40+
| 'julia'
41+
| 'kotlin'
42+
| 'less'
43+
| 'lexon'
44+
| 'lua'
45+
| 'liquid'
46+
| 'm3'
47+
| 'markdown'
48+
| 'mdx'
49+
| 'mips'
50+
| 'msdax'
51+
| 'mysql'
52+
| 'objective-c'
53+
| 'pascal'
54+
| 'pascaligo'
55+
| 'perl'
56+
| 'pgsql'
57+
| 'php'
58+
| 'pla'
59+
| 'postiats'
60+
| 'powerquery'
61+
| 'powershell'
62+
| 'proto'
63+
| 'pug'
64+
| 'python'
65+
| 'qsharp'
66+
| 'r'
67+
| 'razor'
68+
| 'redis'
69+
| 'redshift'
70+
| 'restructuredtext'
71+
| 'ruby'
72+
| 'rust'
73+
| 'sb'
74+
| 'scala'
75+
| 'scheme'
76+
| 'scss'
77+
| 'shell'
78+
| 'sol'
79+
| 'aes'
80+
| 'sparql'
81+
| 'sql'
82+
| 'st'
83+
| 'swift'
84+
| 'systemverilog'
85+
| 'verilog'
86+
| 'tcl'
87+
| 'twig'
88+
| 'typescript'
89+
| 'typespec'
90+
| 'vb'
91+
| 'wgsl'
92+
| 'xml'
93+
| 'yaml'
94+
| 'json'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { COLORS } from '../../../styles/theme/colors'
2+
3+
export function SimpleJsonCode(props: { data: Record<string, any> }) {
4+
return (
5+
<textarea
6+
readOnly
7+
value={JSON.stringify(props.data, null, 2)}
8+
style={{
9+
width: '100%',
10+
height: '350px',
11+
color: COLORS.brandExtraDark,
12+
background: COLORS.grayLight,
13+
borderRadius: '5px',
14+
padding: '10px',
15+
}}
16+
/>
17+
)
18+
}

src/app/components/CodeDisplay/index.tsx

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,56 @@
1-
import { FC, ReactNode, useState } from 'react'
1+
import React, { FC, useState, Suspense } from 'react'
22
import { useTranslation } from 'react-i18next'
33
import Box from '@mui/material/Box'
44
import Typography from '@mui/material/Typography'
5-
import { styled } from '@mui/material/styles'
6-
import { ScrollableDataDisplay } from '../../components/ScrollableDataDisplay'
75
import { CopyToClipboard, FloatingCopyToClipboard } from '../../components/CopyToClipboard'
86
import { useScreenSize } from '../../hooks/useScreensize'
97
import { base64ToHex } from '../../utils/helpers'
8+
import { MonacoLanguages } from './MonacoLanguages'
9+
10+
const TextAreaFallback = ({ value }: { value?: string }) => (
11+
<textarea readOnly value={value} style={{ width: '100%', height: '100%', colorScheme: 'dark' }} />
12+
)
13+
14+
const MonacoEditor = React.lazy(async () => {
15+
try {
16+
const monaco = await import('monaco-editor')
17+
const monacoReact = await import('@monaco-editor/react')
18+
// Load from npm, not cdn.jsdelivr.net
19+
// https://www.npmjs.com/package/@monaco-editor/react#use-monaco-editor-as-an-npm-package
20+
window.MonacoEnvironment = {
21+
getWorker(id, label) {
22+
return new Worker(
23+
new URL('../../../../node_modules/monaco-editor/esm/vs/editor/editor.worker.js', import.meta.url),
24+
{ type: 'module' },
25+
)
26+
},
27+
}
28+
monacoReact.loader.config({ monaco })
29+
30+
return monacoReact
31+
} catch (e) {
32+
console.error('monaco-editor wrapper failed to load. Using <textarea> instead')
33+
return {
34+
default: (props => (
35+
<TextAreaFallback value={props.value || props.defaultValue} />
36+
)) as typeof import('@monaco-editor/react').default,
37+
}
38+
}
39+
})
1040

1141
type CodeDisplayProps = {
12-
code: ReactNode
13-
copyToClipboardValue: string
42+
code: string
43+
language: MonacoLanguages
1444
label?: string
1545
extraTopPadding?: boolean
1646
floatingCopyButton?: boolean
1747
}
1848

1949
const CodeDisplay: FC<CodeDisplayProps> = ({
2050
code,
21-
copyToClipboardValue,
22-
label,
23-
extraTopPadding,
51+
language,
52+
label = undefined,
53+
extraTopPadding = false,
2454
floatingCopyButton = false,
2555
}) => {
2656
const { t } = useTranslation()
@@ -52,15 +82,31 @@ const CodeDisplay: FC<CodeDisplayProps> = ({
5282
</Typography>
5383
)}
5484
{floatingCopyButton ? (
55-
<FloatingCopyToClipboard isVisible={isHovering} value={copyToClipboardValue} />
85+
<FloatingCopyToClipboard isVisible={isHovering} value={code} />
5686
) : (
5787
<CopyToClipboard
58-
value={copyToClipboardValue}
88+
value={code}
5989
label={isMobile ? t('common.copy') : t('contract.copyButton', { subject: label })}
6090
/>
6191
)}
6292
</Box>
63-
<ScrollableDataDisplay data={code} />
93+
<Box sx={{ height: '350px', overflow: 'auto', resize: 'vertical' }}>
94+
{/* While loading wrapper show <textarea> instead */}
95+
<Suspense fallback={<TextAreaFallback value={code} />}>
96+
<MonacoEditor
97+
// While loading and if monaco-editor internals fail to load: show <textarea> instead
98+
loading={<TextAreaFallback value={code} />}
99+
language={language}
100+
value={code}
101+
theme="vs-dark"
102+
options={{
103+
readOnly: true,
104+
fontSize: 14,
105+
wordWrap: 'on',
106+
}}
107+
/>
108+
</Suspense>
109+
</Box>
64110
</Box>
65111
)
66112
}
@@ -72,16 +118,10 @@ type RawDataDisplayProps = {
72118

73119
export const RawDataDisplay: FC<RawDataDisplayProps> = ({ data, label }) => {
74120
const code = data === undefined ? undefined : base64ToHex(data)
75-
if (!code) {
76-
return null
77-
}
78-
return <CodeDisplay code={code} copyToClipboardValue={code} label={label} extraTopPadding />
79-
}
121+
if (!code) return null
80122

81-
const StyledPre = styled('pre')({
82-
margin: 0,
83-
whiteSpace: 'break-spaces',
84-
})
123+
return <CodeDisplay code={code} label={label} extraTopPadding language="plaintext" />
124+
}
85125

86126
type FileDisplayProps = {
87127
code: string | undefined
@@ -91,14 +131,7 @@ type FileDisplayProps = {
91131
export const FileDisplay: FC<FileDisplayProps> = ({ code, filename }) => {
92132
if (!code) return null
93133

94-
return (
95-
<CodeDisplay
96-
code={<StyledPre>{code}</StyledPre>}
97-
copyToClipboardValue={code}
98-
label={filename}
99-
extraTopPadding
100-
/>
101-
)
134+
return <CodeDisplay code={code} label={filename} extraTopPadding language="sol" />
102135
}
103136

104137
type JsonCodeDisplayProps = {
@@ -111,11 +144,6 @@ export const JsonCodeDisplay: FC<JsonCodeDisplayProps> = ({ data, label, floatin
111144
const formattedJson = JSON.stringify(data, null, 2)
112145

113146
return (
114-
<CodeDisplay
115-
code={<StyledPre>{formattedJson}</StyledPre>}
116-
copyToClipboardValue={formattedJson}
117-
label={label}
118-
floatingCopyButton={floatingCopyButton}
119-
/>
147+
<CodeDisplay code={formattedJson} label={label} floatingCopyButton={floatingCopyButton} language="json" />
120148
)
121149
}

src/app/components/ScrollableDataDisplay/index.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/app/pages/RuntimeTransactionDetailPage/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { getFiatCurrencyForScope, showFiatValues } from '../../../config'
3030
import { convertToNano, getGasPrice } from '../../utils/number-utils'
3131
import { useWantedTransaction } from '../../hooks/useWantedTransaction'
3232
import { MultipleTransactionsWarning } from '../../components/Transactions/MultipleTransactionsWarning'
33-
import { JsonCodeDisplay } from '../..//components/CodeDisplay'
33+
import { SimpleJsonCode } from '../../components/CodeDisplay/SimpleJsonCode'
3434
import { isRoflTransaction } from '../../utils/transaction'
3535
import Box from '@mui/material/Box'
3636
import { RoundedBalance } from 'app/components/RoundedBalance'
@@ -361,7 +361,7 @@ export const RuntimeTransactionDetailView: FC<{
361361
<>
362362
<dt>{t('transaction.rawData')}</dt>
363363
<dd>
364-
<JsonCodeDisplay data={transaction.body} floatingCopyButton />
364+
<SimpleJsonCode data={transaction.body} />
365365
</dd>
366366
</>
367367
)}

yarn.lock

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,6 +1713,20 @@
17131713
"@lezer/lr" "^0.15.4"
17141714
json5 "^2.2.1"
17151715

1716+
"@monaco-editor/loader@^1.5.0":
1717+
version "1.5.0"
1718+
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.5.0.tgz#dcdbc7fe7e905690fb449bed1c251769f325c55d"
1719+
integrity sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==
1720+
dependencies:
1721+
state-local "^1.0.6"
1722+
1723+
"@monaco-editor/react@^4.7.0":
1724+
version "4.7.0"
1725+
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.7.0.tgz#35a1ec01bfe729f38bfc025df7b7bac145602a60"
1726+
integrity sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==
1727+
dependencies:
1728+
"@monaco-editor/loader" "^1.5.0"
1729+
17161730
"@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2":
17171731
version "3.0.2"
17181732
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz#44d752c1a2dc113f15f781b7cc4f53a307e3fa38"
@@ -7452,6 +7466,11 @@ minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8:
74527466
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
74537467
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
74547468

7469+
monaco-editor@^0.52.2:
7470+
version "0.52.2"
7471+
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.52.2.tgz#53c75a6fcc6802684e99fd1b2700299857002205"
7472+
integrity sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==
7473+
74557474
ms@2.1.2:
74567475
version "2.1.2"
74577476
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@@ -8611,6 +8630,11 @@ stack-utils@^2.0.3:
86118630
dependencies:
86128631
escape-string-regexp "^2.0.0"
86138632

8633+
state-local@^1.0.6:
8634+
version "1.0.7"
8635+
resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5"
8636+
integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==
8637+
86148638
stop-iteration-iterator@^1.0.0:
86158639
version "1.0.0"
86168640
resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4"

0 commit comments

Comments
 (0)