godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Add "Declutter Script" shortcut #1035

Closed Error7Studios closed 2 years ago

Error7Studios commented 4 years ago

Describe the project you are working on: (N/A) (Applies to any project)

Describe the problem or limitation you are having in your project: When I have a section of code that I want to change, I often: 1) Copy the section of code 2) Comment it out 3) Paste it below the original commented-out version 4) Test changes until stable 5) Delete the old commented-out code

However, after doing this a lot, I end up with a lot of commented lines which I need to remove. Previously, I had been doing this by: 1) Trimming the trailing whitespace from the script I want to clean 2) Copy/pasting it into Notepad++ 3) Removing all lines beginning with '#' 4) Pasting the cleaned up code back into my project.

This does the job, but it's tedious, and actually dangerous because it removes ALL comments, even those you may wish to keep for documentation. It also deletes multiline string lines that begin with '#'

Describe the feature / enhancement and how it helps to overcome the problem or limitation: I created a simple script which handles all of this for you. It also has these additional features: 1) Replaces consecutive blank lines with X number of blank lines (0, 1, 2, etc.) 2) Allows for defining custom symbols to indicate commented lines which should be preserved (#//, etc.) 3) Preserves custom multi-line comments (#/ ... #/) 4) Preserves comments inside strings (single or multi-line strings) 5) Preserves spaced comments by default("# ")

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams: (code provided below) Currently, this code works by dragging and dropping the script to be cleaned onto an export variable of the tool node containing this script.

The cleaned-up code is copied to the OS clipboard, and the user then simply replaces the old code with the new code (Ctrl+A, Ctrl+V)

If this enhancement will not be used often, can it be worked around with a few lines of script?: I think people would use this a lot. I certainly do.

Is there a reason why this should be core and not an add-on in the asset library?: This could help speed up people's workflow, and also automatically reduces the number of blank lines, which makes it easier to see more of the code on the screen at once.

If this was a built-in shortcut in the script editor, it would be even quicker/easier to do than using a tool node to handle this.

Note: While I put in checks to make this code as safe as I could, it is not without risk. The user should be responsible for backing up their code before using this tool. Being built into the editor should allow undo functionality if they made a mistake.

Working Example: (Before) Unclean Code

(After, with max_blank_linesset to 1) Clean Code

Code:

tool
extends Node

const comment_spaced := "# "
const comment_slashed := "#//"
const comment_warning := "# warning-ignore"

const permanent_comment_symbols := [comment_spaced, comment_slashed, comment_warning]
const multiline_string_symbol := "\"\"\""

export(bool) var keep_spaced_comment := true setget set_keep_spaced_comment
func set_keep_spaced_comment(__bool: bool) -> void:
    keep_spaced_comment = __bool

export(int) var max_blank_lines := 1 setget set_max_blank_lines
func set_max_blank_lines(__line_count: int) -> void:
# warning-ignore:narrowing_conversion
    max_blank_lines = max(__line_count, 0)

export(Script) var script_to_clean = null setget set_script_to_clean
func set_script_to_clean(__script: Script) -> void:
    if !__script:
        return

    var __source_code: String = __script.source_code
    var __source_lines: Array = __source_code.split("\n")

    var __source_code_trimmed := ""
    var __consecutive_blank_lines := 0

    var __is_comment := false

    var __in_permanent_comment := false
    var __in_multiline_comment := false
    var __on_multiline_comment_last_line := false

    var __in_multiline_string := false

    for __i in __source_lines.size():
        var __line: String = __source_lines[__i]

        var __r_stripped_line: String = __line.strip_edges(false, true)
        var __stripped_line: String = __line.strip_edges(true, true)

        if !__in_multiline_string:
            __is_comment = __stripped_line.begins_with("#")
        else:
            __is_comment = false

        if __stripped_line.begins_with("#/*"):
            if !__in_multiline_string:
                __in_multiline_comment = true

        if __stripped_line.begins_with("#*/"):
            if !__in_multiline_string:
                if !__in_multiline_comment:
                    var __err_msg := str("\n\t", "Multiline comment symbol error:",
                                        "\n\t\t", "End symbol (*/) found at line: ", __i + 1, ".",
                                        "\n\t\t", "No previous start symbol (/*) found.",
                                        "\n\t\t", "Can't copy source code until resolved."
                                        )

                    print(__err_msg)
                    push_warning(__err_msg)

                __on_multiline_comment_last_line = true

        var __permanent_comment_symbols := permanent_comment_symbols.duplicate(true)
        if !keep_spaced_comment:
            __permanent_comment_symbols.erase(comment_spaced)

        if !__in_multiline_string:
            for __symbol in __permanent_comment_symbols:
                if __stripped_line.begins_with(__symbol):
                    __in_permanent_comment = true

        if multiline_string_symbol in __r_stripped_line:
            var __multiline_string_symbol_count := __r_stripped_line.count(multiline_string_symbol)
            var __is_even: bool = __multiline_string_symbol_count % 2 == 0

            if !__is_comment:
                if __in_multiline_string:
                    __in_multiline_string = __is_even
                else:
                    __in_multiline_string = !__is_even

        if __in_permanent_comment \
        or __in_multiline_comment \
        or !__is_comment:

            if !__stripped_line.empty():

                __consecutive_blank_lines = 0
                __source_code_trimmed += __r_stripped_line + "\n"
            else:
                __consecutive_blank_lines += 1

                if __consecutive_blank_lines <= max_blank_lines:
                    __source_code_trimmed += "\n"

            if __on_multiline_comment_last_line:
                __in_multiline_comment = false
                __on_multiline_comment_last_line = false

            __in_permanent_comment = false

    while __source_code_trimmed.ends_with("\n"):
        __source_code_trimmed = __source_code_trimmed.trim_suffix("\n")

    OS.clipboard = __source_code_trimmed
    var __success_msg := \
            str("\n", "Copied source code to clipboard.",
                "\n\t", "'script.resource_path': ", __script.resource_path,
                "\n\t", "'keep_spaced_comment': ", keep_spaced_comment,
                "\n\t", "'max_blank_lines': ", max_blank_lines
                )

    self.property_list_changed_notify()
    print(__success_msg)
Jummit commented 4 years ago

Would regex support in the search-and-replace tool work too? That would be a more general-purpose solution than a magic button that does everything.

Error7Studios commented 4 years ago

If you accidentally replace '#' instead of '# ', '#//', etc., you just deleted all forms of comments. And you may not notice it until it's too late to undo.

Also, I don't want to do (Replace: \n\n With: \n) 10 times to remove big sections of blank space.

It may work, but I think the regex to make sure you're not deleting something inside a multiline string would be pretty complex.

Should the user be expected to know how to type advanced regex to handle that case? It would also delete strings that start with '#' unless they check for that with the regex.

Doesn't seem worth it to just use regex.

Calinou commented 4 years ago

When I have a section of code that I want to change, I often:

  • Copy the section of code
  • Comment it out
  • Paste it below the original commented-out version
  • Test changes until stable
  • Delete the old commented-out code

You may want to look into using version control instead. This way, you can display staged/unstaged changes and pick stuff from old revisions if you need it.

(You can use Git without pushing your project to any remote service.)

regakakobigman commented 4 years ago

I don't know how I feel about this. The difference between # and # is very small; most projects contain both styles due to small typos that aren't worth fixing. This proposal would then make these small typos into extremely important syntax that controls where a comment will be deleted or not.

I would prefer if it was really, really hard to opt-in. Something like ### would still mistakenly delete people's stylized comment blocks. Maybe, instead of deleting lines based on a specific string, you could use a tool in the script editor to explicitly mark what will and won't be deleted.

Still, I don't think I would use this that much. I don't think the added complexity will be worth the time saved clicking and dragging to delete sections of code. I also think it's too prone to mistakes right now, and that a delete-tool based solution would be too complicated.

Error7Studios commented 4 years ago

You may want to look into using version control instead

I could use version control for this, but I like having the commented line(s) right there for direct comparison, instead of just changing it and then checking the diff later if I need to revert.

I also think it's too prone to mistakes right now

What about adding a secondary comment symbol and shortcut?

For example, pressing Control+Shift+Alt+K could toggle the line as a temporary comment, prefixed by some character combination the user would never accidentally type, like #!|.

Then, there would just be a single option to remove only the temporary comments, so that all other comments would be safe.

Calinou commented 4 years ago

@Error7Studios I don't know of any editor/IDE that has a "temporary comment" feature built-in. I'm kind of wary about implementing features that have no prior art in other IDEs, as it will most likely only be used sparsely by people.

willnationsdev commented 3 years ago

993 is a related proposal on how to add doc-comments to GDScript, to auto-generate documentation from comments.

If it does come to pass, from that conversation, that doc-comments end up with a distinct syntax compared to regular comments (for example, as was suggested in https://github.com/godotengine/godot-proposals/issues/993#issuecomment-645097039), then it would be fairly simple to have a quick utility to auto-remove all comments which are not doc-comments, since the language would already be distinguishing between which is which.

Calinou commented 2 years ago

Closing due to lack of support (see the above comments and reactions on OP).