I'm trying to implement a toggle comment feature in QScintilla that works with multiple selection. Unfortunately I don't know very well how to do it, so far I've come up with this code:
import sys
import re
import math
from PyQt5.Qt import *  # noqa
from PyQt5.Qsci import QsciScintilla
from PyQt5 import Qsci
from PyQt5.Qsci import QsciLexerCPP
class Commenter():
    def __init__(self, sci, comment_str):
        self.sci = sci
        self.comment_str = comment_str
    def is_commented_line(self, line):
        return line.strip().startswith(self.comment_str)
    def toggle_comment_block(self):
        sci = self.sci
        line, index = sci.getCursorPosition()
        if sci.hasSelectedText() and self.is_commented_line(sci.text(sci.getSelection()[0])):
            self.uncomment_line_or_selection()
        elif not self.is_commented_line(sci.text(line)):
            self.comment_line_or_selection()
        else:
            start_line = line
            while start_line > 0 and self.is_commented_line(sci.text(start_line - 1)):
                start_line -= 1
            end_line = line
            lines = sci.lines()
            while end_line < lines and self.is_commented_line(sci.text(end_line + 1)):
                end_line += 1
            sci.setSelection(start_line, 0, end_line, sci.lineLength(end_line))
            self.uncomment_line_or_selection()
            sci.setCursorPosition(line, index - len(self.comment_str))
    def comment_line_or_selection(self):
        sci = self.sci
        if sci.hasSelectedText():
            self.comment_selection()
        else:
            self.comment_line()
    def uncomment_line_or_selection(self):
        sci = self.sci
        if sci.hasSelectedText():
            self.uncomment_selection()
        else:
            self.uncomment_line()
    def comment_line(self):
        sci = self.sci
        line, index = sci.getCursorPosition()
        sci.beginUndoAction()
        sci.insertAt(self.comment_str, line, sci.indentation(line))
        sci.endUndoAction()
    def uncomment_line(self):
        sci = self.sci
        line, index = sci.getCursorPosition()
        if not self.is_commented_line(sci.text(line)):
            return
        sci.beginUndoAction()
        sci.setSelection(
            line, sci.indentation(line),
            line, sci.indentation(line) + len(self.comment_str)
        )
        sci.removeSelectedText()
        sci.endUndoAction()
    def comment_selection(self):
        sci = self.sci
        if not sci.hasSelectedText():
            return
        line_from, index_from, line_to, index_to = sci.getSelection()
        if index_to == 0:
            end_line = line_to - 1
        else:
            end_line = line_to
        sci.beginUndoAction()
        for line in range(line_from, end_line + 1):
            sci.insertAt(self.comment_str, line, sci.indentation(line))
        sci.setSelection(line_from, 0, end_line + 1, 0)
        sci.endUndoAction()
    def uncomment_selection(self):
        sci = self.sci
        if not sci.hasSelectedText():
            return
        line_from, index_from, line_to, index_to = sci.getSelection()
        if index_to == 0:
            end_line = line_to - 1
        else:
            end_line = line_to
        sci.beginUndoAction()
        for line in range(line_from, end_line + 1):
            if not self.is_commented_line(sci.text(line)):
                continue
            sci.setSelection(
                line, sci.indentation(line),
                line,
                sci.indentation(line) + len(self.comment_str)
            )
            sci.removeSelectedText()
            if line == line_from:
                index_from -= len(self.comment_str)
            if index_from < 0:
                index_from = 0
            if line == line_to:
                index_to -= len(self.comment_str)
            if index_to < 0:
                index_to = 0
        sci.setSelection(line_from, index_from, line_to, index_to)
        sci.endUndoAction()
class Foo(QsciScintilla):
    def __init__(self, parent=None):
        super().__init__(parent)
        # http://www.scintilla.org/ScintillaDoc.html#Folding
        self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
        # Indentation
        self.setIndentationsUseTabs(False)
        self.setIndentationWidth(4)
        self.setBackspaceUnindents(True)
        self.setIndentationGuides(True)
        # Set the default font
        self.font = QFont()
        self.font.setFamily('Consolas')
        self.font.setFixedPitch(True)
        self.font.setPointSize(10)
        self.setFont(self.font)
        self.setMarginsFont(self.font)
        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(self.font)
        self.setMarginsFont(self.font)
        self.setMarginWidth(0, fontmetrics.width("000") + 6)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QColor("#cccccc"))
        # Indentation
        self.setIndentationsUseTabs(False)
        self.setIndentationWidth(4)
        self.setBackspaceUnindents(True)
        lexer = QsciLexerCPP()
        lexer.setFoldAtElse(True)
        lexer.setFoldComments(True)
        lexer.setFoldCompact(False)
        lexer.setFoldPreprocessor(True)
        self.setLexer(lexer)
        # Use raw messages to Scintilla here
        # (all messages are documented here: http://www.scintilla.org/ScintillaDoc.html)
        # Ensure the width of the currently visible lines can be scrolled
        self.SendScintilla(QsciScintilla.SCI_SETSCROLLWIDTHTRACKING, 1)
        # Multiple cursor support
        self.SendScintilla(QsciScintilla.SCI_SETMULTIPLESELECTION, True)
        self.SendScintilla(QsciScintilla.SCI_SETMULTIPASTE, 1)
        self.SendScintilla(
            QsciScintilla.SCI_SETADDITIONALSELECTIONTYPING, True)
        # Comment feature goes here
        self.commenter = Commenter(self, "//")
        QShortcut(QKeySequence("Ctrl+7"), self,
                  self.commenter.toggle_comment_block)
def main():
    app = QApplication(sys.argv)
    ex = Foo()
    ex.setText("""\
#include <iostream>
using namespace std;
void Function0() {
    cout << "Function0";
}
void Function1() {
    cout << "Function1";
}
void Function2() {
    cout << "Function2";
}
void Function3() {
    cout << "Function3";
}
int main(void) {
    if (1) {
        if (1) {
            if (1) {
                if (1) {
                    int yay;
                }
            }
        }
    }
    if (1) {
        if (1) {
            if (1) {
                if (1) {
                    int yay2;
                }
            }
        }
    }
    return 0;
}\
    """)
    ex.resize(800, 600)
    ex.show()
    sys.exit(app.exec_())
if __name__ == "__main__":
    main()
Relevant Qscintilla docs live here:
Right now the feature just support one single selection/cursor and the way is commenting is really ugly. As you can see in the code, if you press ctrl while pressing the mouse you'll be able to create multiple cursors/selections already.
There are few things I don't know how to achieve right now though:
1) I'd like the comments to become well-aligned, that is, they should start at the same level of indentation. The existing feature right now produces ugly unaligned comments, example of what I call "well-aligned" comments:
2) Right now only one cursor/selection is being considered. How do I loop over the cursors/selections to apply a toggle_selection function?
3) I guess if you loop over the selections the result would be than having an even number of cursors in a particular line won't comment the line (comment, uncomment), for instance, something like this:
4) An odd number of cursors in a particular line would affect the line because (comment, uncomment, comment), for instance, something like this:
5) If you loop over the cursors/selections you'll end up producing output like the below one.
EDIT: 1st draft
class Commenter():
    def __init__(self, sci, comment_str):
        self.sci = sci
        self.comment_str = comment_str
    def selections(self):
        regions = []
        for i in range(self.sci.SendScintilla(QsciScintilla.SCI_GETSELECTIONS)):
            regions.append({
                'begin': self.selection_start(i),
                'end': self.selection_end(i)
            })
        return regions
    def selection_start(self, selection):
        return self.sci.SendScintilla(QsciScintilla.SCI_GETSELECTIONNSTART, selection)
    def selection_end(self, selection):
        return self.sci.SendScintilla(QsciScintilla.SCI_GETSELECTIONNEND, selection)
    def text(self, *args):
        return self.sci.text(*args)
    def run(self):
        send_scintilla = self.sci.SendScintilla
        for region in self.selections():
            print(region)
            print(repr(self.text(region['begin'],region['end'])))
EDIT2: I've discovered the source code of this feature I'm trying to implement is available on SublimeText Default.sublime-package (zip file), comments.py. That code supports not only normal comments // but also block comments /* ... */. Main problem is porting that code to QScintilla seems to be quite tricky :/





