packages/editor/src/Editor.tsx
import React, { ReactElement, useState, useRef, ChangeEvent } from 'react'
import { ChocolabTokens } from '@chocolab/algorithm-transpiler'
import Line from './Line'
import Comment from './keywords/Comment'
import Handler from './keywords/Handler'
import Number from './keywords/Number'
import String from './keywords/String'
import Text from './keywords/Text'
import Type from './keywords/Type'
import Background from './Background'
type EditorProps = {
readonly content?: string
readonly lang: ChocolabTokens
}
export default function Editor({ content, lang }: EditorProps): ReactElement {
// const [localContent, setLocalContet] = useState(content || '')
const [reRender, setReRender] = useState(false)
const localContent = useRef(tokenizer(content))
/**
* On input handler.
* @param v - Input event.
*/
function input(v: ChangeEvent<HTMLInputElement>): void {
const current = v.target.innerText
// eslint-disable-next-line functional/immutable-data
localContent.current = tokenizer(current)
// setReRender(!reRender)
}
function highlight(s: string): ReactElement {
const { comments, handlers, numbers, strings, types } = lang
if (comments[0] && comments.some((exp) => exp[0].test(s))) return <Comment dark>{s}</Comment>
if (handlers[0] && handlers.some((exp) => exp[0].test(s))) return <Handler dark>{s}</Handler>
if (numbers[0] && numbers.some((exp) => exp[0].test(s))) return <Number dark>{s}</Number>
if (strings[0] && strings.some((exp) => exp[0].test(s))) return <String dark>{s}</String>
if (types[0] && types.some((exp) => exp[0].test(s))) return <Type dark>{s}</Type>
return <Text>{s}</Text>
}
function tokenizeLine(line = ''): readonly ReactElement[] {
// eslint-disable-next-line functional/prefer-readonly-type
const res: ReactElement[] = []
let [space, ...spaces] = line.split(/[^ ]+/) // get space
let [word, ...words] = line.split(/ +/) // get words
if (space) res.push(<>{space}</>)
else [space, ...spaces] = spaces
// eslint-disable-next-line functional/no-loop-statement
while (space || spaces.length || word || words.length) {
if (word) {
res.push(highlight(word));
[word, ...words] = words
}
else [word, ...words] = words
if (space) {
res.push(<>{space}</>);
[space, ...spaces] = spaces
}
else [space, ...spaces] = spaces
}
return res
}
function tokenizer(s = ''): readonly ReactElement[] {
return s.split('\n').map((line) =>
<Line key={line} dark>{tokenizeLine(line)}</Line>)
// return s.replace(/(algorithm)/, '<span>$1</span>')
//
}
return (
<Background dark>
<div contentEditable onInput={input} style={{ marginLeft: 20, outline: '0px' }}>
{localContent.current}
</div>
</Background>
)
}