Skip to content

✨ Code Folding #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
9 of 13 tasks
gaetgu opened this issue Apr 24, 2022 · 7 comments
Open
9 of 13 tasks

✨ Code Folding #43

gaetgu opened this issue Apr 24, 2022 · 7 comments
Assignees
Labels
editor enhancement New feature or request UI

Comments

@gaetgu
Copy link

gaetgu commented Apr 24, 2022

Description

Code folding allows developers to collapse and expand sections of code (such as functions, classes, conditionals, and comments) to reduce visual clutter and focus on the parts they’re working on. This feature is particularly useful when navigating large files or deeply nested structures.

Proposed Behavior

  • Enable folding for code blocks such as:
    • Functions and methods
    • Classes and structs
    • Loops and conditionals
    • Multi-line comments and regions
  • Display a vertical folding ribbon in the gutter next to line numbers, where users can click to collapse or expand blocks.
  • Collapsed blocks should show an inline indicator with an expand toggle.
  • Optionally, include editor commands for fold all / unfold all.

Implementation Notes

  • Can be powered by the existing Tree-sitter syntax trees to determine foldable ranges based on code structure.
  • Should maintain fold state across file/tab switches and ideally across sessions.
  • Folding ribbon interactions should be smooth and intuitive, with hover states and visual cues.
  • Should add animation for folding and unfolding to make transitions feel natural and responsive.

Benefits

  • Reduces visual noise and helps maintain focus while editing.
  • Makes it easier to navigate and manage large files.
  • Provides a cleaner overview of structural code layout.

Additional Context

The folding ribbon provides a familiar and discoverable UI for interacting with folded sections and complements keyboard or menu-based folding commands.

Screenshots

Screen.Recording.2022-04-24.at.8.32.40.AM.mov
Additional Screenshots Screen Shot 2022-04-24 at 8 30 03 AM

Hover:
Screen Shot 2022-04-24 at 8 31 04 AM

Clicked (folded code):
Screen Shot 2022-04-24 at 8 31 40 AM

dark and light mode:
imgonline-com-ua-twotoone-0btV6nX2OfPxYH

In Progress TODOs

  • Fold Ribbon
    • Draw nested folds
    • Draw adjacent folds with a horizontal line (instead of overlapping rounded edges)
    • Draw folded ranges
  • Fold Model
    • Generate folds from a provider for a document
    • Update folds as a document is edited
  • Fold Providers
    • Line indent provider
    • TreeSitter provider
  • Perform Folding
    • Draw text attachment for fold range
    • Animate folded text
@gaetgu gaetgu added the enhancement New feature or request label Apr 24, 2022
@gaetgu
Copy link
Author

gaetgu commented Apr 26, 2022

I have been messing around with CoreGraphics a little bit, and it looks like the ribbon is a gray (in light mode), transparent line with a width of 7 (not 6 or 8, which I find strange) and a round cap. They also have a very fine white stroke around the edge. My experiments are not that great and I can't quite get them to play nice with SwiftUI views, but I thought I would leave that basic information here in case it helps.

@nanashili
Copy link

I have been messing around with CoreGraphics a little bit, and it looks like the ribbon is a gray (in light mode), transparent line with a width of 7 (not 6 or 8, which I find strange) and a round cap. They also have a very fine white stroke around the edge. My experiments are not that great and I can't quite get them to play nice with SwiftUI views, but I thought I would leave that basic information here in case it helps.

Apple likes using odd numbers in everything

@austincondiff
Copy link
Collaborator

I never understood this. I typically like designing in multiples of 2 (2, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64, 72, 80, 96, 104, etc.) because they can always divide into two without falling on a subpixel. This is especially helpful when dealing with responsive layouts and 1x and 2x densities. I think Apple chooses to design in odd numbers so that it can always have a center pixel.

@stale stale bot added the wontfix This will not be worked on label Jun 26, 2022
@CodeEditApp CodeEditApp deleted a comment from stale bot Jun 26, 2022
@stale stale bot removed the wontfix This will not be worked on label Jun 26, 2022
@austincondiff austincondiff changed the title ✨ Add Code Folding Ribbon ✨ Code Folding Ribbon Jun 28, 2022
@austincondiff austincondiff transferred this issue from CodeEditApp/CodeEdit Jun 28, 2022
@austincondiff
Copy link
Collaborator

This may require #127 to be complete in order to begin working on this.

@inlinecoder
Copy link

inlinecoder commented Jan 9, 2025

My 5 cents: while this ribbon looks great in XCode... well, it just looks great and brings close to zero value.
What does make life easier and is actually helpful is code blocks indents.

Hopefully, this feature won't implement XCodes' direction blindly. 🤞

UPD 5 mins later: I was referring to #65

Image

@austincondiff austincondiff changed the title ✨ Code Folding Ribbon ✨ Code Folding Feb 27, 2025
@austincondiff
Copy link
Collaborator

FYI, I've added the issue description for increased clarity.

@austincondiff austincondiff moved this from 📋 Todo to 🏃‍♂️ In Progress in CodeEdit Project Apr 25, 2025
thecoolwinter added a commit to CodeEditApp/CodeEditTextView that referenced this issue May 9, 2025
### Description

Adds an API for creating "text attachments". Essentially, views that replace ranges of text and act as a single character in typesetting, layout, and selection.

#### Detailed Changes

Text layout consists of two steps:

- Laying out whole lines
- Typesetting line fragments

The changes in this PR mostly consist of changes to the typesetting step. This step breaks down a line of text into fragments that fit into a constrained width. Text attachments are built by making 'runs' of content in that typesetting step.

> These are intentionally kept separate from the text storage. If these modifications were in the storage object, they'd be shared between editors that share storage objects. Putting these in the layout system means that a user can fold lines in one editor, and view them normally in another.

- Text attachments:
  - **New** `TextAttachment` protocol. A generic type that can draw it's contents in a line.
  - **New** `AnyTextAttachment` helps type-erase `any TextAttachment` and has a `range` for CETV to use. Very similar to `AnyHashable` or `AnyView`.
  - **New** `TextAttachmentManager` manages an ordered array of attachments, and manages hiding and showing text lines as needed, as well as invalidating layout when modifications happen.
- `TextLayoutManager` changes:
  - Added a new `determineVisiblePosition` method. This method takes in a line position and returns a new (potentially larger) position by merging lines covered by attachments. This is the foundational method for merging lines that attachments cover.
  - Removing the existing `Iterator`.
  - Added two iterators, `YPositionIterator` and `RangeIterator` that iterate over a range of y positions and text offsets, respectively. These iterators are now used by the `layoutLines` method to merge lines that have attachments and not layout hidden lines.
- Typesetting:
  - `Typesetter.swift` is marked as new, but that's because it's drastically changed.
    - `Typesetter` still performs typesetting on a text line, but it now takes into account attachments. It breaks the line into content runs, then calculates line fragments using those runs and a constrained width.
  - `TypesetContext` and `LineFragmentTypesetContext` represent partial parsing states while typesetting. They're both used once during typesetting and then discarded. Keeping them in their own structs makes `Typesetter` much more readable.
  - `CTLineTypesetData` was previously represented by a tuple, but a struct makes things clearer. It represents layout information received from a `CTTypesetter` for a `CTLine`.
  - Line break suggestion methods moved to a `CTTypesetter` extension. Each method was taking a `typesetter` argument, so moving to an extension makes them more ergonomic.
    - The only change to these methods was a change from passing a `startOffset` to instead pass a `subrange` that the typesetter finds line breaks in.
- `LineFragment`
  - Line fragments now have to manage a series of content runs that can be either attachments or plain text.
  - Drawing methods have been updated to loop over runs and draw them.
  - Position fetching methods now take into account attachments as well as text positions.
- Scroll view listeners - *this could have been a different PR but it's a small change, sorry!*.
  -  Fixed up the way the text view found it's enclosing scroll view, and listens to scroll changes.

#### Testing

- Added typesetting tests for attachments.
- Added layout manager tests.
  - Iteration
  - Invalidation
  - Attachments

### Related Issues

* CodeEditApp/CodeEditSourceEditor#43
* CodeEditApp/CodeEditSourceEditor#287
* CodeEditApp/CodeEdit#1623

### Checklist

- [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
- [x] The issues this PR addresses are related to each other
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] My changes are all related to the related issue above
- [x] I documented my code

### Screenshots

> Demo menu item was a testing menu item, it either adds a demo attachment to the selected range, or removes selected attachments (if any are selected). It's not included in this PR.

To test the changes like in the demo video replace `TextView+Menu.swift` with this:
<details><summary>`TextView+Menu.swift`</summary>
<p>

```swift
//
//  TextView+Menu.swift
//  CodeEditTextView
//
//  Created by Khan Winter on 8/21/23.
//

import AppKit

extension TextView {
    override public func menu(for event: NSEvent) -> NSMenu? {
        guard event.type == .rightMouseDown else { return nil }

        let menu = NSMenu()

        menu.items = [
            NSMenuItem(title: "Cut", action: #selector(cut(_:)), keyEquivalent: "x"),
            NSMenuItem(title: "Copy", action: #selector(copy(_:)), keyEquivalent: "c"),
            NSMenuItem(title: "Paste", action: #selector(paste(_:)), keyEquivalent: "v"),
            NSMenuItem(title: "Attach", action: #selector(toggleAttachmentAtSelection), keyEquivalent: "b")
        ]

        return menu
    }

    class DemoAttachment: TextAttachment {
        var width: CGFloat = 100

        func draw(in context: CGContext, rect: NSRect) {
            context.setFillColor(NSColor.red.cgColor)
            context.fill(rect)
        }
    }

    @objc func toggleAttachmentAtSelection() {
        if layoutManager.attachments.get(
            startingIn: selectedRange()
        ).first?.range.location == selectedRange().location {
            layoutManager.attachments.remove(atOffset: selectedRange().location)
        } else {
            layoutManager.attachments.add(DemoAttachment(), for: selectedRange())
        }
    }
}

```

</p>
</details> 


https://github.com/user-attachments/assets/b178fe13-d5d2-4e3d-aa55-f913df1d6c4b
thecoolwinter added a commit that referenced this issue May 29, 2025
### Description

> [!NOTE]
> For reviewers, this is merging into the dev branch. These changes
require the version of CETV in [this
PR](CodeEditApp/CodeEditTextView#93). Please
pull those changes locally and test using that.

> [!NOTE]
> I'll be making some TODOs in the tracking issue #43 for things that
aren't included here. Like the overlapping folds UI issue.

Adds the first version of the code folding ribbon, with a very basic
folding model.

This is mostly a UI change. It includes changes to the gutter, and a new
view for displaying folds. The model and related demo fold provider
should be considered incomplete and only for demo purposes.

This also doesn't implement the hover state yet. Just a very basic
outline of everything.

Things to review:
- New `FoldingRibbonView`
- New `LineFoldingModel`
- Changes in `GutterView`
- Changes to `TextViewController`
- Changes to `CodeEditSourceEditor`

### Related Issues

* #43 

### Checklist

- [x] I read and understood the [contributing
guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md)
as well as the [code of
conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
- [x] The issues this PR addresses are related to each other
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] My changes are all related to the related issue above
- [x] I documented my code

### Screenshots

Light mode.
![Screenshot 2025-05-07 at 3 22
17 PM](https://github.com/user-attachments/assets/a9b7838f-6bea-4bb4-bd61-b72175c76788)

Dark Mode.
![Screenshot 2025-05-08 at 10 12
45 AM](https://github.com/user-attachments/assets/fb8e3264-71ec-40aa-9d62-7d4a74c15343)

Folds are transparent for scrolling text.
![Screenshot 2025-05-08 at 10 08
35 AM](https://github.com/user-attachments/assets/17a1623c-3e8e-40a5-ace3-6adbe8e13320)

---------

Co-authored-by: Austin Condiff <[email protected]>
thecoolwinter added a commit that referenced this issue May 30, 2025
### Description

This makes the `StyledRangeStore` type generic and `Sendable`. This type
was originally created for storing relative ranges of highlight values
(captures and modifiers). It excels at storing values for subranges of
data even for large documents. I'm hoping to make this type a generic
type we can use in other places in the package, like code folding, to
store document state that needs to be maintained in lock-step with the
document's real contents.

Detailed changes:
- Renamed `StyledRangeStore` to `RangeStore`, as well as all associated
types.
- Made `RangeStore` a value type (struct) with copy-on-write semantics,
allowing for it to be concurrency safe and `Sendable`.
> This doesn't have any effect on performance with the existing
highlighter code. The highlighter code modifies the storage objects it
uses in-place, so there is no copying necessary, only mutating.
- Made `RangeStore` store a new, generic, `RangeStoreElement` type.
- Updated `StyledRangeContainer` to use the updated `RangeStore`, with a
custom element type that stores captures and modifiers.
- Updated `StyledRangeContainer` to use a raised version of the
`combineLower[Higher]Priority` methods only where they're relevant
(instead of the requirement being on `RangeStoreElement`).
- Updated relevant tests.

### Related Issues

* #43

### Checklist

- [x] I read and understood the [contributing
guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md)
as well as the [code of
conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
- [x] The issues this PR addresses are related to each other
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] My changes are all related to the related issue above
- [x] I documented my code

### Screenshots

No behavior changes. This is in prep for #43, but also lays the
groundwork for other features using a type like this.
thecoolwinter added a commit that referenced this issue Jun 3, 2025
### Description

Adds the hover interaction to the code folding ribbon.

Details:
- Animates in when entering the fold region.
- Does not animate when moving between folds after animation.
- Hovered lines are emphasized and not transparent.

### Related Issues

* #43 

### Checklist

- [x] I read and understood the [contributing
guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md)
as well as the [code of
conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
- [x] The issues this PR addresses are related to each other
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] My changes are all related to the related issue above
- [x] I documented my code

### Screenshots


https://github.com/user-attachments/assets/164e61e9-07c0-4a0c-814d-7a70226e0136

---------

Co-authored-by: Austin Condiff <[email protected]>
@thecoolwinter
Copy link
Collaborator

Updated the todo items with the changes in #331. Very close to being done here

thecoolwinter added a commit to CodeEditApp/CodeEditLanguages that referenced this issue Jun 11, 2025
…on) (#87)

### Description

This PR introduces a new build step, where missing tree-sitter query files are copied from nvim-treesitter. Neovim has done an amazing job curating good tree-sitter language support through queries. They use the same parsers we do, but have implemented a few more queries on top of the ones implemented in the parent language repositories.

I don't want to look like we're just stealing the work neovim has done. Neovim is under the Apache 2.0 license, and each query file used from their project is copied with a copyright notice directing contributors to their repository. Hopefully the sharing of query files means issues found in CodeEdit will be fixed for Neovim as well.

### Related Issues

* CodeEditApp/CodeEditSourceEditor#43 - required to implement code folding using tree-sitter.

### Checklist

- [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
- [x] The issues this PR addresses are related to each other
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] My changes are all related to the related issue above
- [x] I documented my code

### Screenshots
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
editor enhancement New feature or request UI
Projects
Status: 🏃‍♂️ In Progress
Development

No branches or pull requests

6 participants