-
-
Notifications
You must be signed in to change notification settings - Fork 299
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
Code action to organize imports #2051
Code action to organize imports #2051
Conversation
I'm not sure about this being behind A problem for having a "blessed" location in the file for imports, is a few projects (including zig itself) are actually moving to have all imports at the bottom of files. Due to feeling like imports at the top are usually just noise to be scrolled past to get to the meat of the file. Also I can't get the code action to apply in vscode, neither as a quick fix nor in the command palette. |
Interesting stuff! I agree, I just wanted the diagnostic to not be on by default (people have preferences and it could be annoying). So two solutions that come to mind are 1) creating an entirely new config flag or 2) scrapping the diagnostic and having the code action just available whenever. I will take a look at the problem you mention. It is supposed to appear when having the cursor on the diagnostic. |
Thank you for taking the time to revive #881.The detailed an thorough description of this PR is very helpful. I will try to answer/address some of the points that you have mentioned.
There are three "builtin packages":
This function has been moved to
Zig requires that the first parameter of
IIRC, this function was added to make it easier to control which string/name is used to do sorting. #881 sorted imported files like
This was added as an optimization. The given range tells us for which part of the document the code actions should be computed. If the imports are not visible then there is no need to compute the code action. It not necessary to bring this back to get this PR merged.
What about non doc-comments (
I have the same problem. Using the "Source Action..." command in VS Code is the only way I was able to run the code actions. I would also prefer to not have this feature rely on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are some thing I noticed from looking over the code. I will give this a more thorough review later on.
There are two possibles I could imagine: Move alias to the end of each sectionconst std = @import("std");
const Ast = std.zig.Ast;
const Server = std.zig.Server;
const lsp = @import("lsp");
const AnyTransport = lsp.AnyTransport;
const types = lsp.types;
const DocumentScope = @import("../DocumentScope.zig");
const Declaration = DocumentScope.Declaration;
const Scope = DocumentScope.Scope; Move alias after all importsconst std = @import("std");
const lsp = @import("lsp");
const DocumentScope = @import("../DocumentScope.zig");
// Not sure if we should sort by variable name or "path". I think path is more reasonable
const Ast = std.zig.Ast;
const Server = std.zig.Server;
const AnyTransport = lsp.AnyTransport;
const types = lsp.types;
const Declaration = DocumentScope.Declaration;
const Scope = DocumentScope.Scope; I would personally prefer the first option but I am open to suggestions.
Maybe move them into their own section after all |
I am happy to say that I made progress on the VS Code situation. zls_organize_imports_on_save.mp4But as you pointed out, the Organize Imports does not show up in the Command pallet and the shortcut does not work either. So I investigated with other LSPs. After looking around the code, I found the difference in registering --- a/src/Server.zig
+++ b/src/Server.zig
@@ -615,7 +615,9 @@ fn initializeHandler(server: *Server, arena: std.mem.Allocator, request: types.I
},
.documentHighlightProvider = .{ .bool = true },
.hoverProvider = .{ .bool = true },
- .codeActionProvider = .{ .bool = true },
+ .codeActionProvider = .{ .CodeActionOptions = .{ .codeActionKinds = &.{
+ .@"source.organizeImports",
+ } } },
.declarationProvider = .{ .bool = true },
.definitionProvider = .{ .bool = true },
.typeDefinitionProvider = .{ .bool = true }, And it's there. I suppose it has to be explicitly listed as supported. So how do we implement this? Simply create an array of all the code actions we support? |
I don't think there is any alternative to just hard-coding the supported code action kinds. I also noticed that Sublime Text LSP requires |
Moving aliases to the right group has been implemented. I went for grouping based on the parent import: const abc = @import("abc.zig");
const abc_related = abc.related;
const abc_related2 = abc.related2;
const xyz = @import("xyz.zig");
const xyz_related = xyz.related; I am interested in what you think could be improved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan to give some of the code in getImportsDecls
a closer look later on but this is what I found so far. The sorting behavior seems to work well and I did not encounter issues besides the infinite loop thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for repeatedly requesting changes but this time it is going to be the last. I promise :)
What about grouping by directory for the file imports? Perhaps if there are > 1 file imports from the same directory, they could be in a group of their own.
No sure, I wouldn't be against this. I do have an alternative idea on how to do the grouping that is a bit more flexible so that we wouldn't need to decide these things. I will try to open a follow-up issue about it (unless I forget about it).
What about
@embedFile
?
I would suggest to either move them into their own section all imports and aliases or just leave them be. Ignoring them will kind of achieve the same thing. This can always be changed afterwards.
Is there anything else you would still like to discuss before this gets merged?
Thanks for collaborating and taking the time to teach me more about Zig! The followup tasks would need some benchmarking (never optimize blindly). Do you have a recommended microbenchmark setup for Zig? |
This post on ziggit.dev may be useful to you. My usually workflow involves perf, hotspot and poop. If you wish to use this as an opportunity to learn more about optimizing code then you can just use my benchmarking program and try to optimize it yourself. I would still suggest to use f27d726 because it just performs a more efficient version of calling Here is a showcase of the optimized version performed poop: Show Results
The speedup is less extreme on smaller (more realistic) files. |
This reverts commit 64b1fb7.
Fixes detection of some capabilities in vscode and possibly sublime text
Surely there are some edge cases I have not thought of
infinite loop bug with @embedfile
This function is much fast at converting multiple ranges on the same document.
- use a bitset to skip root decls - use a hash set to lookup import decls based on their Ast Node
Hopefully introduces zero new bugs
098ed5b
to
dc685d9
Compare
Great success! Bench - reproduced results❯ poop zig-out/bin/bench-orig zig-out/bin/bench-techatrix
Benchmark 1 (3 runs): zig-out/bin/bench-orig
measurement mean ± σ min … max outliers delta
wall_time 95.0s ± 2.43s 93.4s … 97.8s 0 ( 0%) 0%
peak_rss 53.1MB ± 135KB 53.0MB … 53.2MB 0 ( 0%) 0%
cpu_cycles 425G ± 1.60G 424G … 427G 0 ( 0%) 0%
instructions 1.01T ± 246K 1.01T … 1.01T 0 ( 0%) 0%
cache_references 1.58G ± 35.9M 1.55G … 1.62G 0 ( 0%) 0%
cache_misses 43.3M ± 5.08M 38.1M … 48.2M 0 ( 0%) 0%
branch_misses 141M ± 42.0K 141M … 141M 0 ( 0%) 0%
Benchmark 2 (3 runs): zig-out/bin/bench-techatrix
measurement mean ± σ min … max outliers delta
wall_time 43.2s ± 189ms 43.0s … 43.4s 0 ( 0%) ⚡- 54.5% ± 4.1%
peak_rss 111MB ± 154KB 111MB … 111MB 0 ( 0%) 💩+108.8% ± 0.6%
cpu_cycles 192G ± 1.01G 191G … 193G 0 ( 0%) ⚡- 54.7% ± 0.7%
instructions 478G ± 228K 478G … 478G 0 ( 0%) ⚡- 52.7% ± 0.0%
cache_references 540M ± 36.6M 514M … 582M 0 ( 0%) ⚡- 65.8% ± 5.2%
cache_misses 15.7M ± 1.01M 14.7M … 16.7M 0 ( 0%) ⚡- 63.8% ± 19.1%
branch_misses 11.5M ± 68.8K 11.5M … 11.6M 0 ( 0%) ⚡- 91.8% ± 0.1% I noticed from the profiler that the Then I circumvented the Bench - direct root scope lookup (improvement over the first optimization)Benchmark 1 (3 runs): bench-techatrix
measurement mean ± σ min … max outliers delta
wall_time 42.7s ± 604ms 42.1s … 43.3s 0 ( 0%) 0%
peak_rss 111MB ± 18.8KB 111MB … 111MB 0 ( 0%) 0%
cpu_cycles 190G ± 1.55G 189G … 191G 0 ( 0%) 0%
instructions 478G ± 855K 478G … 478G 0 ( 0%) 0%
cache_references 552M ± 29.6M 530M … 585M 0 ( 0%) 0%
cache_misses 19.6M ± 947K 18.5M … 20.4M 0 ( 0%) 0%
branch_misses 11.6M ± 48.9K 11.5M … 11.6M 0 ( 0%) 0%
Benchmark 2 (3 runs): bench-root-scope-no-strings
measurement mean ± σ min … max outliers delta
wall_time 2.56s ± 23.3ms 2.54s … 2.59s 0 ( 0%) ⚡- 94.0% ± 2.3%
peak_rss 111MB ± 75.9KB 111MB … 111MB 0 ( 0%) - 0.1% ± 0.1%
cpu_cycles 9.39G ± 26.2M 9.37G … 9.42G 0 ( 0%) ⚡- 95.0% ± 1.3%
instructions 27.0G ± 413K 27.0G … 27.0G 0 ( 0%) ⚡- 94.4% ± 0.0%
cache_references 107M ± 571K 106M … 107M 0 ( 0%) ⚡- 80.6% ± 8.6%
cache_misses 5.33M ± 64.1K 5.28M … 5.40M 0 ( 0%) ⚡- 72.7% ± 7.8%
branch_misses 10.6M ± 33.6K 10.5M … 10.6M 0 ( 0%) ⚡- 8.8% ± 0.8% Bench - total differenceBenchmark 1 (3 runs): bench-before
measurement mean ± σ min … max outliers delta
wall_time 94.9s ± 292ms 94.6s … 95.2s 0 ( 0%) 0%
peak_rss 53.2MB ± 119KB 53.1MB … 53.3MB 0 ( 0%) 0%
cpu_cycles 427G ± 1.03G 426G … 428G 0 ( 0%) 0%
instructions 1.01T ± 639K 1.01T … 1.01T 0 ( 0%) 0%
cache_references 1.57G ± 28.1M 1.54G … 1.59G 0 ( 0%) 0%
cache_misses 46.0M ± 4.86M 42.0M … 51.4M 0 ( 0%) 0%
branch_misses 141M ± 47.9K 141M … 141M 0 ( 0%) 0%
Benchmark 2 (3 runs): bench-final
measurement mean ± σ min … max outliers delta
wall_time 2.58s ± 2.60ms 2.57s … 2.58s 0 ( 0%) ⚡- 97.3% ± 0.5%
peak_rss 111MB ± 108KB 110MB … 111MB 0 ( 0%) 💩+107.9% ± 0.5%
cpu_cycles 9.41G ± 9.30M 9.40G … 9.42G 0 ( 0%) ⚡- 97.8% ± 0.4%
instructions 27.0G ± 162K 27.0G … 27.0G 0 ( 0%) ⚡- 97.3% ± 0.0%
cache_references 107M ± 1.19M 107M … 109M 0 ( 0%) ⚡- 93.2% ± 2.9%
cache_misses 5.21M ± 91.3K 5.12M … 5.30M 0 ( 0%) ⚡- 88.7% ± 16.9%
branch_misses 10.5M ± 11.1K 10.5M … 10.5M 0 ( 0%) ⚡- 92.5% ± 0.1% At this point we are more or less IO bound. |
Self-explanatory, index into string with the length of potentially different string.
I should have mentioned that the configuration options in the benchmarking program are meant to be used with a Release Build ( Anyway, the removal of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you again for reviving #881, your work is greatly appreciated!
Organize Imports Diagnostic and Code Action
This PR adds a diagnostic and a code action to organize imports in the current Zig file. The code action is available in the command palette as
organize @import
.It is designed to be fairly customizable, so that we can arrive at a acceptable sorting style, like different grouping or case sensitivity.
All decisions, especially those around diagnostics are very much open to critique as I do not have experience with the LSP.
Closes #1637, continues work from #881.
Design
An import is a top level declaration of a variable that uses the
@import
builtin.A top level declaration that uses import variables (like
const print = std.debug.print;
) is not considered an import and stays in place.The sorting splits imports into three separated groups: builtin packages, packages and project files.
The builtin packages are sorted in the order:
std
,builtin
,build_options
.The sorting inside other groups is done by comparing the declaration names (the
x
inconst x = @import("file.zig");
).The feature is tested both in unit tests and in neovim. The unit tests required a new helper function.
Diagnostic
The diagnostic is enabled under the
warn_style
zls config option, which is disabled by default.The diagnostic's range is the entire line of the import which would be moved by organizing imports.
That means one diagnostic per wrongly placed import line.
The diagnostic does not consider the correctness of separators between imports.
Code Action
The code action is available based on the diagnostic.
This means that users with the
warn_style
option disabled will not be offered the code action.The code action always sorts. The previous PR had optimization using
isSorted
to avoid sorting when it was not necessary.This, however, did not work well in some corner cases.
The documentation comments (
///
) are moved with the import they are attached to.It also respects
Top-Level Doc Comments
(//!
), which must appear before any expression. This is handled using the commments token location.This feature required running
generateCodeAction
for each diagnostic provided in request.Previously, it ran the function only on diagnostics from
getAstCheckDiagnostics
.Considerations and notes
zig fmt
andtextDocument/formatting
does not interfere with the sorting.@import(variable);
(we don't do comptime)builder.range
field which no longer exists). If it was relevant, I can study it further.warn_style
as seen in tests. It is just that user cannot call it without the diagnostic.std.sort.sort
does not exist anymore. I pickedstd.sort.heap
as a replacement. My understanding is that we do not need a stable sort here, but feel free to correct me and suggest a different sort from thestd
library.Unresolved questions
const print = std.debug.print;
to be sorted as well in some way?@import("root")
? (Currently a package, which does not seem right)getSortSlice
function ported from previous PR seems weird. I would just doreturn self.name
and not look in the import path. Any reasoning behind this?DiagnosticKind
could be parsed from the diagnostic code, which would be cleaner and would easily allow more versions of the diagnostic message. Or use thedata
field of the diagnostic message.@embedFile
?Ast.zig
, for example, imports extra stuff near end of the file for tests.