Skip to content

Commit

Permalink
fix(*): formatting yaml props and export cjs module
Browse files Browse the repository at this point in the history
Fix formatting YAML props in nested block components.

Output a CommonJS module in addition to ESM, primarily for utilizing
the formatter function in @nuxtlabs/vscode-mdc.
  • Loading branch information
adamdehaven committed Jan 14, 2025
1 parent e990198 commit 4e2b080
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 56 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ const editor = monaco.editor.create(el, {
})
```

## VS Code Extension

The exported `formatter` and `getDocumentFoldingRanges` functions are also utilized in [@nuxtlabs/vscode-mdc](https://github.com/nuxtlabs/vscode-mdc) to provide the functionality to the [MDC VS Code extension](https://marketplace.visualstudio.com/items?itemName=Nuxt.mdc).

## 💻 Development

- Clone repository
Expand Down
21 changes: 21 additions & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineBuildConfig } from 'unbuild'

// https://github.com/unjs/unbuild?tab=readme-ov-file#configuration
export default defineBuildConfig({
name: '@nuxtlabs/monarch-mdc',
// Each separate plugin's entry file should be listed here
entries: [
'./src/index',
],
externals: [
'monaco-editor-core',
],
// Generates .d.ts declaration file(s)
declaration: true,
// Clean the output directory before building
clean: true,
rollup: {
// Export as CommonJS module, primarily for accessing the formatter in @nuxtlabs/vscode-mdc
emitCJS: true,
},
})
11 changes: 1 addition & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.3.0",
"license": "MIT",
"description": "Integrate MDC syntax with Monaco Editor",
"main": "./dist/index.mjs",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
Expand Down Expand Up @@ -41,14 +41,5 @@
"npm": {
"publish": false
}
},
"build": {
"entries": [
"./src/index"
],
"externals": [
"monaco-editor-core"
],
"declaration": true
}
}
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,
}))
}
8 changes: 8 additions & 0 deletions src/formatter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ styles: >
:::level-2
:inline2{prop="value"}
::::level-3
---
foo: "bar"
child-foo: "child-bar"
---
content
::::
:::
Expand All @@ -441,6 +445,10 @@ content
:::level-2
:inline2{prop="value"}
::::level-3
---
foo: "bar"
child-foo: "child-bar"
---
content
::::
:::
Expand Down
9 changes: 8 additions & 1 deletion src/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/**
* !Important: The exported `formatter` 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.
*/

/**
* Formatter Options
*/
Expand Down Expand Up @@ -198,7 +205,7 @@ export const formatter = (content: string, { tabSize = 2, isFormatOnType = false
// Adjust indentation for YAML block content based on the base indent level
if (yamlState.baseIndent !== null) {
const relativeIndent = indent - yamlState.baseIndent
formattedLines[formattedIndex++] = getIndent(parentIndent + relativeIndent) + trimmedContent
formattedLines[formattedIndex++] = getIndent(Math.max(yamlState.baseIndent, parentIndent + relativeIndent)) + trimmedContent
continue
}
}
Expand Down
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 4e2b080

Please sign in to comment.