Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Mar 3, 2023. It is now read-only.

Conversation

@Ingramz
Copy link
Contributor

@Ingramz Ingramz commented Nov 10, 2019

Description of the Change

Text borrowed from #15096, which I had to close due to a force-push.

While working on atom/snippets#239, I realized that there isn't a properly defined behavior for selecting a segment of text on single line and indenting/outdenting it:

indent_undefined_behavior

Here is why I don't think this has been defined yet:

  1. There is no spec for single-line selected text being indented via indent():

    describe ".indent()", ->
    describe "when the selection is empty", ->
    describe "when autoIndent is disabled", ->
    describe "if 'softTabs' is true (the default)", ->
    it "inserts 'tabLength' spaces into the buffer", ->
    tabRegex = new RegExp("^[ ]{#{editor.getTabLength()}}")
    expect(buffer.lineForRow(0)).not.toMatch(tabRegex)
    editor.indent()
    expect(buffer.lineForRow(0)).toMatch(tabRegex)
    it "respects the tab stops when cursor is in the middle of a tab", ->
    editor.setTabLength(4)
    buffer.insert([12, 2], "\n ")
    editor.setCursorBufferPosition [13, 1]
    editor.indent()
    expect(buffer.lineForRow(13)).toMatch /^\s+$/
    expect(buffer.lineForRow(13).length).toBe 4
    expect(editor.getCursorBufferPosition()).toEqual [13, 4]
    buffer.insert([13, 0], " ")
    editor.setCursorBufferPosition [13, 6]
    editor.indent()
    expect(buffer.lineForRow(13).length).toBe 8
    describe "if 'softTabs' is false", ->
    it "insert a \t into the buffer", ->
    editor.setSoftTabs(false)
    expect(buffer.lineForRow(0)).not.toMatch(/^\t/)
    editor.indent()
    expect(buffer.lineForRow(0)).toMatch(/^\t/)
    describe "when autoIndent is enabled", ->
    describe "when the cursor's column is less than the suggested level of indentation", ->
    describe "when 'softTabs' is true (the default)", ->
    it "moves the cursor to the end of the leading whitespace and inserts enough whitespace to bring the line to the suggested level of indentaion", ->
    buffer.insert([5, 0], " \n")
    editor.setCursorBufferPosition [5, 0]
    editor.indent(autoIndent: true)
    expect(buffer.lineForRow(5)).toMatch /^\s+$/
    expect(buffer.lineForRow(5).length).toBe 6
    expect(editor.getCursorBufferPosition()).toEqual [5, 6]
    it "respects the tab stops when cursor is in the middle of a tab", ->
    editor.setTabLength(4)
    buffer.insert([12, 2], "\n ")
    editor.setCursorBufferPosition [13, 1]
    editor.indent(autoIndent: true)
    expect(buffer.lineForRow(13)).toMatch /^\s+$/
    expect(buffer.lineForRow(13).length).toBe 4
    expect(editor.getCursorBufferPosition()).toEqual [13, 4]
    buffer.insert([13, 0], " ")
    editor.setCursorBufferPosition [13, 6]
    editor.indent(autoIndent: true)
    expect(buffer.lineForRow(13).length).toBe 8
    describe "when 'softTabs' is false", ->
    it "moves the cursor to the end of the leading whitespace and inserts enough tabs to bring the line to the suggested level of indentaion", ->
    convertToHardTabs(buffer)
    editor.setSoftTabs(false)
    buffer.insert([5, 0], "\t\n")
    editor.setCursorBufferPosition [5, 0]
    editor.indent(autoIndent: true)
    expect(buffer.lineForRow(5)).toMatch /^\t\t\t$/
    expect(editor.getCursorBufferPosition()).toEqual [5, 3]
    describe "when the difference between the suggested level of indentation and the current level of indentation is greater than 0 but less than 1", ->
    it "inserts one tab", ->
    editor.setSoftTabs(false)
    buffer.setText(" \ntest")
    editor.setCursorBufferPosition [1, 0]
    editor.indent(autoIndent: true)
    expect(buffer.lineForRow(1)).toBe '\ttest'
    expect(editor.getCursorBufferPosition()).toEqual [1, 1]
    describe "when the line's indent level is greater than the suggested level of indentation", ->
    describe "when 'softTabs' is true (the default)", ->
    it "moves the cursor to the end of the leading whitespace and inserts 'tabLength' spaces into the buffer", ->
    buffer.insert([7, 0], " \n")
    editor.setCursorBufferPosition [7, 2]
    editor.indent(autoIndent: true)
    expect(buffer.lineForRow(7)).toMatch /^\s+$/
    expect(buffer.lineForRow(7).length).toBe 8
    expect(editor.getCursorBufferPosition()).toEqual [7, 8]
    describe "when 'softTabs' is false", ->
    it "moves the cursor to the end of the leading whitespace and inserts \t into the buffer", ->
    convertToHardTabs(buffer)
    editor.setSoftTabs(false)
    buffer.insert([7, 0], "\t\t\t\n")
    editor.setCursorBufferPosition [7, 1]
    editor.indent(autoIndent: true)
    expect(buffer.lineForRow(7)).toMatch /^\t\t\t\t$/
    expect(editor.getCursorBufferPosition()).toEqual [7, 4]
    describe "when the selection is not empty", ->
    it "indents the selected lines", ->
    editor.setSelectedBufferRange([[0, 0], [10, 0]])
    selection = editor.getLastSelection()
    spyOn(selection, "indentSelectedRows")
    editor.indent()
    expect(selection.indentSelectedRows).toHaveBeenCalled()
    describe "if editor.softTabs is false", ->
    it "inserts a tab character into the buffer", ->
    editor.setSoftTabs(false)
    expect(buffer.lineForRow(0)).not.toMatch(/^\t/)
    editor.indent()
    expect(buffer.lineForRow(0)).toMatch(/^\t/)
    expect(editor.getCursorBufferPosition()).toEqual [0, 1]
    expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength()]
    editor.indent()
    expect(buffer.lineForRow(0)).toMatch(/^\t\t/)
    expect(editor.getCursorBufferPosition()).toEqual [0, 2]
    expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength() * 2]

  2. indentSelectedRows (which is used in indent()) is poorly documented:

    atom/src/selection.coffee

    Lines 671 to 679 in a7736b8

    else
    @indentSelectedRows()
    # Public: If the selection spans multiple rows, indent all of them.
    indentSelectedRows: ->
    [start, end] = @getBufferRowRange()
    for row in [start..end]
    @editor.buffer.insert([row, 0], @editor.getTabText()) unless @editor.buffer.lineLengthForRow(row) is 0
    return

    If the selection spans multiple rows, indent all of them.

    It's possible to interpret this in a way that if the selection does not span across multiple rows, then nothing gets indented. Insert your favorite programmer-goes-to-grocery-store-joke here.

  3. The current behavior is not intuitive and unexpected for anyone coming from a different editor. With the exception of Brackets, other text editors tend to agree that selecting text and pressing tab within a single line should not indent the whole line, but insert/overwrite with a tab.

Outdent is done through outdentSelectedRows, which has a spec for single line selected text. However it seems to be slightly mislabeled, there is actually less than one line selected. That is not a big deal and can be easily fixed, but it's possible that something is missing from here too.

At minimum I am looking to define behavior for following cases:

  1. Selecting whole line and issuing indent().
  2. Selecting substring within line with length greater than 0 and issuing indent().
  3. Selecting whole line and issuing outdentSelectedRows().
  4. Selecting substring within line with length greater than 0 and issuing outdentSelectedRows().

There might be additional cases that might need be considered, but I don't want this to be come overly ambitious, so starting with a more managable goal might be better.

The motivation for all of this comes from behavior related to text selections that come from traversing tab stops within snippets, where indenting/outdenting may lead to unexpected results:

indent_snippet1
indent_snippet2

Personally I think that outdent behavior can be kept as-is and it is sufficient to simply improve the spec and cover cases 3 and 4. Outdenting does not break out of snippet's tab stops whereas indenting does, so indicating change with a slightly more destructive (but predictable) operation should be obvious enough that it's impossible to return to snippet's tab stops.

Visual Studio Code's behavior is a suitable candidate, which indents if the whole line is selected, but inserts a tab when only part of line is selected. Outdent is handled in the same way as Atom does it now. If there aren't any objections or better ideas, I might just implement that and propose this as the final solution.

Code at the time of writing in pull request is just to get the ball rolling and does not represent the final proposed solution in any way. But it does replace everything with a tab if at most only one line has been selected.

Currently the code implements Visual Studio Code behavior where fully selected single line indents and otherwise inserts a tab. Outdent remains the same as it is now.

Alternate Designs

Sublime text's behavior was considered, which replaces the line with a tab if the start and end cursor is on the same line. This however can be at times more inconvenient than the Visual Studio Code behavior of indenting the line instead.

Possible Drawbacks

Change in behavior that might upset users who are already used to the less flexible behavior in place right now.

Verification Process

Both hand-testing and automated testing of use cases that were not previously covered (selecting right amount of text and pressing Tab.

Release Notes

Not finalized.

@Ingramz
Copy link
Contributor Author

Ingramz commented Nov 10, 2019

@xjArea I recreated the pull request and brought the code up-to-date.

To answer your question, as far as I know this is the best way of solving it - we propose a change and see if maintainers are fine with it.

@xjArea
Copy link

xjArea commented Nov 11, 2019

@Ingramz Thank you very much for your reply. I'm sorry that it didn't pass the Atom test ? But I found that the only error is the MAC environment, and I use the Window system, so I don't care about this error😊 .
If I want to use your improved code, how can I put them into my Atom ? I searched related files and found that they didn't exist in my folder(version by atom-x64-windows.zip
). I don't know the structure of the software. I guess it needs to be built before it can be used, but I don't know these. So, what can I do ? Just sit and waiting Atom Group solve it 😂?

@Ingramz
Copy link
Contributor Author

Ingramz commented Nov 11, 2019

The build failure comes from the fact that the test in snippets package expects current atom behavior. That test is simply too tightly coupled to the core, which is fine for now. I'll see what I can do about it.

Unfortunately windows build artifacts did not get uploaded, which is why I cannot tell you to use those. Building Atom on your own system is not impossible, but can be difficult if you haven't done it before.

@xjArea
Copy link

xjArea commented Nov 11, 2019

OK, although I didn't solve the problem, I am still very grateful for everything you have done. I will continue to use Atom and look forward to the day when this problem can be solved 🙏.

@darangi darangi force-pushed the single-line-selected-text-indent branch from a11140e to 5ae3d67 Compare August 24, 2021 16:39
@darangi
Copy link
Contributor

darangi commented Sep 3, 2021

@Ingramz do you mind taking a look at the failing tests? I believe these changes caused the failure, The CI is not just being flaky.

@sadick254
Copy link
Contributor

Thank you for your PR!

We haven't gotten a response to our failing tests request. I'm going to close this but don't hesitate to reach out if you have or find time to fix the tests, we'll be happy to reopen the PR.

@sadick254 sadick254 closed this Sep 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants