Toggle comments both ways


#1

If I select a block of text, some lines of which are commented out and some not, Cmd-/ adds a level of comment to the entire block. Is there a way to truly toggle the “commented status” of each line? What I mean is that I would like the block

# This line is commented out
This line is not
# This one is
# This one is too

to become

This line is commented out
# This line is not
This one is
This one is too

Thanks.`


#2

You could write a command to break the selection apart into lines and then run editor.toggleLineComments() on each of them.


#3

Thanks. Sorry if the question is really basic, but could you point me in the right direction? How does one loop over lines of selected text?


#4

Try this:

atom.commands.add 'atom-text-editor', 'custom:toggle-comments-by-line', ->
  return unless editor = atom.workspace.getActiveTextEditor()

  rows = []
  selections = editor.getSelections()
  for selection in selections
    range = selection.getBufferRowRange()
    selection.clear()
    row = range[0]
    while (row <= range[1])
      cursor = editor.addCursorAtBufferPosition([row, 0])
      cursor.selection.toggleLineComments()

      row++

#5

Thanks for your reply. I was writing up my solution when yours appeared.

I pieced this together, and it seems to do just what I was looking for:

atom.commands.add 'atom-text-editor', 'commentary', ->
  return unless editor = atom.workspace.getActiveTextEditor()
  selection = editor.getLastSelection()
  editor.toggleLineCommentsForBufferRows(row, row) for row in selection.getBufferRange().getRows()

We can add a keymap:

'atom-workspace':
  'alt-cmd-/': 'commentary'

The name commentary comes from a Vim package of the same name.


#6

I like the fact that your proposed solution works with multiple selections. One thing I noticed is that if a line is selected twice (e.g., because you’re selecting the word ‘atom’ and it appears twice on the same line), the toggle is applied twice, so that that line remains as it was. That’s probably not the intended behavior. So I came up with the following, which is incredibly succinct:

# https://coffeescript-cookbook.github.io/chapters/arrays/removing-duplicate-elements-from-arrays
Array::unique = ->
 output = {}
 output[@[key]] = @[key] for key in [0...@length]
 value for key, value of output

atom.commands.add 'atom-text-editor', 'custom:commentary', ->
 return unless editor = atom.workspace.getActiveTextEditor()
 rows = [].concat (selection.getBufferRange().getRows() for selection in editor.getSelections())...
 editor.toggleLineCommentsForBufferRows(row, row) for row in rows.unique()

#7

Darn. I forgot about that.

I am impressed by the concision of your CoffeeScript.


#8

For the record, I discovered that extending Array as above can have side effects in packages. For good measure, we can also skip blank lines.Thus I’m now using the variant:

onlyUnique = (value, index, self) -> self.indexOf(value) == index
isBlank = (line) -> line.match /^\S*$/

atom.commands.add 'atom-text-editor', 'custom:commentary', ->
  return unless editor = atom.workspace.getActiveTextEditor()
  rows = [].concat (selection.getBufferRange().getRows() for selection in editor.getSelections())...
  editor.toggleLineCommentsForBufferRows(row, row) for row in rows.filter(onlyUnique) when !isBlank(editor.lineTextForBufferRow(row))

Join Line Above