Skip to content

Commit

Permalink
feat: click line-number to highlight line
Browse files Browse the repository at this point in the history
Restores AceEditor functionality
  • Loading branch information
snorrees committed Jan 16, 2023
1 parent ef4fe48 commit 8e5cf07
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 193 deletions.
226 changes: 93 additions & 133 deletions src/CodeInput.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* eslint-disable react/jsx-handler-names */
import React, {Suspense, useCallback, useEffect, useMemo, useRef} from 'react'
import React, {Suspense, useCallback, useMemo} from 'react'
import {
FieldMember,
InputProps,
MemberField,
ObjectInputProps,
ObjectMember,
ObjectSchemaType,
RenderInputCallback,
set,
Expand All @@ -30,6 +31,7 @@ const EditorContainer = styled(Card)`
height: 250px;
overflow-y: auto;
`
const defaultMode = 'text'

/**
* @public
Expand Down Expand Up @@ -76,105 +78,19 @@ export function CodeInput(props: CodeInputProps) {
onPathFocus,
} = props

const editorRef = useRef<any>()

const fieldMembers = useMemo(
() => members.filter((member) => member.kind === 'field') as FieldMember[],
[members]
)

const languageFieldMember = fieldMembers.find((member) => member.name === 'language')
const filenameMember = fieldMembers.find((member) => member.name === 'filename')
const codeFieldMember = fieldMembers.find((member) => member.name === 'code')
const languageFieldMember = useFieldMember(members, 'language')
const filenameMember = useFieldMember(members, 'filename')
const codeFieldMember = useFieldMember(members, 'code')

const handleCodeFocus = useCallback(() => {
onPathFocus(PATH_CODE)
}, [onPathFocus])

const handleToggleSelectLine = useCallback(
(lineNumber: number) => {
const editorSession = editorRef.current?.editor?.getSession()
const backgroundMarkers = editorSession?.getMarkers(true)
const currentHighlightedLines = Object.keys(backgroundMarkers)
.filter((key) => backgroundMarkers[key].type === 'screenLine')
.map((key) => backgroundMarkers[key].range.start.row)
const currentIndex = currentHighlightedLines.indexOf(lineNumber)
if (currentIndex > -1) {
// toggle remove
currentHighlightedLines.splice(currentIndex, 1)
} else {
// toggle add
currentHighlightedLines.push(lineNumber)
currentHighlightedLines.sort()
}
onChange(
set(
currentHighlightedLines.map(
(line) =>
// ace starts at line (row) 0, but we store it starting at line 1
line + 1
),
['highlightedLines']
)
)
},
[editorRef, onChange]
const onHighlightChange = useCallback(
(lines: number[]) => onChange(set(lines, ['highlightedLines'])),
[onChange]
)

const handleGutterMouseDown = useCallback(
(event: any) => {
const target = event.domEvent.target
if (target.classList.contains('ace_gutter-cell')) {
const row = event.getDocumentPosition().row
handleToggleSelectLine(row)
}
},
[handleToggleSelectLine]
)

useEffect(() => {
const editor = editorRef?.current?.editor
return () => {
editor?.session?.removeListener('guttermousedown', handleGutterMouseDown)
}
}, [editorRef, handleGutterMouseDown])

const handleEditorLoad = useCallback(
(editor: any) => {
editor?.on('guttermousedown', handleGutterMouseDown)
},
[handleGutterMouseDown]
)

const getLanguageAlternatives = useCallback((): CodeInputLanguage[] => {
const languageAlternatives = type.options?.languageAlternatives
if (!languageAlternatives) {
return SUPPORTED_LANGUAGES
}

if (!Array.isArray(languageAlternatives)) {
throw new Error(
`'options.languageAlternatives' should be an array, got ${typeof languageAlternatives}`
)
}

return languageAlternatives.reduce((acc: CodeInputLanguage[], {title, value: val, mode}) => {
const alias = LANGUAGE_ALIASES[val]
if (alias) {
// eslint-disable-next-line no-console
console.warn(
`'options.languageAlternatives' lists a language with value "%s", which is an alias of "%s" - please replace the value to read "%s"`,
val,
alias,
alias
)

return acc.concat({title, value: alias, mode: mode})
}
return acc.concat({title, value: val, mode})
}, [])
}, [type])

const handleCodeChange = useCallback(
(code: string) => {
const path = PATH_CODE
Expand All @@ -187,38 +103,14 @@ export function CodeInput(props: CodeInputProps) {
},
[onChange, type]
)

const languages = getLanguageAlternatives().slice()

const languages = useLanguageAlternatives(props.schemaType)
const fixedLanguage = type.options?.language

const language = value?.language || fixedLanguage

// the language config from the schema
const configured = languages.find((entry) => entry.value === language)

const mode = configured?.mode ?? resolveAliasedLanguage(language) ?? 'text'

const renderLanguageInput = useCallback(
(inputProps: Omit<InputProps, 'renderDefault'>) => {
return (
<Select
{...(inputProps as StringInputProps)}
onChange={(e) => {
const newValue = e.currentTarget.value
inputProps.onChange(newValue ? set(newValue) : unset())
}}
>
{languages.map((lang: {title: string; value: string}) => (
<option key={lang.value} value={lang.value}>
{lang.title}
</option>
))}
</Select>
)
},
[languages]
)
const mode = configured?.mode ?? resolveAliasedLanguage(language) ?? defaultMode

const CodeMirror = useCodeMirror()

Expand All @@ -230,14 +122,10 @@ export function CodeInput(props: CodeInputProps) {
<Suspense fallback={<Card padding={2}>Loading code editor...</Card>}>
<CodeMirror
mode={mode}
ref={editorRef}
onChange={handleCodeChange}
value={inputProps.value as string}
/* markers={
value && value.highlightedLines
? createHighlightMarkers(value.highlightedLines)
: undefined
}*/
highlightLines={value?.highlightedLines}
onHighlightChange={onHighlightChange}
readOnly={readOnly}
onFocus={handleCodeFocus}
onBlur={elementProps.onBlur}
Expand All @@ -251,7 +139,7 @@ export function CodeInput(props: CodeInputProps) {
CodeMirror,
handleCodeChange,
handleCodeFocus,
handleEditorLoad,
onHighlightChange,
mode,
elementProps.onBlur,
readOnly,
Expand All @@ -262,13 +150,7 @@ export function CodeInput(props: CodeInputProps) {
return (
<Stack space={4}>
{languageFieldMember && (
<MemberField
member={languageFieldMember}
renderItem={renderItem}
renderField={renderField}
renderInput={renderLanguageInput}
renderPreview={renderPreview}
/>
<LanguageField {...props} member={languageFieldMember} languages={languages} />
)}

{type.options?.withFilename && filenameMember && (
Expand All @@ -293,3 +175,81 @@ export function CodeInput(props: CodeInputProps) {
</Stack>
)
}

function LanguageField(
props: CodeInputProps & {member: FieldMember; languages: CodeInputLanguage[]}
) {
const {member, languages, renderItem, renderField, renderPreview} = props
const renderLanguageInput = useCallback(
(inputProps: Omit<InputProps, 'renderDefault'>) => {
return (
<Select
{...(inputProps as StringInputProps)}
value={(inputProps as StringInputProps).value ?? defaultMode}
onChange={(e) => {
const newValue = e.currentTarget.value
inputProps.onChange(newValue ? set(newValue) : unset())
}}
>
{languages.map((lang: {title: string; value: string}) => (
<option key={lang.value} value={lang.value}>
{lang.title}
</option>
))}
</Select>
)
},
[languages]
)

return (
<MemberField
member={member}
renderItem={renderItem}
renderField={renderField}
renderInput={renderLanguageInput}
renderPreview={renderPreview}
/>
)
}

function useFieldMember(members: ObjectMember[], fieldName: string) {
return useMemo(
() =>
members.find(
(member): member is FieldMember => member.kind === 'field' && member.name === fieldName
),
[members, fieldName]
)
}

function useLanguageAlternatives(type: CodeSchemaType) {
return useMemo((): CodeInputLanguage[] => {
const languageAlternatives = type.options?.languageAlternatives
if (!languageAlternatives) {
return SUPPORTED_LANGUAGES
}

if (!Array.isArray(languageAlternatives)) {
throw new Error(
`'options.languageAlternatives' should be an array, got ${typeof languageAlternatives}`
)
}

return languageAlternatives.reduce((acc: CodeInputLanguage[], {title, value: val, mode}) => {
const alias = LANGUAGE_ALIASES[val]
if (alias) {
// eslint-disable-next-line no-console
console.warn(
`'options.languageAlternatives' lists a language with value "%s", which is an alias of "%s" - please replace the value to read "%s"`,
val,
alias,
alias
)

return acc.concat({title, value: alias, mode: mode})
}
return acc.concat({title, value: val, mode})
}, [])
}, [type])
}
58 changes: 0 additions & 58 deletions src/codemirror/CodeMirrorLazyLanguage.tsx

This file was deleted.

Loading

0 comments on commit 8e5cf07

Please sign in to comment.