import { useState, useRef } from 'react'; import styled from 'styled-components'; import ReactEditor from 'react-simple-code-editor'; import EditorPrismStyle from './EditorPrismStyle'; import { getHighlighter } from '../util/highlighting'; export default function EditorTextArea({ code, setCode, language, fontSize }) { const highlight = getHighlighter(language); function highlightWithLineNumbers(input, grammar) { return highlight(input, grammar) .split('\n') .map((line, i) => `${i + 1}${line}`) .join('\n'); } const lastBracketState = useState(null); const editorRef = useRef(); function keydown(e) { handleKeydown(e, editorRef.current, lastBracketState); } return ( ); } const StyledReactEditor = styled(ReactEditor)` counter-reset: line; font-size: ${props => props.size}px; outline: 0; min-height: calc(100vh - 2rem); #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; user-select: none; } `; const KEYCODE_ENTER = 13; const KEYCODE_PARENS = 57; const KEYCODE_PARENS_CLOSE = 48; const KEYCODE_BRACKETS = 219; const KEYCODE_BRACKETS_CLOSE = 221; const KEYCODE_QUOTE = 222; const KEYCODE_BACK_QUOTE = 192; function getPair({ keyCode, shiftKey }) { if (keyCode === KEYCODE_PARENS && shiftKey) { return ['(', ')']; } else if (keyCode === KEYCODE_BRACKETS) { if (shiftKey) { return ['{', '}']; } else { return ['[', ']']; } } else if (keyCode === KEYCODE_QUOTE) { if (shiftKey) { return ['"', '"']; } else { return ["'", "'"]; } } else if (keyCode === KEYCODE_BACK_QUOTE && !shiftKey) { return ['`', '`']; } return null; } function handleKeydown(e, editor, [autoBracket, setAutoBracket]) { const { value, selectionStart, selectionEnd } = e.target; if (selectionStart !== selectionEnd) { return; } // 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); // When entering an open bracket/quote, add the closing one const pair = getPair(e); if (pair) { // 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; } e.preventDefault(); editor._applyEdits({ value: value.substring(0, selectionStart) + pair[0] + pair[1] + value.substring(selectionEnd), selectionStart: selectionStart + 1, selectionEnd: selectionStart + 1, }); setAutoBracket(pair[1]); } // When pressing enter immediately after an open bracket, automatically add a newline plus extra indent if ( e.keyCode === KEYCODE_ENTER && selectionEnd !== 0 && value[selectionEnd - 1] === '{' ) { const line = editor._getLines(value, selectionStart).pop(); const matches = line.match(/^\s+/); const existingIndent = matches ? matches[0] : ''; const indent = ' '; const updatedValue = value.substring(0, selectionStart) + '\n' + existingIndent + indent + '\n' + existingIndent + value.substring(selectionEnd); const updatedSelection = selectionStart + 1 /* newline */ + existingIndent.length + indent.length; e.preventDefault(); editor._applyEdits({ value: updatedValue, selectionStart: updatedSelection, selectionEnd: updatedSelection, }); } }