Draftist Docs

Code

Use fenced code blocks with a language identifier for syntax highlighting:

md
```rust
fn main() {
    println!("Hello!");
}
```

Renders as:

rust
fn main() {
    println!("Hello!");
}

Supported Languages

Syntax highlighting is powered by Shiki and supports its bundled languages and aliases.

Draftist additionally supports:

If a language identifier isn't recognized, Draftist leaves the code readable but unhighlighted. Ping us on Discord if the language you need is missing.

Code Block Features

Add attributes after the language identifier using key=value syntax, separated by spaces:

AttributeDescriptionExample
fileDisplay a filename badgefile=parser.rs
highlightHighlight specific lineshighlight=3,5-8,12
captionAdd a description below the blockcaption="Error handling logic"

Filename

Shows a filename badge in the code block header:

md
```rust file=src/main.rs
fn main() {
    let config = Config::load();
    run(config);
}
```

Renders as:

src/main.rs
rust
fn main() {
    let config = Config::load();
    run(config);
}

Line Highlighting

Highlight specific lines to draw attention to important parts:

md
```rust highlight=3-5
fn validate(input: &str) -> Result<&str, ValidationError> {
    let value = input.trim();
    if value.is_empty() {
        return Err(ValidationError::MissingValue);
    }
    Ok(value)
}
```

Renders as:

rust
fn validate(input: &str) -> Result<&str, ValidationError> {
    let value = input.trim();
    if value.is_empty() {
        return Err(ValidationError::MissingValue);
    }
    Ok(value)
}
FormatExampleDescription
Single linehighlight=5Highlights line 5
Rangehighlight=10-15Highlights lines 10 through 15
Mixedhighlight=2,5-8,12Combines singles and ranges

For a more convenient option, add a notation comment directly to each line you want to highlight instead of counting line numbers:

md
```rust
fn validate(input: &str) -> Result<&str, ValidationError> {
    let value = input.trim(); // [\!code highlight]
    if value.is_empty() { // [\!code highlight:3]
        return Err(ValidationError::MissingValue);
    }
    Ok(value)
}
```

Renders as:

rust
fn validate(input: &str) -> Result<&str, ValidationError> {
    let value = input.trim(); // [!code highlight]
    if value.is_empty() { // [!code highlight:3]
        return Err(ValidationError::MissingValue);
    }
    Ok(value)
}

Add a count such as [!code highlight:3] to highlight consecutive lines.

Diffs

Mark removed and added lines with diff notation:

md
```rust
fn parse_opening(parser: &mut Parser) -> Result<Token> {
    parser.stream.expect(TokenKind::LParen) // [\!code --]
    parser.expect(TokenKind::LParen) // [\!code ++]
}
```

Renders as:

rust
fn parse_opening(parser: &mut Parser) -> Result<Token> {
    parser.stream.expect(TokenKind::LParen) // [!code --]
    parser.expect(TokenKind::LParen) // [!code ++]
}

Focus

Focus a line or region and de-emphasize the surrounding code:

md
```rust
pub fn expression(parser: &mut Parser) -> Result<Expression> {
    let mut left = parser.primary()?;

    while let Some(operator) = parser.peek_operator() { // [\!code focus:6]
        let precedence = operator.precedence();
        parser.advance();
        let right = parser.expression_at(precedence)?;
        left = Expression::binary(left, operator, right);
    }

    Ok(left)
}
```

Renders as:

rust
pub fn expression(parser: &mut Parser) -> Result<Expression> {
    let mut left = parser.primary()?;

    while let Some(operator) = parser.peek_operator() { // [!code focus:6]
        let precedence = operator.precedence();
        parser.advance();
        let right = parser.expression_at(precedence)?;
        left = Expression::binary(left, operator, right);
    }

    Ok(left)
}

Diagnostics

Use information, warning, and error treatments to distinguish line states:

md
```rust
fn load_cached(key: &str) -> Result<Value, CacheError> {
    match read_cache(key) {
        Ok(Some(value)) => Ok(value), // [\!code info]
        Ok(None) => Err(CacheError::Missing), // [\!code warning]
        Err(error) => Err(CacheError::Unavailable(error)), // [\!code error]
    }
}
```

Renders as:

rust
fn load_cached(key: &str) -> Result<Value, CacheError> {
    match read_cache(key) {
        Ok(Some(value)) => Ok(value), // [!code info]
        Ok(None) => Err(CacheError::Missing), // [!code warning]
        Err(error) => Err(CacheError::Unavailable(error)), // [!code error]
    }
}

Word Highlights

Highlight a word wherever it appears in the code block:

md
```rust
fn read_cache(cache: &Cache, key: &str) -> Option<Value> { // [\!code word:entry]
    let entry = cache.get(key);
    entry.or_else(|| cache.load(key))
}
```

Renders as:

rust
fn read_cache(cache: &Cache, key: &str) -> Option<Value> { // [!code word:entry]
    let entry = cache.get(key);
    entry.or_else(|| cache.load(key))
}

Literal Notation

Write [\!code highlight] to display the notation literally. Draftist keeps the comment but displays and copies it without the escape:

rust
/// Add `// [\!code highlight]` after a statement to emphasize it.
pub fn highlight_help() {}

Draftist removes notation comments from highlighted code and copied text.

Caption

Captions show up below the code block:

md
```rust file=parser.rs caption="Recover at the next statement boundary"
fn synchronize(parser: &mut Parser) {
    while let Some(token) = parser.peek() {
        match token.kind {
            TokenKind::Semicolon => {
                parser.advance();
                return;
            }
            TokenKind::Fn | TokenKind::Let => return,
            _ => parser.advance(),
        }
    }
}
```

Renders as:

parser.rs
rust
fn synchronize(parser: &mut Parser) {
    while let Some(token) = parser.peek() {
        match token.kind {
            TokenKind::Semicolon => {
                parser.advance();
                return;
            }
            TokenKind::Fn | TokenKind::Let => return,
            _ => parser.advance(),
        }
    }
}
Recover at the next statement boundary

UI Features

Every code block comes with a copy button and a language badge. If the block has a block ID, there's also a link button so readers can grab a direct link to it.