Skip to content

Commit

Permalink
refactor: isomorphic function
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdehaven committed Jan 14, 2025
1 parent 23e25a4 commit 751e0df
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 45 deletions.
57 changes: 12 additions & 45 deletions src/folding-provider.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,22 @@
import type { languages, editor } from 'monaco-editor-core'
import { getDocumentFoldingRanges } from './get-document-folding-ranges'

/**
* Provides folding ranges for the MDC language in the Monaco editor.
*
* @param {monaco.editor.ITextModel} model - The text model to provide folding ranges for.
* @returns {languages.ProviderResult<languages.FoldingRange[]>} An array of folding ranges for the editor.
*
* The function identifies folding ranges based on:
* - Custom block components defined by start tags (e.g., "::container" or ":::button")
* and end tags (e.g., "::" or ":::" with matching opening tag level).
* - Markdown code blocks delimited by triple backticks (```) or tildes (~~~).
* @param {editor.ITextModel} model - The text model for which folding ranges are to be provided.
* @returns A promise that resolves to an array of folding ranges.
*/
export const foldingProvider = (model: editor.ITextModel): languages.ProviderResult<languages.FoldingRange[]> => {
const ranges = [] // Array to store folding ranges
const stack = [] // Stack to manage nested block components
const lines = model.getLinesContent() // Retrieve all lines
let insideCodeBlock = false // Flag to track if inside a code block

for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
const line = lines[lineNumber].trim() // Remove extra whitespace

// Check if the current line starts or ends a markdown code block
if (/^\s*(?:`{3,}|~{3,})/.test(line)) {
insideCodeBlock = !insideCodeBlock // Toggle code block mode
continue // Skip further processing for this line
}

// Skip processing lines inside a markdown code block
if (insideCodeBlock) {
continue
}

// Match the start tag (e.g., "::container" or ":::button")
const startMatch = line.match(/^\s*:{2,}([\w-]+)/)
if (startMatch) {
// Push start block onto the stack
stack.push({ start: lineNumber + 1, tagName: startMatch[1] }) // Save 1-based line number and tag name
continue // Skip further processing for this line
}

// Match the end tag (e.g., "::" or ":::" with matching opening tag level)
const endMatch = line.match(/^\s*:{2,}$/)
if (endMatch && stack.length > 0) {
const lastBlock = stack.pop() // Retrieve the last unmatched start block
ranges.push({
start: lastBlock?.start ?? 0, // Block start line (1-based)
end: lineNumber + 1, // Current line as block end (1-based)
})
}
const documentAdapter = {
getLine: (lineNumber: number) => model.getLineContent(lineNumber + 1),
lineCount: model.getLineCount(),
}

// Return all folding ranges to the editor
return ranges
const ranges = getDocumentFoldingRanges(documentAdapter)

return ranges.map(range => ({
start: range.start + 1,
end: range.end + 1,
}))
}
102 changes: 102 additions & 0 deletions src/get-document-folding-ranges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* !Important: The exported `getDocumentFoldingRanges` function in this file is also utilized in
* the `@nuxtlabs/vscode-mdc` VSCode extension https://github.com/nuxtlabs/vscode-mdc.
*
* Any changes to the function signature or behavior should be tested and verified in the extension.
*/

/** Represents a text document, providing methods to access its content. */
interface TextDocument {
/**
* Retrieves the content of a specific line in the document.
* @param lineNumber - The zero-based line number to retrieve.
* @returns The content of the specified line.
*/
getLine: (lineNumber: number) => string

/* The total number of lines in the document. */
lineCount: number
}

/**
* A block of code that can be folded in an editor.
*
* @interface FoldingBlock
* @property {number} start - The starting line number of the folding block.
* @property {string} tagName - The tag name associated with the folding block.
* @property {number} colons - The number of colons in the folding block.
*/
interface FoldingBlock {
start: number
tagName: string
colons: number
}

/**
* A range in a text document that can be folded.
*
* @interface FoldingRange
* @property {number} start - The zero-based line number where the folding starts.
* @property {number} end - The zero-based line number where the folding ends.
*/
interface FoldingRange {
start: number
end: number
}

/**
* Generates the folding ranges for a given text document. This function is designed to be used with
* text documents that follow the Monarch or TextMate syntax highlighting conventions.
*
* @param {TextDocument} document - The text document to compute folding ranges for.
* @param {(lineNumber: number) => string} document.getLine - A function that returns the content of a line given its line number.
* @param {number} document.lineCount - The total number of lines in the document.
* @returns {FoldingRange[]} - An array of FoldingRange objects representing the folding regions in the document.
*/
export const getDocumentFoldingRanges = (document: TextDocument): FoldingRange[] => {
const ranges: FoldingRange[] = []
const stack: FoldingBlock[] = []
let insideCodeBlock = false

for (let lineNumber = 0; lineNumber < document.lineCount; lineNumber++) {
const line = document.getLine(lineNumber).trim()

// Check for code block markers
if (/^\s*(?:`{3,}|~{3,})/.test(line)) {
insideCodeBlock = !insideCodeBlock
continue
}

// Skip processing lines inside a markdown code block
if (insideCodeBlock) {
continue
}

// Match start tags
const startMatch = line.match(/^\s*(:{2,})([\w-]+)/)
if (startMatch) {
stack.push({
start: lineNumber,
tagName: startMatch[2],
colons: startMatch[1].length,
})
continue
}

// Match end tags
const endMatch = line.match(/^\s*(:{2,})$/)
if (endMatch && stack.length > 0) {
const colonCount = endMatch[1].length
const lastBlock = stack[stack.length - 1]
if (lastBlock && lastBlock.colons === colonCount) {
stack.pop()
ranges.push({
start: lastBlock.start,
end: lineNumber,
})
}
}
}

return ranges
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,4 @@ export const language = <languages.IMonarchLanguage>{

export { formatter } from './formatter'
export { foldingProvider } from './folding-provider'
export { getDocumentFoldingRanges } from './get-document-folding-ranges'

0 comments on commit 751e0df

Please sign in to comment.