2021-05-20 16:17:54 +01:00
|
|
|
import { useState, useRef } from 'react';
|
2021-03-27 11:49:46 +00:00
|
|
|
import styled from 'styled-components';
|
2021-03-26 22:00:12 +00:00
|
|
|
import ReactEditor from 'react-simple-code-editor';
|
2021-03-27 11:49:46 +00:00
|
|
|
import EditorPrismStyle from './EditorPrismStyle';
|
2021-04-02 18:54:37 +01:00
|
|
|
import { getHighlighter } from '../util/highlighting';
|
2021-03-26 22:00:12 +00:00
|
|
|
|
2021-03-27 14:17:28 +00:00
|
|
|
export default function EditorTextArea({ code, setCode, language, fontSize }) {
|
2021-03-26 22:00:12 +00:00
|
|
|
const highlight = getHighlighter(language);
|
|
|
|
|
|
|
|
|
|
function highlightWithLineNumbers(input, grammar) {
|
|
|
|
|
return highlight(input, grammar)
|
|
|
|
|
.split('\n')
|
|
|
|
|
.map((line, i) => `<span class='editorLineNumber'>${i + 1}</span>${line}`)
|
|
|
|
|
.join('\n');
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-20 16:17:54 +01:00
|
|
|
const lastBracketState = useState(null);
|
2021-03-28 17:39:16 +01:00
|
|
|
const editorRef = useRef();
|
|
|
|
|
function keydown(e) {
|
2021-05-20 16:17:54 +01:00
|
|
|
handleKeydown(e, editorRef.current, lastBracketState);
|
2021-03-28 17:39:16 +01:00
|
|
|
}
|
|
|
|
|
|
2021-03-26 22:00:12 +00:00
|
|
|
return (
|
2021-03-27 11:49:46 +00:00
|
|
|
<EditorPrismStyle>
|
|
|
|
|
<StyledReactEditor
|
2021-03-28 17:39:16 +01:00
|
|
|
ref={editorRef}
|
2021-03-26 22:00:12 +00:00
|
|
|
value={code}
|
|
|
|
|
onValueChange={setCode}
|
|
|
|
|
highlight={highlightWithLineNumbers}
|
2021-03-27 20:05:15 +00:00
|
|
|
placeholder={'Paste (or type) some code...'}
|
2021-03-26 22:00:12 +00:00
|
|
|
padding={10}
|
2021-03-27 14:17:28 +00:00
|
|
|
size={fontSize}
|
2021-04-02 13:05:15 +01:00
|
|
|
textareaId="code-area"
|
2021-03-27 21:41:08 +00:00
|
|
|
autoFocus={true}
|
2021-03-28 17:39:16 +01:00
|
|
|
onKeyDown={keydown}
|
2021-03-26 22:00:12 +00:00
|
|
|
/>
|
2021-03-27 11:49:46 +00:00
|
|
|
</EditorPrismStyle>
|
2021-04-02 13:05:15 +01:00
|
|
|
);
|
2021-03-26 22:00:12 +00:00
|
|
|
}
|
2021-03-27 11:49:46 +00:00
|
|
|
|
|
|
|
|
const StyledReactEditor = styled(ReactEditor)`
|
|
|
|
|
counter-reset: line;
|
2021-03-27 14:17:28 +00:00
|
|
|
font-size: ${props => props.size}px;
|
2021-03-27 11:49:46 +00:00
|
|
|
outline: 0;
|
2021-03-27 14:17:28 +00:00
|
|
|
min-height: calc(100vh - 2rem);
|
2021-03-27 11:49:46 +00:00
|
|
|
|
|
|
|
|
#code-area {
|
|
|
|
|
outline: none;
|
|
|
|
|
padding-left: 60px !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pre {
|
|
|
|
|
padding-left: 60px !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.editorLineNumber {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 0px;
|
|
|
|
|
color: ${props => props.theme.editor.lineNumber};
|
|
|
|
|
text-align: right;
|
|
|
|
|
width: 40px;
|
|
|
|
|
font-weight: 100;
|
2021-03-27 21:41:08 +00:00
|
|
|
user-select: none;
|
2021-03-27 11:49:46 +00:00
|
|
|
}
|
|
|
|
|
`;
|
2021-03-28 17:39:16 +01:00
|
|
|
|
|
|
|
|
const KEYCODE_ENTER = 13;
|
|
|
|
|
const KEYCODE_PARENS = 57;
|
2021-05-20 16:17:54 +01:00
|
|
|
const KEYCODE_PARENS_CLOSE = 48;
|
2021-03-28 17:39:16 +01:00
|
|
|
const KEYCODE_BRACKETS = 219;
|
2021-05-20 16:17:54 +01:00
|
|
|
const KEYCODE_BRACKETS_CLOSE = 221;
|
2021-03-28 17:39:16 +01:00
|
|
|
const KEYCODE_QUOTE = 222;
|
|
|
|
|
const KEYCODE_BACK_QUOTE = 192;
|
|
|
|
|
|
2021-05-20 16:17:54 +01:00
|
|
|
function getPair({ keyCode, shiftKey }) {
|
|
|
|
|
if (keyCode === KEYCODE_PARENS && shiftKey) {
|
2021-03-28 17:39:16 +01:00
|
|
|
return ['(', ')'];
|
2021-05-20 16:17:54 +01:00
|
|
|
} else if (keyCode === KEYCODE_BRACKETS) {
|
|
|
|
|
if (shiftKey) {
|
2021-03-28 17:39:16 +01:00
|
|
|
return ['{', '}'];
|
|
|
|
|
} else {
|
|
|
|
|
return ['[', ']'];
|
|
|
|
|
}
|
2021-05-20 16:17:54 +01:00
|
|
|
} else if (keyCode === KEYCODE_QUOTE) {
|
|
|
|
|
if (shiftKey) {
|
2021-03-28 17:39:16 +01:00
|
|
|
return ['"', '"'];
|
|
|
|
|
} else {
|
|
|
|
|
return ["'", "'"];
|
|
|
|
|
}
|
2021-05-20 16:17:54 +01:00
|
|
|
} else if (keyCode === KEYCODE_BACK_QUOTE && !shiftKey) {
|
2021-03-28 17:39:16 +01:00
|
|
|
return ['`', '`'];
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-20 16:17:54 +01:00
|
|
|
function handleKeydown(e, editor, [autoBracket, setAutoBracket]) {
|
2021-03-28 17:39:16 +01:00
|
|
|
const { value, selectionStart, selectionEnd } = e.target;
|
|
|
|
|
if (selectionStart !== selectionEnd) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-20 16:17:54 +01:00
|
|
|
// If the user types a closing bracket explictly, just jump to after the automatically added one
|
|
|
|
|
if (
|
|
|
|
|
selectionStart !== 0 &&
|
|
|
|
|
autoBracket === e.key &&
|
|
|
|
|
(e.keyCode === KEYCODE_BRACKETS_CLOSE || e.keyCode === KEYCODE_PARENS_CLOSE)
|
|
|
|
|
) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
editor._applyEdits({
|
|
|
|
|
value: value,
|
|
|
|
|
selectionStart: selectionStart + 1,
|
|
|
|
|
selectionEnd: selectionStart + 1,
|
|
|
|
|
});
|
|
|
|
|
setAutoBracket(null);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reset auto brackets
|
|
|
|
|
setAutoBracket(null);
|
|
|
|
|
|
2021-03-28 17:39:16 +01:00
|
|
|
// When entering an open bracket/quote, add the closing one
|
|
|
|
|
const pair = getPair(e);
|
|
|
|
|
if (pair) {
|
2021-05-20 16:17:54 +01:00
|
|
|
// don't add double apostrophes if it looks like a sentence
|
|
|
|
|
if (
|
|
|
|
|
e.keyCode === KEYCODE_QUOTE &&
|
|
|
|
|
!e.shiftKey &&
|
|
|
|
|
selectionStart !== 0 &&
|
|
|
|
|
/[a-zA-Z]/.test(value.charAt(selectionStart - 1))
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-28 17:39:16 +01:00
|
|
|
e.preventDefault();
|
|
|
|
|
editor._applyEdits({
|
2021-04-02 13:05:15 +01:00
|
|
|
value:
|
|
|
|
|
value.substring(0, selectionStart) +
|
|
|
|
|
pair[0] +
|
|
|
|
|
pair[1] +
|
|
|
|
|
value.substring(selectionEnd),
|
2021-03-28 17:39:16 +01:00
|
|
|
selectionStart: selectionStart + 1,
|
2021-04-02 13:05:15 +01:00
|
|
|
selectionEnd: selectionStart + 1,
|
2021-03-28 17:39:16 +01:00
|
|
|
});
|
2021-05-20 16:17:54 +01:00
|
|
|
setAutoBracket(pair[1]);
|
2021-03-28 17:39:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When pressing enter immediately after an open bracket, automatically add a newline plus extra indent
|
2021-04-02 13:05:15 +01:00
|
|
|
if (
|
|
|
|
|
e.keyCode === KEYCODE_ENTER &&
|
|
|
|
|
selectionEnd !== 0 &&
|
|
|
|
|
value[selectionEnd - 1] === '{'
|
|
|
|
|
) {
|
2021-03-28 17:39:16 +01:00
|
|
|
const line = editor._getLines(value, selectionStart).pop();
|
|
|
|
|
const matches = line.match(/^\s+/);
|
2021-04-02 13:05:15 +01:00
|
|
|
const existingIndent = matches ? matches[0] : '';
|
2021-03-28 17:39:16 +01:00
|
|
|
|
|
|
|
|
const indent = ' ';
|
2021-04-02 13:05:15 +01:00
|
|
|
const updatedValue =
|
|
|
|
|
value.substring(0, selectionStart) +
|
|
|
|
|
'\n' +
|
|
|
|
|
existingIndent +
|
|
|
|
|
indent +
|
|
|
|
|
'\n' +
|
|
|
|
|
existingIndent +
|
|
|
|
|
value.substring(selectionEnd);
|
|
|
|
|
const updatedSelection =
|
|
|
|
|
selectionStart + 1 /* newline */ + existingIndent.length + indent.length;
|
2021-03-28 17:39:16 +01:00
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
editor._applyEdits({
|
|
|
|
|
value: updatedValue,
|
|
|
|
|
selectionStart: updatedSelection,
|
2021-04-02 13:05:15 +01:00
|
|
|
selectionEnd: updatedSelection,
|
|
|
|
|
});
|
2021-03-28 17:39:16 +01:00
|
|
|
}
|
|
|
|
|
}
|