posit-dev / posit-dev/py-shiny

Express: Support trailing semicolon as a signal to not display that expression

Open
#807 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.8k
Forks
135
Avg merge
2d 18h
Merged PRs (30d)
21

Description

In Jupyter, you can give an expression a trailing semicolon to suppress display.

"hello";

will show nothing.

Below is some code, inspired by IPython.core.displayhook, that will determine if an AST node has a trailing semicolon. (This information is not included in the AST so we have to go back and tokenize the source code.)

This should be very easy to integration into @display_body/@render.display because those functions already descend down to the level of each auto-displayed expression and wrap them with sys.displayhook(). This code can be used to decide on a node-by-node basis whether to skip that transformation.

However, it's not as easy with shiny.express._run, which executes e.g. with sidebar(): and its contents as a single top-level node. In order to support the semicolon check, we'd have to either do more AST transformation up front, or change the implementation of shiny.express._run to work more like @display_body.

def expr_ends_in_semicolon(node: ast.AST, filename: str) -> bool:
    if node.end_lineno is None:
        return False

    end_line: int = node.end_lineno

    last_line = linecache.getline(filename, end_line)
    if last_line == "":
        # The line could not be loaded (file not found?)
        return False

    # Do a cheap check first
    if ";" not in last_line:
        return False

    def lines():
        lineno = node.lineno
        while lineno <= end_line:
            line = linecache.getline(filename, lineno)
            # if lineno == node.end_lineno:
            #     line = line[: node.end_col_offset]
            if lineno == node.lineno:
                line = line[node.col_offset :]
            yield line
            lineno += 1
        yield ""

    gen_lines = lines()

    tokens = list(tokenize.generate_tokens(lambda: next(gen_lines, "")))

    for token in reversed(tokens):
        if token[0] in (
            tokenize.ENDMARKER,
            tokenize.NL,
            tokenize.NEWLINE,
            tokenize.COMMENT,
        ):
            continue
        if (token[0] == tokenize.OP) and (token[1] == ";"):
            return True
        else:
            return False

    return False

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading @display_body, @render.display, and shiny.express._run to understand how expressions are auto-displayed and how top-level blocks are executed. Use the provided expr_ends_in_semicolon logic and the Jupyter example as behavioral references; done means trailing-semicolon expressions are suppressed without affecting other expressions, including the relevant execution paths.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.