Skip to content

Commit

Permalink
feat: add indentation on wrapped lines
Browse files Browse the repository at this point in the history
  • Loading branch information
fauzi9331 committed Jul 22, 2023
1 parent 8da09a4 commit 4a6b25f
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 2 deletions.
5 changes: 3 additions & 2 deletions src/lib/components/modes/textmode/TextMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import { needsFormatting } from '$lib/utils/jsonUtils.js'
import { faJSONEditorFormat } from '$lib/img/customFontawesomeIcons.js'
import { indentationMarkers } from '@replit/codemirror-indentation-markers'
import { wrappedLineIndentation } from './codemirror/wrappedLineIndentation.js'
export let readOnly: boolean
export let mainMenuBar: boolean
Expand Down Expand Up @@ -516,7 +517,8 @@
tabSizeCompartment.of(EditorState.tabSize.of(tabSize)),
indentUnitCompartment.of(createIndentUnit(indentation)),
themeCompartment.of(EditorView.theme({}, { dark: hasDarkTheme() })),
EditorView.lineWrapping
EditorView.lineWrapping,
wrappedLineIndentation
]
})
Expand Down Expand Up @@ -650,7 +652,6 @@
}
const codeMirrorText = getCodeMirrorValue()
const isChanged = codeMirrorText !== text
debug('onChangeCodeMirrorValue', { isChanged })
if (!isChanged) {
Expand Down
110 changes: 110 additions & 0 deletions src/lib/components/modes/textmode/codemirror/wrappedLineIndentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { getIndentUnit } from '@codemirror/language'
import { EditorState, Facet, Line, RangeSetBuilder } from '@codemirror/state'
import {
Decoration,
EditorView,
ViewPlugin,
ViewUpdate,
type DecorationSet,
type PluginValue
} from '@codemirror/view'

class WrappedLineIndentation implements PluginValue {
view: EditorView
decorations!: DecorationSet
initialPaddingLeft: string | null
indentUnit: number

constructor(view: EditorView) {
this.view = view
this.indentUnit = getIndentUnit(view.state)
this.initialPaddingLeft = null
this.generate(view.state)
}

update(update: ViewUpdate) {
const indentUnit = getIndentUnit(update.state)
const indentUnitChanged = indentUnit !== this.indentUnit
if (indentUnitChanged) {
this.indentUnit = indentUnit
}
if (update.docChanged || update.viewportChanged || indentUnitChanged) {
this.generate(update.state)
}
}

private generate(state: EditorState) {
const builder = new RangeSetBuilder<Decoration>()

// Measure the 'padding-left' value of the '.cm-line' class for the first time when DOM loads.
// As the DOM is not available during initial load, we use 'requestMeasure' to perform this task.
// After acquiring the initial 'padding-left' value, we can then generate decorations without further DOM measurements.
if (this.initialPaddingLeft) {
this.addStyleToBuilder(builder, state, this.initialPaddingLeft)
} else {
this.view.requestMeasure({
read: (measure) => {
const lineElement = measure.contentDOM.querySelector('.cm-line')

if (lineElement) {
this.initialPaddingLeft = window
.getComputedStyle(lineElement)
.getPropertyValue('padding-left')
this.addStyleToBuilder(builder, state, this.initialPaddingLeft)
}
this.decorations = builder.finish()
}
})
}

this.decorations = builder.finish()
}

private addStyleToBuilder(
builder: RangeSetBuilder<Decoration>,
state: EditorState,
initialPaddingLeft: string
) {
const visibleLines = this.getVisibleLines(state)
for (const line of visibleLines) {
const indentSize = this.getIndentSize(line)
const paddingValue = `calc(${indentSize + 2}ch + ${initialPaddingLeft})`
builder.add(
line.from,
line.from,
Decoration.line({
attributes: {
style: `padding-left: ${paddingValue}; text-indent: -${indentSize + 2}ch;`
}
})
)
}
}

// Get all lines that are currently visible in the viewport.
private getVisibleLines(state: EditorState) {
const lines = new Set<Line>()
for (const { from, to } of this.view.visibleRanges) {
let pos = from
while (pos <= to) {
const line = state.doc.lineAt(pos)
if (!lines.has(line)) {
lines.add(line)
}
pos = line.to + 1
}
}
return lines
}

private getIndentSize(line: Line) {
// is it possible to have tab character `\t`?
return line.text.length - line.text.trimStart().length
}
}

export const wrappedLineIndentation = [
ViewPlugin.fromClass(WrappedLineIndentation, {
decorations: (v) => v.decorations
})
]

0 comments on commit 4a6b25f

Please sign in to comment.