Migrate extension to @overleaf/codemirror-tree-view (#15769)

GitOrigin-RevId: 8355158040923a55dafc7b1c3f566f7e7703cb02
This commit is contained in:
Alf Eaton 2023-11-17 12:25:52 +00:00 committed by Copybot
parent bf6257af80
commit cdd9ff9ed0
3 changed files with 29 additions and 243 deletions

20
package-lock.json generated
View file

@ -7637,6 +7637,17 @@
"resolved": "services/clsi-perf",
"link": true
},
"node_modules/@overleaf/codemirror-tree-view": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@overleaf/codemirror-tree-view/-/codemirror-tree-view-0.1.3.tgz",
"integrity": "sha512-/ysOnX+ovObqj0uR78tumQtK/y0qFwbawcCGxT9JDeyJPgfPrK3PYTIvoZ1SgmSxaXpOPkds7aL+4Hv6VWZqSw==",
"dev": true,
"peerDependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/@overleaf/contacts": {
"resolved": "services/contacts",
"link": true
@ -39635,6 +39646,7 @@
"@opentelemetry/sdk-trace-base": "^1.15.2",
"@opentelemetry/sdk-trace-web": "^1.15.2",
"@opentelemetry/semantic-conventions": "^1.15.2",
"@overleaf/codemirror-tree-view": "^0.1.3",
"@overleaf/ranges-tracker": "*",
"@overleaf/stream-utils": "*",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
@ -46393,6 +46405,13 @@
}
}
},
"@overleaf/codemirror-tree-view": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@overleaf/codemirror-tree-view/-/codemirror-tree-view-0.1.3.tgz",
"integrity": "sha512-/ysOnX+ovObqj0uR78tumQtK/y0qFwbawcCGxT9JDeyJPgfPrK3PYTIvoZ1SgmSxaXpOPkds7aL+4Hv6VWZqSw==",
"dev": true,
"requires": {}
},
"@overleaf/contacts": {
"version": "file:services/contacts",
"requires": {
@ -47778,6 +47797,7 @@
"@opentelemetry/sdk-trace-web": "^1.15.2",
"@opentelemetry/semantic-conventions": "^1.15.2",
"@overleaf/access-token-encryptor": "*",
"@overleaf/codemirror-tree-view": "^0.1.3",
"@overleaf/fetch-utils": "*",
"@overleaf/logger": "*",
"@overleaf/metrics": "*",

View file

@ -1,22 +1,8 @@
import {
Annotation,
Compartment,
EditorSelection,
EditorState,
StateEffect,
StateField,
Transaction,
} from '@codemirror/state'
import {
Decoration,
DecorationSet,
EditorView,
ViewPlugin,
} from '@codemirror/view'
import { syntaxTree } from '@codemirror/language'
import { toggleVisualEffect } from '../../extensions/visual/visual'
import { hasLanguageLoadedEffect } from '../../extensions/language'
import customLocalStorage from '../../../../infrastructure/local-storage'
import { Compartment } from '@codemirror/state'
import { EditorView, ViewPlugin } from '@codemirror/view'
import { treeView } from '@overleaf/codemirror-tree-view'
// to enable: window.localStorage.setItem('cm6-dev-tools', '"on"')
// to disable: window.localStorage.removeItem('cm6-dev-tools')
@ -90,233 +76,12 @@ const toggleDevTools = () => {
}
}
const createExtension = () =>
isActive() ? [devToolsView, highlightSelectedNode, devToolsTheme] : []
const devToolsTheme = EditorView.baseTheme({
'.ol-cm-dev-tools-container': {
padding: '8px 8px 0',
backgroundColor: '#222',
color: '#eee',
fontSize: '13px',
flexShrink: '0',
fontFamily: '"SF Mono", monospace',
height: 'calc(100% - 32px)',
overflow: 'auto',
position: 'absolute',
const treeViewTheme = EditorView.baseTheme({
// note: duplicate selector to ensure extension theme styles are overriden
'.cm-tree-view-container.cm-tree-view-container': {
top: '32px',
right: 0,
width: '50%',
},
'.ol-cm-dev-tools-item': {
cursor: 'pointer',
borderTop: '2px solid transparent',
borderBottom: '2px solid transparent',
scrollMargin: '2em',
},
'.ol-cm-selected-node-highlight': {
backgroundColor: 'yellow',
},
'.ol-cm-dev-tools-covered-item': {
backgroundColor: 'rgba(255, 255, 0, 0.2)',
},
'.ol-cm-dev-tools-selected-item': {
backgroundColor: 'rgba(255, 255, 0, 0.5)',
color: '#000',
},
'.ol-cm-dev-tools-cursor-before': {
borderTopColor: 'rgba(255, 255, 0, 1)',
'& + .ol-cm-dev-tools-cursor-before': {
borderTopColor: 'transparent',
},
},
'.ol-cm-dev-tools-positions': {
position: 'sticky',
bottom: '0',
backgroundColor: 'inherit',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-end',
},
'.ol-cm-dev-tools-position': {
padding: '4px 0',
minHeight: 'unset',
},
})
const fromDevTools = Annotation.define()
const transactionIsFromDevTools = (tr: Transaction) =>
tr.annotation(fromDevTools)
const devToolsView = ViewPlugin.define(view => {
const scroller = document.querySelector<HTMLDivElement>('.cm-scroller')
if (!scroller) {
return {}
}
const container = document.createElement('div')
container.classList.add('ol-cm-dev-tools-container')
scroller.after(container)
scroller.style.width = '50%'
const highlightNodeRange = (from: number, to: number) => {
view.dispatch({
effects: [selectedNodeEffect.of({ from, to })],
})
}
const selectNodeRange = (from: number, to: number) => {
view.dispatch({
annotations: [fromDevTools.of(true)],
selection: EditorSelection.single(from, to),
effects: EditorView.scrollIntoView(from, { y: 'center' }),
})
view.focus()
}
buildPanel(view.state, container, highlightNodeRange, selectNodeRange, true)
return {
update(update) {
if (
update.docChanged ||
update.selectionSet ||
hasLanguageLoadedEffect(update)
) {
const scroll = !update.transactions.some(transactionIsFromDevTools)
buildPanel(
update.state,
container,
highlightNodeRange,
selectNodeRange,
scroll
)
}
},
destroy() {
container.remove()
scroller.style.width = 'unset'
},
}
})
const buildPanel = (
state: EditorState,
container: HTMLDivElement,
highlightNodeRange: (from: number, to: number) => void,
selectNodeRange: (from: number, to: number) => void,
scroll: boolean
) => {
container.textContent = '' // clear
const tree = syntaxTree(state)
const { selection } = state
let itemToCenter: HTMLDivElement
let depth = 0
tree.iterate({
enter(nodeRef) {
const { from, to, name } = nodeRef
const element = document.createElement('div')
element.classList.add('ol-cm-dev-tools-item')
element.style.paddingLeft = `${depth * 16}px`
element.textContent = name
element.addEventListener('mouseover', () => {
highlightNodeRange(from, to)
})
element.addEventListener('click', () => {
selectNodeRange(from, to)
})
container.append(element)
for (const range of selection.ranges) {
// completely covered by selection
if (range.from <= from && range.to >= to) {
element.classList.add('ol-cm-dev-tools-selected-item')
itemToCenter = element
} else if (
(range.from > from && range.from < to) ||
(range.to > from && range.to < to)
) {
element.classList.add('ol-cm-dev-tools-covered-item')
itemToCenter = element
}
if (range.head === from) {
element.classList.add('ol-cm-dev-tools-cursor-before')
itemToCenter = element
}
}
depth++
},
leave(node) {
depth--
},
})
const positions = document.createElement('div')
positions.classList.add('ol-cm-dev-tools-positions')
container.append(positions)
for (const range of state.selection.ranges) {
const line = state.doc.lineAt(range.head)
const column = range.head - line.from + 1
const position = document.createElement('div')
position.classList.add('ol-cm-dev-tools-position')
position.textContent = `line ${line.number}, col ${column}, pos ${range.head}`
positions.append(position)
}
if (scroll && itemToCenter!) {
window.setTimeout(() => {
itemToCenter.scrollIntoView({
block: 'center',
inline: 'center',
})
})
}
}
const selectedNodeEffect = StateEffect.define<{
from: number
to: number
} | null>()
const highlightSelectedNode = StateField.define<DecorationSet>({
create() {
return Decoration.none
},
update(value, tr) {
if (tr.selection) {
value = Decoration.none
}
for (const effect of tr.effects) {
if (effect.is(selectedNodeEffect)) {
if (effect.value) {
const { from, to } = effect.value
// TODO: widget decoration if no range to decorate?
if (to > from) {
value = Decoration.set([
Decoration.mark({
class: 'ol-cm-selected-node-highlight',
}).range(from, to),
])
}
} else {
value = Decoration.none
}
}
}
return value
},
provide(f) {
return EditorView.decorations.from(f)
},
})
const createExtension = () => (isActive() ? [treeView, treeViewTheme] : [])

View file

@ -198,6 +198,7 @@
"@opentelemetry/sdk-trace-base": "^1.15.2",
"@opentelemetry/sdk-trace-web": "^1.15.2",
"@opentelemetry/semantic-conventions": "^1.15.2",
"@overleaf/codemirror-tree-view": "^0.1.3",
"@overleaf/ranges-tracker": "*",
"@overleaf/stream-utils": "*",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",