-
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new tools and refactor existing ones
- Loading branch information
Showing
29 changed files
with
1,125 additions
and
471 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { lintGutter } from '@codemirror/lint'; | ||
import { nordInit } from '@uiw/codemirror-theme-nord'; | ||
import CodeMirror, { | ||
EditorSelection, | ||
EditorView, | ||
Extension, | ||
ReactCodeMirrorRef, | ||
ViewUpdate | ||
} from '@uiw/react-codemirror'; | ||
import { FunctionComponent, useRef, useState } from 'react'; | ||
|
||
type parseError = { | ||
message: string; | ||
line?: number; | ||
column?: number; | ||
position?: number; | ||
}; | ||
|
||
const BaseEditor: FunctionComponent<{ | ||
value: string; | ||
lang: string; | ||
showValidMsg?: boolean; | ||
hideGoToLine?: boolean; | ||
editorExtensions: Extension[]; | ||
onErrorChange?: (value: string, view?: ViewUpdate) => parseError; | ||
onValueChange?: (value: string, view?: ViewUpdate) => void; | ||
}> = function ({ | ||
value, | ||
lang, | ||
showValidMsg, | ||
editorExtensions, | ||
hideGoToLine, | ||
onErrorChange, | ||
onValueChange | ||
}) { | ||
const [error, setError] = useState<parseError>(null); | ||
const editor = useRef<ReactCodeMirrorRef>(); | ||
|
||
const scrollDocToView = (error: parseError) => { | ||
if (!editor?.current?.state?.doc) { | ||
return; | ||
} | ||
const position = | ||
error.position !== undefined | ||
? error.position | ||
: editor.current.view.state.doc.line(error.line + 1).from + | ||
error.column; | ||
|
||
editor.current.view?.dispatch({ | ||
selection: EditorSelection.single(position, position), | ||
scrollIntoView: true | ||
}); | ||
}; | ||
|
||
return ( | ||
<div className='d-flex flex-column code-editor-container'> | ||
<CodeMirror | ||
theme={nordInit({ | ||
settings: { | ||
fontFamily: | ||
'"Fira Code", Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace' | ||
} | ||
})} | ||
extensions={[ | ||
EditorView.lineWrapping, | ||
lintGutter(), | ||
...editorExtensions | ||
]} | ||
basicSetup={{ lintKeymap: false }} | ||
height={'100%'} | ||
style={{ minHeight: '0' }} | ||
className={'h-100'} | ||
value={value} | ||
lang={lang} | ||
ref={editor} | ||
onChange={(value, view) => { | ||
if (onErrorChange) { | ||
setError(onErrorChange(value, view)); | ||
} | ||
|
||
if (onValueChange) { | ||
onValueChange(value, view); | ||
} | ||
}} | ||
></CodeMirror> | ||
{error && ( | ||
<div className='bg-danger-subtle border-start border-danger border-4 p-4 mt-4 position-relative d-flex justify-content-between'> | ||
<div | ||
style={{ | ||
whiteSpace: 'pre-wrap', | ||
fontFamily: | ||
'"Fira Code", Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace' | ||
}} | ||
> | ||
{error.message} | ||
</div> | ||
{!hideGoToLine && ( | ||
<div className='flex-shrink-0'> | ||
<a | ||
href='#' | ||
onClick={(a) => { | ||
a.preventDefault(); | ||
scrollDocToView(error); | ||
}} | ||
> | ||
Go to line | ||
</a> | ||
</div> | ||
)} | ||
</div> | ||
)} | ||
{!error && showValidMsg && ( | ||
<div className='bg-success-subtle border-start border-success border-4 p-4 my-4'> | ||
<div>{lang.toUpperCase()} is valid!</div> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default BaseEditor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { FunctionComponent } from 'react'; | ||
import BaseEditor from './base-editor'; | ||
|
||
export function base64ToBytes(base64) { | ||
const binString = atob(base64); | ||
return Uint8Array.from(binString, (m) => m.codePointAt(0)); | ||
} | ||
|
||
export function bytesToBase64(bytes) { | ||
const binString = String.fromCodePoint(...bytes); | ||
return btoa(binString); | ||
} | ||
|
||
const Base64Editor: FunctionComponent<{ | ||
value: string; | ||
showValidMsg?: boolean; | ||
onValueChange?: (value: string) => void; | ||
}> = function (props) { | ||
return ( | ||
<BaseEditor | ||
lang='text' | ||
hideGoToLine | ||
editorExtensions={[]} | ||
onErrorChange={(currentValue: string, viewUpdate) => { | ||
try { | ||
new TextDecoder().decode(base64ToBytes(currentValue)); | ||
|
||
return null; | ||
} catch (error) { | ||
return { | ||
message: error.message.replace( | ||
"Failed to execute 'atob' on 'Window': ", | ||
'' | ||
) | ||
}; | ||
} | ||
}} | ||
{...props} | ||
/> | ||
); | ||
}; | ||
|
||
export default Base64Editor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { json, jsonParseLinter } from '@codemirror/lang-json'; | ||
import { linter } from '@codemirror/lint'; | ||
import { Text } from '@uiw/react-codemirror'; | ||
import { FunctionComponent } from 'react'; | ||
import BaseEditor from './base-editor'; | ||
|
||
const jsonLinter = linter(jsonParseLinter(), { delay: 100 }); | ||
|
||
function getErrorPosition( | ||
error: SyntaxError, | ||
doc: Text | ||
): { line?: number; column?: number; position?: number } { | ||
let match; | ||
if ((match = error.message.match(/at position (\d+)/))) | ||
return { position: Math.min(+match[1], doc.length) }; | ||
if ((match = error.message.match(/at line (\d+) column (\d+)/))) | ||
return { | ||
position: Math.min(doc.line(+match[1]).from + +match[2] - 1, doc.length) | ||
}; | ||
|
||
return { | ||
position: 1 | ||
}; | ||
} | ||
|
||
const JsonEditor: FunctionComponent<{ | ||
value: string; | ||
showValidMsg?: boolean; | ||
onValueChange?: (value: string) => void; | ||
}> = function (props) { | ||
return ( | ||
<BaseEditor | ||
lang='json' | ||
editorExtensions={[json(), jsonLinter]} | ||
onErrorChange={(currentValue: string, viewUpdate) => { | ||
try { | ||
JSON.parse(currentValue); | ||
|
||
return null; | ||
} catch (error) { | ||
return { | ||
message: error.message, | ||
...getErrorPosition(error, viewUpdate.state.doc) | ||
}; | ||
} | ||
}} | ||
{...props} | ||
/> | ||
); | ||
}; | ||
|
||
export default JsonEditor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { FunctionComponent } from 'react'; | ||
import BaseEditor from './base-editor'; | ||
|
||
const TextEditor: FunctionComponent<{ | ||
value: string; | ||
showValidMsg?: boolean; | ||
onValueChange?: (value: string) => void; | ||
}> = function (props) { | ||
return <BaseEditor lang='text' editorExtensions={[]} {...props} />; | ||
}; | ||
|
||
export default TextEditor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { xml } from '@codemirror/lang-xml'; | ||
import { linter } from '@codemirror/lint'; | ||
import { Text } from '@uiw/react-codemirror'; | ||
import { XMLValidator } from 'fast-xml-parser'; | ||
import { FunctionComponent } from 'react'; | ||
import BaseEditor from './base-editor'; | ||
|
||
const xmlLinter = linter( | ||
(view) => { | ||
const validation = XMLValidator.validate(view.state.doc.toString()); | ||
|
||
if (validation !== true && validation.err) { | ||
const startChar = | ||
view.state.doc.line(validation.err.line).from + validation.err.col; | ||
|
||
return [ | ||
{ | ||
from: startChar, | ||
to: startChar, | ||
severity: 'error', | ||
message: validation.err.msg | ||
} | ||
]; | ||
} else { | ||
return []; | ||
} | ||
}, | ||
{ delay: 100 } | ||
); | ||
|
||
function getErrorPosition( | ||
error: SyntaxError, | ||
doc: Text | ||
): { line?: number; column?: number; position?: number } { | ||
let match; | ||
if ((match = error.message.match(/at position (\d+)/))) | ||
return { position: Math.min(+match[1], doc.length) }; | ||
if ((match = error.message.match(/at line (\d+) column (\d+)/))) | ||
return { | ||
position: Math.min(doc.line(+match[1]).from + +match[2] - 1, doc.length) | ||
}; | ||
|
||
return { | ||
position: 1 | ||
}; | ||
} | ||
|
||
const XmlEditor: FunctionComponent<{ | ||
value: string; | ||
showValidMsg?: boolean; | ||
onValueChange?: (value: string) => void; | ||
}> = function (props) { | ||
return ( | ||
<BaseEditor | ||
lang='xml' | ||
editorExtensions={[xml(), xmlLinter]} | ||
onErrorChange={(currentValue: string, viewUpdate) => { | ||
const validation = XMLValidator.validate(currentValue); | ||
|
||
if (validation !== true && validation.err) { | ||
const position = | ||
viewUpdate.state.doc.line(validation.err.line).from + | ||
validation.err.col; | ||
|
||
return { | ||
message: validation.err.msg, | ||
position | ||
}; | ||
} | ||
|
||
return null; | ||
}} | ||
{...props} | ||
/> | ||
); | ||
}; | ||
|
||
export default XmlEditor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { yaml } from '@codemirror/lang-yaml'; | ||
import { linter } from '@codemirror/lint'; | ||
import jsyaml from 'js-yaml'; | ||
import { FunctionComponent } from 'react'; | ||
import BaseEditor from './base-editor'; | ||
|
||
const yamlLinter = linter( | ||
(view) => { | ||
try { | ||
jsyaml.load(view.state.doc.toString()); | ||
|
||
return []; | ||
} catch (error) { | ||
// errors are 0-based, but codemirror is 1-based | ||
error.mark.line = error.mark.line + 1; | ||
|
||
let startChar: number; | ||
|
||
// some errors are reported on a final extra line. If this is the case, we put the cursor at the end of the previous line | ||
if (view.state.doc.lines < error.mark.line) { | ||
startChar = view.state.doc.line(error.mark.line - 1).to; | ||
} else { | ||
startChar = | ||
view.state.doc.line(error.mark.line).from + error.mark.column; | ||
} | ||
|
||
return [ | ||
{ | ||
from: startChar, | ||
to: startChar, | ||
severity: 'error', | ||
message: error.reason | ||
} | ||
]; | ||
} | ||
}, | ||
{ delay: 100 } | ||
); | ||
|
||
const YamlEditor: FunctionComponent<{ | ||
value: string; | ||
showValidMsg?: boolean; | ||
onValueChange?: (value: string) => void; | ||
}> = function (props) { | ||
return ( | ||
<BaseEditor | ||
lang='yaml' | ||
editorExtensions={[yaml(), yamlLinter]} | ||
onErrorChange={(currentValue: string) => { | ||
try { | ||
jsyaml.load(currentValue); | ||
|
||
return null; | ||
} catch (error) { | ||
return { | ||
message: error.message, | ||
line: error.mark.line, | ||
column: error.mark.column | ||
}; | ||
} | ||
}} | ||
{...props} | ||
/> | ||
); | ||
}; | ||
|
||
export default YamlEditor; |
Oops, something went wrong.