Draftist Demo

Code Blocks

2025, 15 Dec

Code is everywhere in technical writing. Here's how it looks when published with Draftist.

Basic Code Blocks

Drop your code into a code block with a language identifier to enable syntax highlighting:

rust
pub struct TokenStream<'a> {
    tokens: &'a [Token],
    position: usize,
}

impl<'a> TokenStream<'a> {
    pub fn peek(&self) -> Option<&Token> {
        self.tokens.get(self.position)
    }

    pub fn advance(&mut self) -> Option<Token> {
        let token = self.tokens.get(self.position).cloned();
        self.position += 1;
        token
    }
}

File Labels

You can label your code with the file attribute to add a filename in the header—useful when showing code from a specific file:

token_stream.rs
rust
pub fn expect(&mut self, kind: TokenKind) -> Result<Token> {
    match self.advance() {
        Some(token) if token.kind == kind => Ok(token),
        Some(token) => Err(ParseError::unexpected_token(token, kind)),
        None => Err(ParseError::unexpected_eof()),
    }
}

Line Highlighting

Highlight specific lines in the code to draw attention to specific areas:

combinators.rs
rust
pub fn separated_list<T>(
    parser: &mut Parser, // [!code highlight]
    separator: TokenKind,
    element: impl Fn(&mut Parser) -> Result<T>,
) -> Result<Vec<T>> {
    let mut items = vec![element(parser)?];

    while parser.stream.peek().map(|t| t.kind) == Some(separator) { // [!code highlight:4]
        parser.stream.advance();
        items.push(element(parser)?);
    }

    Ok(items)
}

Diffs

Use diff to emphasize the change:

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

Focus

In a longer example, focus can keep the central operation prominent while letting the setup recede:

expression.rs
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 distinct line treatments to make info, warning, and error states easy to scan:

cache.rs
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

Word highlighting emphasizes every occurrence of an identifier:

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

Captions

The caption attribute adds a description below the code block:

parser.rs
rust
impl Parser {
    fn synchronize(&mut self) {
        while let Some(token) = self.stream.peek() {
            match token.kind {
                TokenKind::Semicolon => {
                    self.stream.advance();
                    return;
                }
                TokenKind::Fn | TokenKind::Let => return,
                _ => self.stream.advance(),
            }
        }
    }
}
Error recovery skips to the next statement boundary after a parse error

Multiple Languages

Syntax highlighting works across languages:

handler.js
js
async function fetchData(url) {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`HTTP error: ${response.status}`);
    }
    return response.json();
}
processor.py
python
def process_items(items):
    return [
        item.upper()
        for item in items
        if item.startswith('_')
    ]
User.res
rescript
type user = {
  id: int,
  name: string,
  email: option<string>,
}

let display = user =>
  switch user.email {
  | Some(email) => `${user.name} <${email}>`
  | None => user.name
  }
types.ts
typescript
interface User {
    id: number;
    name: string;
    email?: string;
}

type Result<T> =
    | { success: true; data: T }
    | { success: false; error: string };

Inline Code

Use backticks for inline code like TokenKind::LParen or Result<T> within text.

Combining Everything

File labels, captions, and line treatments all work together:

delimited.rs
rust
pub fn delimited<T>(
    parser: &mut Parser, // [!code word:parser]
    open: TokenKind,
    inner: impl Fn(&mut Parser) -> Result<T>,
    close: TokenKind,
) -> Result<T> {
    parser.stream.expect(open)?; // [!code --]
    parser.expect(open)?; // [!code ++]

    let result = inner(parser)?;  // [!code highlight]

    match parser.peek() {
        Some(token) if token.kind == close => { // [!code info]
            parser.advance();
            Ok(result)
        }
        Some(token) => { // [!code warning]
            Err(ParseError::unexpected_token(token.clone(), close))
        }
        None => Err(ParseError::unexpected_eof()), // [!code error]
    }
}
Migrating delimiter parsing to the new Parser API

That's it for code blocks. Use syntax highlighting and formatting options to make code samples clearer and easier to follow.