Skip to content

✨ Placeholder Support #287

Description

@austincondiff

Description

Implement support for placeholders in CodeEdit, similar to Xcode’s <#placeholder#> feature. This would allow developers to insert and cycle through editable placeholders in their code, improving productivity and code clarity during development.

Use Case

This feature would be particularly useful for defining temporary or template-like code constructs, such as:

let <#name#> = <#value#>

Placeholders would:

  1. Be highlighted visually.
  2. Be navigable (e.g., via Tab key).
  3. Allow inline editing to replace the placeholder text.

Expected Behavior

  • When a placeholder is inserted (e.g., <#placeholder#>), it should:
  • Appear visually distinct (e.g., highlighted with a subtle background color or outline).
  • Be editable directly in the code editor.
  • Allow navigation between placeholders using keyboard shortcuts like Tab and Shift+Tab.
  • Placeholders should support nested constructs (e.g., let <#name#> = <#type#>(<#arguments#>)).

Technical Notes

  • Integrate placeholder handling with the current text editing features.
  • Consider compatibility with existing syntax highlighting and LSP integration.

Steps to Implement

  1. Define a syntax for placeholders (e.g., <#placeholder#>).
  2. Update the text editor to recognize and render placeholders.
  3. Add keyboard navigation support for placeholders.
  4. Ensure placeholder edits are reflected in the editor state.

Additional Context

This feature would align CodeEdit with Xcode’s behavior, making it a more familiar and seamless experience for developers who work on macOS. It also complements other code editing enhancements, such as autocompletion and LSP support.

Screenshots

image

Activity

  1. moved this from 🆕 New to 📋 Todo in CodeEdit Projecton Jan 10, 2025
  2. thecoolwinter commented on Jan 10, 2025

    @thecoolwinter
    Collaborator

    I wonder if we want to go the route VSCode went with placeholders. They call them tab stops, and I think they're a little more flexible than Xcode's in a specific way. I like that Xcode's allow you to create the placeholders by inserting a special character sequence. It allows for copying placeholders from anywhere, and they automatically are formatted so you can tab between them. VSCode's implementation, though, allows you to have named placeholders. For instance, you may have a snippet like this:

    for (let $1 = 0; $1 < $2.length; $1++) {
        const $3 = $2[$1];
    }

    As the user types in $1 or $2, all the other placeholder positions that match that number are filled in. In the example, the user only has to type i, array, and element once to get:

    for (let i = 0; i < array.length; i++) {
        const element = array[i];
    }

    This approach may be easier to implement for our case as well. If we're not using a special character sequence, we don't have to modify how text is rendered. Instead, we could insert the placeholders as regular text and keep track of the tab stops as the user edits.

    We could also try to detect content with placeholders as the user pastes and suggest they paste it as a snippet when it's detected. That would help band-aid share-ability that we lose by not going the Xcode route.

  3. austincondiff commented on Jan 10, 2025

    @austincondiff
    CollaboratorAuthor

    I like the idea of reusable placeholders. What if we use Xcodes approach but look at the name to see if it is the same? For example:

    for (let <#iterator#> = 0; <#iterator#> < <#array#>.length; <#iterator#>++) {
        const <#element#> = <#array#>[<#iterator#>];
    }

    In this case, placeholders with the same name (e.g., <#iterator#> or <#array#>) would automatically update all occurrences as the user edits one of them. This approach aims to combine the easier-to-read syntax and shareability of Xcode’s method with the flexibility of linked placeholders like in VSCode. I can see the appeal of VSCode’s use of numbered tab stops for simplicity and easier tracking in more complex snippets, so perhaps there’s a middle ground we could explore. What do you think about balancing these approaches to keep both usability and flexibility in mind?

    My concern with the simplicity of VS Code's approach is that it may interfere with the syntax of the code in the snippet whereas Xcode's syntax is less likely to interfere. You could always do something like <#1#>, <#2#>, <#3#>, etc.

    The advantage of VS Code's tab stops approach is that you can customize the order of the tab stops.

  4. thecoolwinter commented on Jan 12, 2025

    @thecoolwinter
    Collaborator

    Okay after some more discussion, @austincondiff and I came up with this syntax for placeholders:

    <#varnumber:defaultvalue#>
    

    When importing snippets from VSCode or TextMates we'll have to convert their syntax ${number:defaultval} into ours, but it also means Xcode placeholders will work out of the box too.

    We discussed what changes are required for this to work, and it'll require two changes in CodeEditTextView. First is a delegate method for modifying how a selection is updated. So when a user puts a cursor on a placeholder, or selects around it, the selection updates to include the entire placeholder range. Something like

    protocol SelectionManagerDelegate: AnyObject {
        func selectionWillUpdate(selectionManager: SelectionManager, selection: TextSelection, newRange: NSRange) -> NSRange?
    }

    The second change is a change to allow a delegate object to custom render line fragments. This is similar to how NSTextView allows for custom rendering, and would only be triggered by the placeholder drawing code when necessary. This would look something like

    protocol LineFragmentDrawing {
        // return false if not drawn by this method.
        func drawLineFragment(ctLine: CTLine, context: CGDrawingContext) -> Bool
    }

    Screenshot 2025-01-11 at 10 14 21 AM

    Alternative to the custom drawing method, we could set the font size to 0.1 for the <# and #> characters, which effectively hides those characters entirely.

  5. hi2gage commented on Jan 18, 2025

    @hi2gage
    Contributor

    Based on our conversation in the weekly meeting:

    I asked:
    Is there a use case for placeholders that would make "reusable placeholders" unhelpful or problematic?
    What is the root problem are we trying to solve by adopting VSCode-style placeholders?


    Placeholder Use Beyond Variable Names

    In Xcode, placeholders are used for more than just variable names. They can represent larger code bodies, for example:

    if <#condition#> {
        <#statements#>
    } else {
        <#statements#>
    }

    or

    func <#name#>(<#parameters#>) -> <#return type#> {
        <#function body#>
    }

    This illustrates that placeholders often represent different elements of the code structure, not just linked values. I think we need to think beyond variable names when considering placeholder functionality.


    Namespacing Concerns

    One major issue with "reusable placeholders" is the forced namespacing of placeholder names and values. Consider this snippet:

    if <#condition#> {
        <#statements#>
    } else {
        <#statements#>
    }

    In this case, the statements placeholder is used twice but represents different content. With reusable placeholders, users would need to namespace them (e.g., <#statements1#> and <#statements2#>), which adds unnecessary cognitive load and complexity.


    Xcode's Reusable Placeholder Workaround

    After further research, I found that Xcode does provide a workaround for reusable placeholders by leveraging multi-cursor editing:

    • Select Previous Occurrence: ⌥⇧⌘E
    • Select Next Occurrence: ⌥⌘E
    • Select Find All Matches: (No default shortcut, but one can be added)

    Source

    Image

    Quirks of Xcode's multi-cursor Implementation

    While that work around is functional, it has quirks. For example, exiting multi-cursor editing causes the cursor to jump to the last token. This means users need to press TAB again to navigate to the next placeholder.

    Snippet flow can also affect the placeholder navigation:

    Example 1:

    var <#variable name#>: <#type#> {
        set {
            <#variable name#> = newValue
        }
        get {
            <#statements#>
        }
    }

    Flow:

    1. Tab to the first variable name.
    2. Use ⌥⌘E to select all occurrences.
    3. Enter the variable name.
    4. Press ESC, then TAB to move to statements.

    Example 2:

    var <#variable name#>: <#type#> {
        get {
            <#statements#>
        }
        set {
            <#variable name#> = newValue
        }
    }

    Flow:

    1. Tab to the first variable name.
    2. Use ⌥⌘E to select all occurrences.
    3. Enter the variable name.
    4. Press ESC, then TAB to move to type.

    This shows that the order of the placeholders matters from the perspective of an auto complete snippet.


    Why I Prefer Xcode-Style Placeholders

    1. Better for Sharing Code:
      I often share code snippets with placeholders between coworkers. This style placeholder is much Xcode's style makes sharing simpler and more intuitive. I'll be honest I use placeholders for that more than I do snippets.

    2. No Namespacing Hassles:
      Xcode’s approach avoids the need for users to manually namespace placeholders, which is especially important for placeholders representing distinct bodies of code.

    3. Ease of Use for Defaults:
      The ability to double-click on default values (e.g., <#SomeSpecialType#>) is extremely helpful for opinionated placeholders, allowing quick replacement while maintaining clarity. This is helpful because I can immediately go to that type definition and see documentation etc.


    On Supporting Other Editors

    During the meeting, @austincondiff raised an important point about ensuring users of other editors (e.g., VSCode) don’t feel alienated or face difficulty importing snippets.

    While I agree with this sentiment, I believe we could address it by building a robust snippet importer that converts formats like VSCode’s ${number:defaultvalue} into Xcode-compatible placeholders (<#number#>).

    Looking at vscode-swift's snippets they have adopted vscode's placeholder style. This was an itch that I scratched wondering if placeholder style could be language specific instead of editor specific.

    I would lean towards aligning CodeEdit more closely with Xcode because it feels like it fits the ethos of this project. Every code editor has its own way of handling placeholders, and perhaps we want to embrace the Xcode approach to make it easier for newcomers / be opinionated about placeholders. (Snippets could be a different story?)

    Plus, I bet the server-side Swift users would likely appreciate this consistency.


    Snippet Usage with AI tools

    With the rise of AI tools I would think the reliance on pre-defined snippets (including those benefiting from reusable placeholders) is diminishing. In this context, being more opinionated and Xcode-aligned has fewer drawbacks, especially from a usability and share-ability standpoint. I would vote for a more human readable placeholder over the ability to have fancy snippets.


    Conclusions

    I vote Xcode style placeholders.

    Any feedback or additional thoughts would be awesome!
    Are there any edge cases or placeholder uses that I’ve missed?
    Do I sound like an Xcode fanboy yet? 😬

    I'm also very new to this project so I can't help with implementation details but maybe once I get up to speed I can talk more in that area.

  6. added a commit that references this issue on May 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions