Skip to content

Commit 07d84e1

Browse files
committed
fix qcodeeditor sym link to be actual file
1 parent b3f93e5 commit 07d84e1

File tree

1 file changed

+379
-1
lines changed

1 file changed

+379
-1
lines changed

QCodeEditor.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

QCodeEditor.py

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
#!/usr/bin/env python2
2+
# -*- coding: utf-8 -*-
3+
'''
4+
Licensed under the terms of the MIT License
5+
https://github.com/luchko/QCodeEditor
6+
@author: Ivan Luchko ([email protected])
7+
8+
Python Highlighting added by:
9+
https://github.com/unihernandez22/QCodeEditor
10+
@author: unihernandez22
11+
12+
Adapted to Binary Ninja by:
13+
@author: Jordan Wiens (https://github.com/psifertex)
14+
15+
Integrating syntax highlighting from:
16+
https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting
17+
Released under the Modified BSD License: http://directory.fsf.org/wiki/License:BSD_3Clause
18+
19+
Note that this will not be merged back to the parent repositories as it's been
20+
modified to be heavily dependent on the BN theme system.
21+
'''
22+
23+
from PySide2.QtCore import Qt, QRect, QRegExp
24+
from PySide2.QtWidgets import QWidget, QTextEdit, QPlainTextEdit
25+
from PySide2.QtGui import (QPainter, QFont, QSyntaxHighlighter, QTextFormat, QTextCharFormat)
26+
from binaryninjaui import (getMonospaceFont, getThemeColor, ThemeColor)
27+
28+
29+
def format(color, style=''):
30+
"""Return a QTextCharFormat with the given attributes."""
31+
_color = eval('getThemeColor(ThemeColor.%s)' % color)
32+
33+
_format = QTextCharFormat()
34+
_format.setForeground(_color)
35+
if 'bold' in style:
36+
_format.setFontWeight(QFont.Bold)
37+
if 'italic' in style:
38+
_format.setFontItalic(True)
39+
40+
return _format
41+
42+
STYLES = {
43+
'keyword': format('StackVariableColor'),
44+
'operator': format('TokenHighlightColor'),
45+
'brace': format('LinearDisassemblySeparatorColor'),
46+
'defclass': format('DataSymbolColor'),
47+
'string': format('StringColor'),
48+
'string2': format('TypeNameColor'),
49+
'comment': format('AnnotationColor', 'italic'),
50+
'self': format('KeywordColor', 'italic'),
51+
'numbers': format('NumberColor'),
52+
'numberbar': getThemeColor(ThemeColor.BackgroundHighlightDarkColor),
53+
'blockselected': getThemeColor(ThemeColor.TokenHighlightColor),
54+
'blocknormal': getThemeColor(ThemeColor.TokenSelectionColor)
55+
}
56+
57+
class PythonHighlighter (QSyntaxHighlighter):
58+
"""Syntax highlighter for the Python language.
59+
"""
60+
# Python keywords
61+
keywords = [
62+
'and', 'assert', 'break', 'class', 'continue', 'def',
63+
'del', 'elif', 'else', 'except', 'exec', 'finally',
64+
'for', 'from', 'global', 'if', 'import', 'in',
65+
'is', 'lambda', 'not', 'or', 'pass', 'print',
66+
'raise', 'return', 'try', 'while', 'yield',
67+
'None', 'True', 'False',
68+
]
69+
70+
# Python operators
71+
operators = [
72+
'=',
73+
# Comparison
74+
'==', '!=', '<', '<=', '>', '>=',
75+
# Arithmetic
76+
'\+', '-', '\*', '/', '//', '\%', '\*\*',
77+
# In-place
78+
'\+=', '-=', '\*=', '/=', '\%=',
79+
# Bitwise
80+
'\^', '\|', '\&', '\~', '>>', '<<',
81+
]
82+
83+
# Python braces
84+
braces = [
85+
'\{', '\}', '\(', '\)', '\[', '\]',
86+
]
87+
def __init__(self, document):
88+
QSyntaxHighlighter.__init__(self, document)
89+
90+
# Multi-line strings (expression, flag, style)
91+
# FIXME: The triple-quotes in these two lines will mess up the
92+
# syntax highlighting from this point onward
93+
self.tri_single = (QRegExp("'''"), 1, STYLES['string2'])
94+
self.tri_double = (QRegExp('"""'), 2, STYLES['string2'])
95+
96+
rules = []
97+
98+
# Keyword, operator, and brace rules
99+
rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
100+
for w in PythonHighlighter.keywords]
101+
rules += [(r'%s' % o, 0, STYLES['operator'])
102+
for o in PythonHighlighter.operators]
103+
rules += [(r'%s' % b, 0, STYLES['brace'])
104+
for b in PythonHighlighter.braces]
105+
106+
# All other rules
107+
rules += [
108+
# 'self'
109+
(r'\bself\b', 0, STYLES['self']),
110+
111+
# Double-quoted string, possibly containing escape sequences
112+
(r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
113+
# Single-quoted string, possibly containing escape sequences
114+
(r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
115+
116+
# 'def' followed by an identifier
117+
(r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
118+
# 'class' followed by an identifier
119+
(r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
120+
121+
# From '#' until a newline
122+
(r'#[^\n]*', 0, STYLES['comment']),
123+
124+
# Numeric literals
125+
(r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
126+
(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
127+
(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']),
128+
]
129+
130+
# Build a QRegExp for each pattern
131+
self.rules = [(QRegExp(pat), index, fmt)
132+
for (pat, index, fmt) in rules]
133+
134+
135+
def highlightBlock(self, text):
136+
"""Apply syntax highlighting to the given block of text.
137+
"""
138+
# Do other syntax formatting
139+
for expression, nth, format in self.rules:
140+
index = expression.indexIn(text, 0)
141+
142+
while index >= 0:
143+
# We actually want the index of the nth match
144+
index = expression.pos(nth)
145+
length = len(expression.cap(nth))
146+
self.setFormat(index, length, format)
147+
index = expression.indexIn(text, index + length)
148+
149+
self.setCurrentBlockState(0)
150+
151+
# Do multi-line strings
152+
in_multiline = self.match_multiline(text, *self.tri_single)
153+
if not in_multiline:
154+
in_multiline = self.match_multiline(text, *self.tri_double)
155+
156+
157+
def match_multiline(self, text, delimiter, in_state, style):
158+
"""Do highlighting of multi-line strings. ``delimiter`` should be a
159+
``QRegExp`` for triple-single-quotes or triple-double-quotes, and
160+
``in_state`` should be a unique integer to represent the corresponding
161+
state changes when inside those strings. Returns True if we're still
162+
inside a multi-line string when this function is finished.
163+
"""
164+
# If inside triple-single quotes, start at 0
165+
if self.previousBlockState() == in_state:
166+
start = 0
167+
add = 0
168+
# Otherwise, look for the delimiter on this line
169+
else:
170+
start = delimiter.indexIn(text)
171+
# Move past this match
172+
add = delimiter.matchedLength()
173+
174+
# As long as there's a delimiter match on this line...
175+
while start >= 0:
176+
# Look for the ending delimiter
177+
end = delimiter.indexIn(text, start + add)
178+
# Ending delimiter on this line?
179+
if end >= add:
180+
length = end - start + add + delimiter.matchedLength()
181+
self.setCurrentBlockState(0)
182+
# No; multi-line string
183+
else:
184+
self.setCurrentBlockState(in_state)
185+
length = len(text) - start + add
186+
# Apply formatting
187+
self.setFormat(start, length, style)
188+
# Look for the next match
189+
start = delimiter.indexIn(text, start + length)
190+
191+
# Return True if still inside a multi-line string, False otherwise
192+
if self.currentBlockState() == in_state:
193+
return True
194+
else:
195+
return False
196+
197+
198+
class QCodeEditor(QPlainTextEdit):
199+
'''
200+
QCodeEditor inherited from QPlainTextEdit providing:
201+
202+
numberBar - set by DISPLAY_LINE_NUMBERS flag equals True
203+
curent line highligthing - set by HIGHLIGHT_CURRENT_LINE flag equals True
204+
setting up QSyntaxHighlighter
205+
206+
references:
207+
https://john.nachtimwald.com/2009/08/19/better-qplaintextedit-with-line-numbers/
208+
http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html
209+
210+
'''
211+
class NumberBar(QWidget):
212+
'''class that deifnes textEditor numberBar'''
213+
214+
def __init__(self, editor):
215+
QWidget.__init__(self, editor)
216+
217+
self.editor = editor
218+
self.editor.blockCountChanged.connect(self.updateWidth)
219+
self.editor.updateRequest.connect(self.updateContents)
220+
self.font = QFont()
221+
self.numberBarColor = STYLES["numberbar"]
222+
223+
def paintEvent(self, event):
224+
225+
painter = QPainter(self)
226+
painter.fillRect(event.rect(), self.numberBarColor)
227+
228+
block = self.editor.firstVisibleBlock()
229+
230+
# Iterate over all visible text blocks in the document.
231+
while block.isValid():
232+
blockNumber = block.blockNumber()
233+
block_top = self.editor.blockBoundingGeometry(block).translated(self.editor.contentOffset()).top()
234+
235+
# Check if the position of the block is out side of the visible area.
236+
if not block.isVisible() or block_top >= event.rect().bottom():
237+
break
238+
239+
# We want the line number for the selected line to be bold.
240+
if blockNumber == self.editor.textCursor().blockNumber():
241+
self.font.setBold(True)
242+
painter.setPen(STYLES["blockselected"])
243+
else:
244+
self.font.setBold(False)
245+
painter.setPen(STYLES["blocknormal"])
246+
painter.setFont(self.font)
247+
248+
# Draw the line number right justified at the position of the line.
249+
paint_rect = QRect(0, block_top, self.width(), self.editor.fontMetrics().height())
250+
painter.drawText(paint_rect, Qt.AlignLeft, str(blockNumber+1))
251+
252+
block = block.next()
253+
254+
painter.end()
255+
256+
QWidget.paintEvent(self, event)
257+
258+
def getWidth(self):
259+
count = self.editor.blockCount()
260+
width = self.fontMetrics().width(str(count)) + 10
261+
return width
262+
263+
def updateWidth(self):
264+
width = self.getWidth()
265+
if self.width() != width:
266+
self.setFixedWidth(width)
267+
self.editor.setViewportMargins(width, 0, 0, 0);
268+
269+
def updateContents(self, rect, scroll):
270+
if scroll:
271+
self.scroll(0, scroll)
272+
else:
273+
self.update(0, rect.y(), self.width(), rect.height())
274+
275+
if rect.contains(self.editor.viewport().rect()):
276+
fontSize = self.editor.currentCharFormat().font().pointSize()
277+
self.font.setPointSize(fontSize)
278+
self.font.setStyle(QFont.StyleNormal)
279+
self.updateWidth()
280+
281+
282+
def __init__(self, DISPLAY_LINE_NUMBERS=True, HIGHLIGHT_CURRENT_LINE=True,
283+
SyntaxHighlighter=None, *args):
284+
'''
285+
Parameters
286+
----------
287+
DISPLAY_LINE_NUMBERS : bool
288+
switch on/off the presence of the lines number bar
289+
HIGHLIGHT_CURRENT_LINE : bool
290+
switch on/off the current line highliting
291+
SyntaxHighlighter : QSyntaxHighlighter
292+
should be inherited from QSyntaxHighlighter
293+
294+
'''
295+
super(QCodeEditor, self).__init__()
296+
297+
self.setFont(QFont("Ubuntu Mono", 11))
298+
self.setLineWrapMode(QPlainTextEdit.NoWrap)
299+
300+
self.DISPLAY_LINE_NUMBERS = DISPLAY_LINE_NUMBERS
301+
302+
if DISPLAY_LINE_NUMBERS:
303+
self.number_bar = self.NumberBar(self)
304+
305+
if HIGHLIGHT_CURRENT_LINE:
306+
self.currentLineNumber = None
307+
self.currentLineColor = STYLES['currentLine']
308+
self.cursorPositionChanged.connect(self.highligtCurrentLine)
309+
310+
if SyntaxHighlighter is not None: # add highlighter to textdocument
311+
self.highlighter = SyntaxHighlighter(self.document())
312+
313+
def resizeEvent(self, *e):
314+
'''overload resizeEvent handler'''
315+
316+
if self.DISPLAY_LINE_NUMBERS: # resize number_bar widget
317+
cr = self.contentsRect()
318+
rec = QRect(cr.left(), cr.top(), self.number_bar.getWidth(), cr.height())
319+
self.number_bar.setGeometry(rec)
320+
321+
QPlainTextEdit.resizeEvent(self, *e)
322+
323+
def highligtCurrentLine(self):
324+
newCurrentLineNumber = self.textCursor().blockNumber()
325+
if newCurrentLineNumber != self.currentLineNumber:
326+
self.currentLineNumber = newCurrentLineNumber
327+
hi_selection = QTextEdit.ExtraSelection()
328+
hi_selection.format.setBackground(self.currentLineColor)
329+
hi_selection.format.setProperty(QTextFormat.FullWidthSelection, True)
330+
hi_selection.cursor = self.textCursor()
331+
hi_selection.cursor.clearSelection()
332+
self.setExtraSelections([hi_selection])
333+
334+
##############################################################################
335+
336+
if __name__ == '__main__':
337+
338+
# TESTING
339+
340+
def run_test():
341+
342+
from PySide2.QtGui import QApplication
343+
import sys
344+
345+
app = QApplication([])
346+
347+
editor = QCodeEditor(DISPLAY_LINE_NUMBERS=True,
348+
HIGHLIGHT_CURRENT_LINE=True,
349+
SyntaxHighlighter=PythonHighlighter)
350+
351+
# text = '''<FINITELATTICE>
352+
# <LATTICE name="myLattice">
353+
# <BASIS>
354+
# <VECTOR>1.0 0.0 0.0</VECTOR>
355+
# <VECTOR>0.0 1.0 0.0</VECTOR>
356+
# </BASIS>
357+
# </LATTICE>
358+
# <PARAMETER name="L" />
359+
# <PARAMETER default="L" name="W" />
360+
# <EXTENT dimension="1" size="L" />
361+
# <EXTENT dimension="2" size="W" />
362+
# <BOUNDARY type="periodic" />
363+
# </FINITELATTICE>
364+
# '''
365+
text = """\
366+
def hello(text):
367+
print(text)
368+
369+
hello('Hello World')
370+
371+
# Comment"""
372+
editor.setPlainText(text)
373+
editor.resize(400,250)
374+
editor.show()
375+
376+
sys.exit(app.exec_())
377+
378+
379+
run_test()

0 commit comments

Comments
 (0)