Skip to content
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

[SR-15880] Add ability to swift-format to only re-format certain ranges of a file #297

Open
ahoppen opened this issue Feb 18, 2022 · 4 comments · May be fixed by #708
Open

[SR-15880] Add ability to swift-format to only re-format certain ranges of a file #297

ahoppen opened this issue Feb 18, 2022 · 4 comments · May be fixed by #708

Comments

@ahoppen
Copy link
Contributor

ahoppen commented Feb 18, 2022

Previous ID SR-15880
Radar rdar://112190730
Original Reporter @ahoppen
Type New Feature
Additional Detail from JIRA
Votes 0
Component/s swift-format
Labels New Feature
Assignee None
Priority Medium

md5: 55c33a2de9a258402756fbffd509321e

Issue Description:

swift-format currently only has the ability to re-format the entire file. Especially when working in projects whose current indentation cannot be fully described by swift-format configuration files and where re-formatting the entire source code is not an option, it makes swift-format basically unusable.

What I do enjoy is git-clang-format’s ability to only re-format the lines that I touched. It would be nice if swift-format had a similar functionality to only format certain ranges of a file.

@ahoppen
Copy link
Contributor Author

ahoppen commented Feb 18, 2022

CC @allevato

@allevato
Copy link
Collaborator

This is the number one feature I'd like to add. Even though we require all code to have the canonical format before it's merged at Google (so we always do full file formats), supporting range formatting would make it easier for us to make targeted changes to the style guide and roll those out without introducing unrelated changes to people's code the first time they format it with the new version.

But it's complicated by a few unique problems. I've thought about various approaches to going about it, but I've never had a solid block of time to explore it. I'll jot down some stream-of-consciousness here so there's a record of some of the issues that have been on my mind about it.

The simplest case is if the selected line range covers a contiguous block of list-like-items—statements/CodeBlockItems, member decl list items, array/dictionary literal elements. Then each of those can just be formatted independently, seeding the indentation with whatever the indentation of the first line was.

If a selection intersects multiple nodes, the desired behavior is less obvious. For example, if someone passes a line range that starts in the middle of a function's argument list and ends in the middle of some loop in the function:

func foo<T: P, U: Q>(
    arg: T,
   arg2: U,               // start at this line
  arg3: [T]) {
   print("hello")
 print("world")
  for element in arg3
  {
         print(element)
    print(arg)            // end at this line
  print(arg2)
      }
}

One approach would be to find the nearest ancestor node of both anchors and format everything in that node, but that would modify lines that weren't in the range, which might be unacceptable. But our formatting approach is very context-sensitive—we need to know the indentation of the func (which is outside the range) to know what level to indent the ) { that terminates the argument list after we move it down to the next line, and then that determines the indentation of the rest of the statements in the function body that are captured in the range... we'd need to do more research into what other formatters do in these kinds of situations.

Another complication is the current model that we use in TokenStreamCreator/PrettyPrinter; a linear stream of formatting commands, which is constructed as the syntax tree is visited by calling methods that indicate what should happen before or after a particular node. This linear stream contains tokens to open/close groups, and those tokens have to be balanced. In most situations, the open/close tokens are symmetrically handled within a particular node's visit method, but there are a few situations where that doesn't quite work and they're mismatched, in order to get the formatting to work out a certain way—one visit method might open a group and then a different visit method would close it. We'd need to be very careful that partial-range formatting didn't break any of those guarantees.

One thing I had hoped to be able to do was completely rewrite TokenStreamCreator/PrettyPrinter to get rid of the linear stream implementation, partly because of that fragility (and because, as special cases became necessary, it's become quite complicated to work with and I'm positive that it's a barrier for new contributions). I've experimented with a tree-based command model (heavily inspired of Javascript's Prettier) that maps more elegantly to the syntax tree, but it's still missing some critical pieces. I don't know if that model necessarily makes it easier to determine the desired behavior when the selected line range doesn't overlap cleanly with sensible syntax nodes, but I think it would solve the imbalance problem.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@shahmishal shahmishal transferred this issue from apple/swift May 9, 2022
@vemishel
Copy link

Just mentioning that this functionality could be very nice to have 🙂

@MahdiBM
Copy link

MahdiBM commented Dec 5, 2023

Noticing Saafo's issue, i just want to bump this up and mention that i think this is a very important feature to make an IDE like vscode useable for writing Swift code in existing codebases.
My attempts to add such a feature to vknabel's formatter extensions did not turn out perfect and it seems to me that it requires built-in support in the formatter tool itself. For now I'm using a local version of vknabel's SwiftFormat (not swift-format) which uses diffing mechanisms to only format a specific range of lines, and although it's not perfect at times, it's still far better than not having it at all, specially combined with on-type formatting.

DaveEwing added a commit that referenced this issue Mar 22, 2024
…es) <#297>.

The basic idea here is to insert `enableFormatting` and `disableFormatting` tokens into the print stream when we enter or leave the selection. When formatting is enabled, we print out the tokens as usual. When formatting is disabled, we turn off any output until the next `enableFormatting` token. When that token is hit, we write the original source text from the location of the last `disableFormatting` to the current location.
Note that this means that all the APIs need the original source text to be passed in.
This initial commit doesn't really have the formatting working well - we need to tune where those tokens are inserted into the stream a bit better. But this does seem to show that the approach should work.
DaveEwing added a commit that referenced this issue Mar 22, 2024
Use nil for `enableFormatting` to indicate that we're done processing the file (otherwise a selection starting at the BOF would output the file twice!).
Support selection ranges starting/finishing inside initial trivia.
@DaveEwing DaveEwing linked a pull request Mar 22, 2024 that will close this issue
DaveEwing added a commit that referenced this issue Mar 26, 2024
Do a better job of getting the whitespace right when entering/leaving a selection. For trivia, only enable/disable around comments.
DaveEwing added a commit that referenced this issue Mar 27, 2024
Allow marked text to use `➡️` and `⬅️` to deliniate the start/end of a range of a selection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants