Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/ec.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ export default defineEcConfig({
defaultProps: {
showLineNumbers: false,
},
inline: 'trailing-curly-colon'
})
4 changes: 4 additions & 0 deletions docs/scripts/typedoc/templates/key-features/frames.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Frames can have optional titles, which are either taken from the code block's me
These features are provided by `@expressive-code/plugin-frames`, which is installed & enabled by default in all framework integrations. You can start using it right away in your documents!
:::

:::note[Code Block type support]
Does not support `inline` code blocks.
:::

## Usage in markdown / MDX

### Code editor frames
Expand Down
4 changes: 4 additions & 0 deletions docs/scripts/typedoc/templates/key-features/text-markers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Expressive Code allows you to add annotations to lines & line ranges, as well as
These features are provided by `@expressive-code/plugin-text-markers`, which is installed & enabled by default in all framework integrations. You can start using it right away in your documents!
:::

:::note[Code Block type support]
Does not support `inline` code blocks.
:::

## Usage in markdown / MDX

### Marking full lines & line ranges
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ This optional plugin allows you to reduce long code examples to their relevant p

The lines of collapsed sections will be replaced by a clickable `X collapsed lines` element. When clicked, the collapsed section will be expanded, showing the previously hidden lines.

:::note[Code Block type support]
Does not support `inline` code blocks.
:::

## Installation

Before being able to use collapsible sections in your code blocks, you need to install the plugin as a dependency and add it to your configuration:
Expand Down
4 changes: 4 additions & 0 deletions docs/scripts/typedoc/templates/plugins/line-numbers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { Steps, Tabs, TabItem } from '@astrojs/starlight/components'

This optional plugin allows you to display line numbers in your code blocks. The line numbers are displayed in the gutter to the left of the code.

:::note[Code Block type support]
Does not support `inline` code blocks.
:::

## Installation

Before being able to display line numbers in your code blocks, you need to install the plugin as a dependency and add it to your configuration:
Expand Down
28 changes: 28 additions & 0 deletions docs/scripts/typedoc/templates/reference/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,14 @@ This plugin is enabled by default. Set this to `false` to disable it.

In addition to the options above, the rehype integration also supports the following options:

:::tip[Detecting code block type and language]
When a code block is detected by Expressive Code, the `<code>` element will be wrapped in a `<pre>` (for `block` code) or `<span>` (for `inline` code) element that will contain the following data attributes which can be helpful for conditional
handling in downstream processing (e.g., `CSS`, `react-markdown`, etc.):

- `data-language`: syntax highlighting language (e.g., `js`)
- `data-ec-type`: `block` or `inline`
:::

### customCreateBlock

<PropertySignature>
Expand Down Expand Up @@ -398,6 +406,26 @@ If the function returns `undefined`, the default locale provided in the Expressi
- **file**: VFileWithOutput\<null\>\
A `VFile` instance representing the Markdown document.

### inline

<PropertySignature>
- Type: false \| `'trailing-curly-colon'` \| undefined
- Default: `false`
</PropertySignature>

Enable inline code to be processed.

Supported Language Identifier Formats:
- `trailing-curly-colon`: Append a `\{:lang}` marker at the end of the inline code (e.g., `code\{:lang}`). The
`trailing-curly-colon` can be escaped with `\` to bypass processing and any backslashes intended to be
rendered just prior to the `trailing-curly-colon` should be escaped (e.g., `\\`):
- Enabled:
- `doWork()\{:js}` -> `doWork(){:js}`
- `doWork()\\\\\{:js}` -> `doWork()\\{:js}`
- Disabled:
- `doWork()\\\{:js}` -> `doWork()\{:js}`
- `doWork()\\\\\\\{:js}` -> `doWork()\\\{:js}`

### tabWidth

<PropertySignature>
Expand Down
12 changes: 12 additions & 0 deletions docs/src/content/docs/key-features/syntax-highlighting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ Result:
- Strikethrough
```

### Inline Syntax Highlighting

To get syntax highlighting for inline code, wrap it in single backticks and ensure that it includes a language identifier that corresponds to the [inline option](/reference/configuration/#inline) specified in the configuration.

```md title="example.md"
Inline code example `console.log('Hello!'){:js}` for syntax highlighting.
```

The rendered result looks like this:

Inline code example `console.log('Hello!'){:js}` for syntax highlighting.

## Supported languages

Out of the box, over 100 languages are supported, including JavaScript, TypeScript, HTML, CSS, Astro, Markdown, MDX, JSON, YAML, and many more. You can find a [list of all language identifiers](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-grammars/README.md) on GitHub.
Expand Down
4 changes: 4 additions & 0 deletions docs/src/content/docs/key-features/word-wrap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ When your code blocks contain long lines, it can be helpful to enable word wrap
This feature is provided by the Expressive Code core package. You can start using it right away in your documents!
:::

:::note[Code Block type support]
Does not support `inline` code blocks.
:::

## Usage in markdown / MDX

### Configuring word wrap per block
Expand Down
6 changes: 4 additions & 2 deletions internal/test-utils/src/html-snapshots.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mkdirSync, writeFileSync } from 'fs'
import { join, dirname } from 'path'
import { ExpressiveCodeEngine, ExpressiveCodeEngineConfig, ExpressiveCodePlugin, ExpressiveCodeTheme, StyleVariant } from '@expressive-code/core'
import { type ExpressiveCodeBlockType, ExpressiveCodeEngine, ExpressiveCodeEngineConfig, ExpressiveCodePlugin, ExpressiveCodeTheme, StyleVariant } from '@expressive-code/core'
import type { Element } from '@expressive-code/core/hast'
import { toHtml } from '@expressive-code/core/hast'

Expand All @@ -13,6 +13,7 @@ export type TestFixture = {
meta?: string | undefined
themes?: ExpressiveCodeTheme[] | undefined
plugins: ExpressiveCodePlugin[]
type?: ExpressiveCodeBlockType | undefined
engineOptions?: Partial<ExpressiveCodeEngineConfig> | undefined
blockValidationFn?: BlockValidationFn | undefined
}
Expand All @@ -26,7 +27,7 @@ export function buildThemeFixtures(themes: ExpressiveCodeTheme[], fixtureContent
return [fixture]
}

async function renderFixture({ fixtureName, code, language = 'js', meta = '', themes, plugins, engineOptions, blockValidationFn }: TestFixture) {
async function renderFixture({ fixtureName, code, language = 'js', meta = '', themes, plugins, engineOptions, blockValidationFn, type }: TestFixture) {
const engine = new ExpressiveCodeEngine({
themes,
plugins,
Expand All @@ -39,6 +40,7 @@ async function renderFixture({ fixtureName, code, language = 'js', meta = '', th
code,
language,
meta,
type,
})

return {
Expand Down
34 changes: 28 additions & 6 deletions packages/@expressive-code/core/src/common/block.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { MetaOptions } from '../helpers/meta-options'
import { ExpressiveCodeProcessingState, validateExpressiveCodeProcessingState } from '../internal/render-block'
import { isNumber, isString, newTypeError } from '../internal/type-checks'
import { isNumber, isString, isValidCodeBlockType, newTypeError } from '../internal/type-checks'
import { ExpressiveCodeLine } from './line'

export type ExpressiveCodeBlockType = 'block' | 'inline'

export interface ExpressiveCodeBlockOptions {
/**
* The type of code block.
*
* @default `block`
*/
type?: ExpressiveCodeBlockType | undefined
/**
* The plaintext contents of the code block.
*/
Expand Down Expand Up @@ -87,6 +95,8 @@ export interface ExpressiveCodeBlockProps {
* If `false`, lines that exceed the available width will cause a horizontal scrollbar
* to appear.
*
* @note Not supported on `inline` code blocks.
*
* @note This option only affects how the code block is displayed and does not change
* the actual code. When copied to the clipboard, the code will still contain the
* original unwrapped lines.
Expand All @@ -103,6 +113,8 @@ export interface ExpressiveCodeBlockProps {
* If `false`, wrapped parts of long lines will always start at column 1.
* This can be useful to reproduce terminal output.
*
* @note Not supported on `inline` code blocks.
*
* @note This option only has an effect if `wrap` is `true`. It only affects how the
* code block is displayed and does not change the actual code. When copied to the clipboard,
* the code will still contain the original unwrapped lines.
Expand All @@ -119,6 +131,8 @@ export interface ExpressiveCodeBlockProps {
* original line. If `preserveIndent` is `false`, this value is used as the
* indentation for all wrapped lines.
*
* @note Not supported on `inline` code blocks.
*
* @note This option only affects how the code block is displayed
* and does not change the actual code. When copied to the clipboard,
* the code will still contain the original unwrapped lines.
Expand All @@ -133,15 +147,16 @@ export interface ExpressiveCodeBlockProps {
*/
export class ExpressiveCodeBlock {
constructor(options: ExpressiveCodeBlockOptions) {
const { code, language, meta = '', props, locale, parentDocument } = options
if (!isString(code) || !isString(language) || !isString(meta)) throw newTypeError('object of type ExpressiveCodeBlockOptions', options)
const { type = 'block', code, language, meta = '', props, locale, parentDocument } = options
if (!isString(code) || !isString(language) || !isString(meta) || !isValidCodeBlockType(type)) throw newTypeError('object of type ExpressiveCodeBlockOptions', options)
this.#lines = []
this.#language = language
this.#meta = meta
this.#metaOptions = new MetaOptions(meta)
this.#props = props || {}
this.#locale = locale
this.#parentDocument = parentDocument
this.#type = type

// Split the code into lines and remove whitespace from the end of the lines
const lines = code.split(/\r?\n/).map((line) => line.trimEnd())
Expand All @@ -154,9 +169,9 @@ export class ExpressiveCodeBlock {
if (lines.length) this.insertLines(0, lines)

// Transfer core meta options to props
this.props.wrap = this.metaOptions.getBoolean('wrap') ?? this.props.wrap
this.props.preserveIndent = this.metaOptions.getBoolean('preserveIndent') ?? this.props.preserveIndent
this.props.hangingIndent = this.metaOptions.getInteger('hangingIndent') ?? this.props.hangingIndent
this.props.wrap = type === 'inline' ? false : this.metaOptions.getBoolean('wrap') ?? this.props.wrap
this.props.preserveIndent = type === 'inline' ? false : this.metaOptions.getBoolean('preserveIndent') ?? this.props.preserveIndent
this.props.hangingIndent = type === 'inline' ? 0 : this.metaOptions.getInteger('hangingIndent') ?? this.props.hangingIndent
}

/**
Expand All @@ -174,7 +189,14 @@ export class ExpressiveCodeBlock {
#locale: ExpressiveCodeBlockOptions['locale']
#parentDocument: ExpressiveCodeBlockOptions['parentDocument']
#state: ExpressiveCodeProcessingState | undefined
#type: ExpressiveCodeBlockType

/**
* The {@link ExpressiveCodeBlockType} of the code block.
*/
get type() {
return this.#type
}
/**
* Provides read-only access to the code block's plaintext contents.
*/
Expand Down
8 changes: 6 additions & 2 deletions packages/@expressive-code/core/src/common/plugin-hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Element } from '../hast'
import { PluginStyles } from '../internal/css'
import { GroupContents, RenderedGroupContents } from '../internal/render-group'
import { ExpressiveCodeBlock } from './block'
import { ExpressiveCodeBlock, type ExpressiveCodeBlockType } from './block'
import { ExpressiveCodeLine } from './line'
import { ExpressiveCodePlugin, ResolverContext } from './plugin'
import { ResolvedExpressiveCodeEngineConfig } from './engine'
Expand Down Expand Up @@ -48,6 +48,8 @@ export interface ExpressiveCodeHookContext extends ExpressiveCodeHookContextBase
* The engine calls the {@link GutterElement.renderLine `renderLine`} function
* of the gutter elements registered by all plugins for every line of the code block.
* The returned elements are then added as children to the line's gutter container.
*
* @note Not supported on `inline` code blocks.
*/
addGutterElement: (element: GutterElement) => void
}
Expand Down Expand Up @@ -304,11 +306,13 @@ export async function runHooks<HookType extends keyof ExpressiveCodePluginHooks>
context: {
plugins: readonly ExpressiveCodePlugin[]
config: ResolvedExpressiveCodeEngineConfig
codeBlockType?: ExpressiveCodeBlockType | undefined
},
runner: (hook: { hookName: HookType; hookFn: NonNullable<ExpressiveCodePluginHooks[HookType]>; plugin: ExpressiveCodePlugin }) => void | Promise<void>
) {
const { plugins, config } = context
const { plugins, config, codeBlockType = 'block' } = context
for (const plugin of plugins) {
if (!(plugin.supportedCodeBlockTypes || ['block']).includes(codeBlockType)) continue
const hookFn = plugin.hooks?.[key]
if (!hookFn) continue

Expand Down
10 changes: 10 additions & 0 deletions packages/@expressive-code/core/src/common/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type ExpressiveCodeBlockType } from './block'
import { ExpressiveCodePluginHooks } from './plugin-hooks'
import { PluginStyleSettings } from './plugin-style-settings'
import { StyleSettingPath } from './style-settings'
Expand Down Expand Up @@ -58,6 +59,15 @@ export interface ExpressiveCodePlugin {
* rendering process. See {@link ExpressiveCodePluginHooks} for a list of available hooks.
*/
hooks?: ExpressiveCodePluginHooks | undefined
/**
* The code block types that are supported by the plugin.
*
* In order for the plugins hooks to be called for a code block with a given type,
* the type must be listed here.
*
* @default [`block`].
*/
supportedCodeBlockTypes?: ExpressiveCodeBlockType[] | undefined
}

export type BaseStylesResolverFn = (context: ResolverContext) => string | Promise<string>
Expand Down
9 changes: 8 additions & 1 deletion packages/@expressive-code/core/src/common/style-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,11 @@ export function getCssVarName(styleSetting: StyleSettingPath) {
return `--ec-${varName}`
}

export const codeLineClass = 'ec-line'
export const codeBlockLineClass = 'ec-line'
export const inlineCodeLineClass = 'ec-line-inline'
/** @deprecated Use {@link codeBlockLineClass} or {@link inlineCodeLineClass} instead. */
export const codeLineClass = codeBlockLineClass

export const containerBlockClass = 'ec-container-block'
export const containerInlineClass = 'ec-container-inline'
export const containerMixedClass = 'ec-container-mixed'
29 changes: 19 additions & 10 deletions packages/@expressive-code/core/src/internal/core-styles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { lighten, ensureColorContrastOnBackground, setAlpha } from '../helpers/color-transforms'
import { ResolverContext } from '../common/plugin'
import { PluginStyleSettings } from '../common/plugin-style-settings'
import { UnresolvedStyleSettings, codeLineClass } from '../common/style-settings'
import { UnresolvedStyleSettings, containerBlockClass, containerInlineClass, codeBlockLineClass, inlineCodeLineClass } from '../common/style-settings'

export interface CoreStyleSettings {
/**
Expand Down Expand Up @@ -232,12 +232,14 @@ export function getCoreBaseStyles({
const ifThemedSelectionColors = (css: string) => (useThemedSelectionColors ? css : '')

return `
font-family: ${cssVar('uiFontFamily')};
font-size: ${cssVar('uiFontSize')};
font-weight: ${cssVar('uiFontWeight')};
line-height: ${cssVar('uiLineHeight')};
text-size-adjust: none;
-webkit-text-size-adjust: none;
&.${containerBlockClass}, .${containerBlockClass} {
font-family: ${cssVar('uiFontFamily')};
font-size: ${cssVar('uiFontSize')};
font-weight: ${cssVar('uiFontWeight')};
line-height: ${cssVar('uiLineHeight')};
text-size-adjust: none;
-webkit-text-size-adjust: none;
}

*:not(:is(svg, svg *)) {
${useStyleReset ? 'all: revert;' : ''}
Expand All @@ -249,6 +251,13 @@ export function getCoreBaseStyles({
color: ${cssVar('uiSelectionForeground')};
}`)}

&.${containerInlineClass}, .${containerInlineClass} {
& > span > code {
background: ${cssVar('codeBackground')};
color: ${cssVar('codeForeground')};
}
}

pre {
display: flex;
margin: 0;
Expand Down Expand Up @@ -285,7 +294,7 @@ export function getCoreBaseStyles({
overflow-x: auto;

/* Enable word wrapping on demand */
&.wrap .${codeLineClass} .code {
&.wrap .${codeBlockLineClass} .code {
white-space: pre-wrap;
overflow-wrap: break-word;
min-width: min(20ch, var(--ecMaxLine, 20ch));
Expand Down Expand Up @@ -315,7 +324,7 @@ export function getCoreBaseStyles({
}

/* Code lines */
.${codeLineClass} {
.${codeBlockLineClass} {
/* RTL support: Code is always LTR */
direction: ltr;
unicode-bidi: isolate;
Expand Down Expand Up @@ -385,7 +394,7 @@ export function getCoreBaseStyles({
export function getCoreThemeStyles(styleVariantIndex: number) {
return `
/* Theme-dependent styles for InlineStyleAnnotation */
.${codeLineClass} :where(span[style^='--']:not([class])) {
:is(.${codeBlockLineClass}, .${inlineCodeLineClass}) :where(span[style^='--']:not([class])) {
color: var(--${styleVariantIndex}, inherit);
background-color: var(--${styleVariantIndex}bg, transparent);
font-style: var(--${styleVariantIndex}fs, inherit);
Expand Down
Loading