Skip to content

Commit

Permalink
basic indentation support in code blocks
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Ilyushchenko <[email protected]>
  • Loading branch information
mr1name committed Dec 17, 2024
1 parent 45e84f7 commit 4a5fbc3
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
152 changes: 152 additions & 0 deletions plugins/text-editor-resources/src/components/extension/indent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//

import { Extension } from '@tiptap/core'
import { type Node } from '@tiptap/pm/model'
import { type EditorState, TextSelection, type Transaction } from '@tiptap/pm/state'

export interface IndendOptions {
indentUnit: string
allowedNodes: string[]
}

export const indentExtensionOptions: IndendOptions = {
indentUnit: ' ',
allowedNodes: ['mermaid', 'codeBlock']
}

export const IndentExtension = Extension.create<IndendOptions>({
name: 'huly-indent',
addKeyboardShortcuts () {
return {
Tab: ({ editor }) => {
if (!editor.isEditable) return false
return this.editor.commands.command(({ state, dispatch }) => {
return dispatch?.(adjustIndent(state, 1, this.options))
})
},
'Shift-Tab': ({ editor }) => {
if (!editor.isEditable) return false
return this.editor.commands.command(({ state, dispatch }) => {
return dispatch?.(adjustIndent(state, -1, this.options))
})
}
}
}
})

export function adjustIndent (state: EditorState, direction: -1 | 1, options: IndendOptions): Transaction | undefined {
const { selection } = state
if (selection instanceof TextSelection) {
return adjustSelectionIndent(state, selection, direction, options)
}
}

export function adjustSelectionIndent (
state: EditorState,
selection: TextSelection,
direction: -1 | 1,
options: IndendOptions
): Transaction | undefined {
const ranges = getLinesInSelection(state, selection).filter((range) =>
options.allowedNodes.some((n) => n === range.node.type.name)
)

if (ranges.length === 0) return

const { indentUnit } = options

const indentLevelOffset = (pos: number, direction: -1 | 1): number => {
const unitSize = indentUnit.length
const levelAdjustment = direction === -1 && pos % unitSize !== 0 ? 0 : direction
const indentPos = Math.floor((pos + levelAdjustment * unitSize) / unitSize) * unitSize
return indentPos - pos
}

const tr = state.tr

if (ranges.length === 1) {
const range = ranges[0]
const withinIndent = selection.from >= range.from && selection.to <= range.from + range.indent && range.indent > 0
if (!withinIndent && direction > 0) {
const indentOffset = indentLevelOffset(selection.from - range.from, direction)
tr.insertText(indentUnit.slice(0, indentOffset), selection.from, selection.to)
return
}
}

let insertionOffset = 0
for (const range of ranges) {
if (direction > 0 ? range.text === '' : range.indent === 0) {
continue
}
const indentOffset = indentLevelOffset(range.indent, direction)
const from = range.from + insertionOffset
if (indentOffset > 0) {
tr.insertText(indentUnit.slice(0, indentOffset), from)
} else {
tr.insertText('', from, from - indentOffset)
}
insertionOffset += indentOffset
}

tr.setSelection(selection.map(tr.doc, tr.mapping))

return tr
}

function countLeadingSpace (str: string): number {
const match = str.match(/^(\s*)/)
return match !== null ? match[0].length : 0
}

interface LineRange {
node: Node
text: string
from: number
indent: number
to: number
}

function getLinesInSelection (state: EditorState, selection: TextSelection): LineRange[] {
const { from, to } = selection // Selection start and end positions
const ranges: LineRange[] = []

state.doc.nodesBetween(from, to, (node: Node, pos: number) => {
if (!node.isTextblock) return

let currentPos = pos + 1
const lines = node.textContent.split('\n')

for (const line of lines) {
const lineStart = currentPos
const lineEnd = currentPos + line.length

if (lineStart <= to && lineEnd >= from) {
ranges.push({
node,
from: lineStart,
indent: countLeadingSpace(line),
to: lineEnd,
text: line
})
}

currentPos = lineEnd + 1
}
})

return ranges
}
9 changes: 9 additions & 0 deletions plugins/text-editor-resources/src/kits/editor-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { Table, TableCell, TableRow } from '../components/extension/table'
import { DefaultKit, type DefaultKitOptions } from './default-kit'
import { MermaidExtension, type MermaidOptions, mermaidOptions } from '../components/extension/mermaid'
import { DrawingBoardExtension, type DrawingBoardOptions } from '../components/extension/drawingBoard'
import { type IndendOptions, IndentExtension, indentExtensionOptions } from '../components/extension/indent'

export interface EditorKitOptions extends DefaultKitOptions {
history?: false
Expand All @@ -53,6 +54,7 @@ export interface EditorKitOptions extends DefaultKitOptions {
| false
drawingBoard?: DrawingBoardOptions | false
mermaid?: MermaidOptions | false
indent?: IndendOptions | false
mode?: 'full' | 'compact'
note?: NoteOptions | false
submit?: SubmitOptions | false
Expand Down Expand Up @@ -260,6 +262,13 @@ async function buildEditorKit (): Promise<Extension<EditorKitOptions, any>> {
staticKitExtensions.push([850, MermaidExtension.configure(this.options.mermaid ?? mermaidOptions)])
}

if (this.options.indent !== false) {
staticKitExtensions.push([
860,
IndentExtension.configure(this.options.indent ?? indentExtensionOptions)
])
}

if (this.options.toolbar !== false) {
staticKitExtensions.push([
900,
Expand Down

0 comments on commit 4a5fbc3

Please sign in to comment.