我正在尝试在 QScintilla 中实现一个切换评论功能,该功能适用于多项选择。不幸的是,我不太清楚该怎么做,到目前为止我已经想出了这个代码:
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()
相关的 Qscintilla 文档住在这里:
现在这个功能只支持一个单一的选择/光标,评论的方式真的很难看。正如您在代码中看到的,如果您在按住鼠标的同时按 ctrl,您将能够创建多个光标/选择。
不过,我现在不知道如何实现几件事:
1)我希望评论对齐,也就是说,它们应该从相同的缩进级别开始。现有功能现在会产生丑陋的未对齐评论,我称之为“对齐良好”的评论示例:
2) 现在只考虑一个光标/选择。如何循环游标/选择以应用 toggle_selection 功能?
3)我猜如果你循环选择结果会比在特定行中有偶数个游标不会评论该行(评论,取消评论),例如,像这样:
4) 特定行中的奇数个游标会影响该行,因为(注释、取消注释、注释),例如,如下所示:
5)如果你循环游标/选择,你最终会产生像下面这样的输出。
编辑:第一稿
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:我发现我正在尝试实现的此功能的源代码可在 SublimeText Default.sublime-package (zip 文件)、comments.py上找到。该代码不仅支持普通注释//
,还支持块注释/* ... */
。主要问题是将代码移植到 QScintilla 似乎非常棘手:/