2023-04-13 04:21:25 -04:00
|
|
|
import {
|
|
|
|
acceptCompletion,
|
|
|
|
autocompletion,
|
|
|
|
closeCompletion,
|
|
|
|
moveCompletionSelection,
|
|
|
|
startCompletion,
|
|
|
|
Completion,
|
|
|
|
} from '@codemirror/autocomplete'
|
|
|
|
import { EditorView, keymap } from '@codemirror/view'
|
2024-10-08 08:18:49 -04:00
|
|
|
import {
|
|
|
|
Compartment,
|
|
|
|
Extension,
|
|
|
|
Prec,
|
|
|
|
TransactionSpec,
|
|
|
|
} from '@codemirror/state'
|
|
|
|
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
|
|
|
|
|
|
|
const moduleExtensions: Array<(options: Record<string, any>) => Extension> =
|
|
|
|
importOverleafModules('autoCompleteExtensions').map(
|
|
|
|
(item: { import: { extension: Extension } }) => item.import.extension
|
|
|
|
)
|
2023-04-13 04:21:25 -04:00
|
|
|
|
|
|
|
const autoCompleteConf = new Compartment()
|
|
|
|
|
2024-10-08 08:18:49 -04:00
|
|
|
type AutoCompleteOptions = {
|
|
|
|
enabled: boolean
|
|
|
|
} & Record<string, any>
|
|
|
|
|
|
|
|
export const autoComplete = ({ enabled, ...rest }: AutoCompleteOptions) =>
|
|
|
|
autoCompleteConf.of(createAutoComplete({ enabled, ...rest }))
|
2023-04-13 04:21:25 -04:00
|
|
|
|
2024-10-08 08:18:49 -04:00
|
|
|
export const setAutoComplete = ({
|
|
|
|
enabled,
|
|
|
|
...rest
|
|
|
|
}: AutoCompleteOptions): TransactionSpec => {
|
2023-04-13 04:21:25 -04:00
|
|
|
return {
|
2024-10-08 08:18:49 -04:00
|
|
|
effects: autoCompleteConf.reconfigure(
|
|
|
|
createAutoComplete({ enabled, ...rest })
|
|
|
|
),
|
2023-04-13 04:21:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-08 08:18:49 -04:00
|
|
|
const createAutoComplete = ({ enabled, ...rest }: AutoCompleteOptions) => {
|
2023-04-13 04:21:25 -04:00
|
|
|
if (!enabled) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
return [
|
|
|
|
[
|
|
|
|
autocompleteTheme,
|
2023-06-08 04:35:51 -04:00
|
|
|
/**
|
|
|
|
* A built-in extension which provides the autocomplete feature,
|
|
|
|
* configured with a custom render function and
|
|
|
|
* a zero interaction delay (so that keypresses are handled after the autocomplete is opened).
|
|
|
|
*/
|
2023-04-13 04:21:25 -04:00
|
|
|
autocompletion({
|
|
|
|
icons: false,
|
|
|
|
defaultKeymap: false,
|
|
|
|
addToOptions: [
|
|
|
|
// display the completion "type" at the end of the suggestion
|
|
|
|
{
|
|
|
|
render: completion => {
|
|
|
|
const span = document.createElement('span')
|
|
|
|
span.classList.add('ol-cm-completionType')
|
|
|
|
if (completion.type) {
|
|
|
|
span.textContent = completion.type
|
|
|
|
}
|
|
|
|
return span
|
|
|
|
},
|
|
|
|
position: 400,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
optionClass: (completion: Completion) => {
|
|
|
|
return `ol-cm-completion-${completion.type}`
|
|
|
|
},
|
|
|
|
interactionDelay: 0,
|
|
|
|
}),
|
2023-06-08 04:35:51 -04:00
|
|
|
/**
|
|
|
|
* A keymap which adds Tab for accepting a completion and Ctrl-Space for opening autocomplete.
|
|
|
|
*/
|
2023-04-13 04:21:25 -04:00
|
|
|
Prec.highest(
|
|
|
|
keymap.of([
|
|
|
|
{ key: 'Escape', run: closeCompletion },
|
|
|
|
{ key: 'ArrowDown', run: moveCompletionSelection(true) },
|
|
|
|
{ key: 'ArrowUp', run: moveCompletionSelection(false) },
|
|
|
|
{ key: 'PageDown', run: moveCompletionSelection(true, 'page') },
|
|
|
|
{ key: 'PageUp', run: moveCompletionSelection(false, 'page') },
|
|
|
|
{ key: 'Enter', run: acceptCompletion },
|
|
|
|
{ key: 'Tab', run: acceptCompletion },
|
|
|
|
])
|
|
|
|
),
|
2023-06-08 04:35:51 -04:00
|
|
|
/**
|
|
|
|
* A keymap which positions Ctrl-Space and Alt-Space below the corresponding bindings for advanced reference search.
|
|
|
|
*/
|
2023-04-13 04:21:25 -04:00
|
|
|
Prec.high(
|
|
|
|
keymap.of([
|
|
|
|
{ key: 'Ctrl-Space', run: startCompletion },
|
|
|
|
{ key: 'Alt-Space', run: startCompletion },
|
|
|
|
])
|
|
|
|
),
|
|
|
|
],
|
2024-10-08 08:18:49 -04:00
|
|
|
moduleExtensions.map(extension => extension({ ...rest })),
|
2023-04-13 04:21:25 -04:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2024-06-18 06:00:17 -04:00
|
|
|
const AUTOCOMPLETE_LINE_HEIGHT = 1.4
|
2023-06-08 04:35:51 -04:00
|
|
|
/**
|
|
|
|
* Styles for the autocomplete menu
|
|
|
|
*/
|
2023-04-13 04:21:25 -04:00
|
|
|
const autocompleteTheme = EditorView.baseTheme({
|
|
|
|
'.cm-tooltip.cm-tooltip-autocomplete': {
|
|
|
|
// shift the tooltip, so the completion aligns with the text
|
|
|
|
marginLeft: '-4px',
|
|
|
|
},
|
2023-05-10 07:59:49 -04:00
|
|
|
'&light .cm-tooltip.cm-tooltip-autocomplete, &light .cm-tooltip.cm-completionInfo':
|
|
|
|
{
|
|
|
|
border: '1px lightgray solid',
|
|
|
|
background: '#fefefe',
|
|
|
|
color: '#111',
|
|
|
|
boxShadow: '2px 3px 5px rgb(0 0 0 / 20%)',
|
|
|
|
},
|
|
|
|
'&dark .cm-tooltip.cm-tooltip-autocomplete, &dark .cm-tooltip.cm-completionInfo':
|
|
|
|
{
|
|
|
|
border: '1px #484747 solid',
|
|
|
|
boxShadow: '2px 3px 5px rgba(0, 0, 0, 0.51)',
|
|
|
|
background: '#25282c',
|
|
|
|
color: '#c1c1c1',
|
|
|
|
},
|
2023-04-13 04:21:25 -04:00
|
|
|
|
|
|
|
// match editor font family and font size, so the completion aligns with the text
|
|
|
|
'.cm-tooltip.cm-tooltip-autocomplete > ul': {
|
|
|
|
fontFamily: 'var(--source-font-family)',
|
|
|
|
fontSize: 'var(--font-size)',
|
|
|
|
},
|
|
|
|
'.cm-tooltip.cm-tooltip-autocomplete li[role="option"]': {
|
|
|
|
display: 'flex',
|
|
|
|
justifyContent: 'space-between',
|
2024-06-18 06:00:17 -04:00
|
|
|
lineHeight: AUTOCOMPLETE_LINE_HEIGHT, // increase the line height from default 1.2, for a larger target area
|
2023-04-13 04:21:25 -04:00
|
|
|
outline: '1px solid transparent',
|
|
|
|
},
|
2024-06-18 06:00:17 -04:00
|
|
|
'.cm-tooltip .cm-completionDetail': {
|
|
|
|
flex: '1 0 auto',
|
|
|
|
fontSize: 'calc(var(--font-size) * 1.4)',
|
|
|
|
lineHeight: `calc(var(--font-size) * ${AUTOCOMPLETE_LINE_HEIGHT})`,
|
|
|
|
overflow: 'hidden',
|
|
|
|
// By default CodeMirror styles the details as italic
|
|
|
|
fontStyle: 'normal !important',
|
|
|
|
// We use this element for the symbol palette, so change the font to the
|
|
|
|
// symbol palette font
|
|
|
|
fontFamily: "'Stix Two Math', serif",
|
|
|
|
},
|
2023-04-13 04:21:25 -04:00
|
|
|
'&light .cm-tooltip.cm-tooltip-autocomplete li[role="option"]:hover': {
|
|
|
|
outlineColor: '#abbffe',
|
|
|
|
backgroundColor: 'rgba(233, 233, 253, 0.4)',
|
|
|
|
},
|
|
|
|
'&dark .cm-tooltip.cm-tooltip-autocomplete li[role="option"]:hover': {
|
|
|
|
outlineColor: 'rgba(109, 150, 13, 0.8)',
|
|
|
|
backgroundColor: 'rgba(58, 103, 78, 0.62)',
|
|
|
|
},
|
|
|
|
'.cm-tooltip.cm-tooltip-autocomplete ul li[aria-selected]': {
|
|
|
|
color: 'inherit',
|
|
|
|
},
|
|
|
|
'&light .cm-tooltip.cm-tooltip-autocomplete ul li[aria-selected]': {
|
|
|
|
background: '#cad6fa',
|
|
|
|
},
|
|
|
|
'&dark .cm-tooltip.cm-tooltip-autocomplete ul li[aria-selected]': {
|
|
|
|
background: '#3a674e',
|
|
|
|
},
|
|
|
|
'.cm-completionMatchedText': {
|
|
|
|
textDecoration: 'none', // remove default underline,
|
|
|
|
},
|
|
|
|
'&light .cm-completionMatchedText': {
|
|
|
|
color: '#2d69c7',
|
|
|
|
},
|
|
|
|
'&dark .cm-completionMatchedText': {
|
|
|
|
color: '#93ca12',
|
|
|
|
},
|
|
|
|
'.ol-cm-completionType': {
|
|
|
|
paddingLeft: '1em',
|
|
|
|
paddingRight: 0,
|
|
|
|
width: 'auto',
|
|
|
|
fontSize: '90%',
|
|
|
|
fontFamily: 'var(--source-font-family)',
|
|
|
|
opacity: '0.5',
|
|
|
|
},
|
|
|
|
'.cm-completionInfo .ol-cm-symbolCompletionInfo': {
|
|
|
|
margin: 0,
|
|
|
|
whiteSpace: 'normal',
|
|
|
|
display: 'flex',
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'center',
|
|
|
|
textAlign: 'center',
|
|
|
|
},
|
|
|
|
'.cm-completionInfo .ol-cm-symbolCharacter': {
|
|
|
|
fontSize: '32px',
|
|
|
|
},
|
|
|
|
})
|