Skip to content

Commit 99d6cd1

Browse files
committed
Ship snippets as a core plugin
1 parent 75f1a80 commit 99d6cd1

File tree

351 files changed

+126586
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

351 files changed

+126586
-0
lines changed

plugins/snippets/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.pyc
2+
__pycache__/
3+
.vscode

plugins/snippets/LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright (c) 2019 Vector 35 Inc
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

plugins/snippets/QCodeEditor.py

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
'''
4+
Licensed under the terms of the MIT License
5+
6+
Re-written for Snippet Editor by Jordan Wiens (https://github.com/psifertex/)
7+
With some original components (line numbers) based on:
8+
9+
https://github.com/luchko/QCodeEditor
10+
@author: Ivan Luchko ([email protected])
11+
'''
12+
13+
import binaryninjaui
14+
from binaryninja import log_warn, bncompleter
15+
if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6:
16+
from PySide6.QtCore import Qt, QRect
17+
from PySide6.QtWidgets import QWidget, QPlainTextEdit
18+
from PySide6.QtGui import (QPainter, QFont, QSyntaxHighlighter, QTextCharFormat, QTextCursor)
19+
else:
20+
from PySide2.QtCore import Qt, QRect
21+
from PySide2.QtWidgets import QWidget, QPlainTextEdit
22+
from PySide2.QtGui import (QPainter, QFont, QSyntaxHighlighter, QTextCharFormat, QTextCursor)
23+
from binaryninjaui import (getMonospaceFont, getThemeColor, ThemeColor)
24+
try:
25+
from pygments import highlight
26+
from pygments.lexers import *
27+
from pygments.formatter import Formatter
28+
29+
class QFormatter(Formatter):
30+
31+
def __init__(self):
32+
Formatter.__init__(self)
33+
self.pygstyles={}
34+
for token, style in self.style:
35+
tokenname = str(token)
36+
if tokenname in bnstyles.keys():
37+
self.pygstyles[str(token)]=bnstyles[tokenname]
38+
#log_warn("MATCH: %s with %s" % (tokenname, str(token)))
39+
else:
40+
self.pygstyles[str(token)]=bnstyles['Token.Name']
41+
#log_warn("NONE: %s with %s" % (tokenname, str(token)))
42+
43+
def format(self, tokensource, outfile):
44+
self.data=[]
45+
for token, value in tokensource:
46+
self.data.extend([self.pygstyles[str(token)],]*len(value))
47+
48+
class Pylighter(QSyntaxHighlighter):
49+
50+
def __init__(self, parent, lang):
51+
QSyntaxHighlighter.__init__(self, parent)
52+
self.formatter=QFormatter()
53+
self.lexer=get_lexer_by_name(lang)
54+
55+
def highlightBlock(self, text):
56+
cb = self.currentBlock()
57+
p = cb.position()
58+
text=self.document().toPlainText()+' \n'
59+
highlight(text,self.lexer,self.formatter)
60+
61+
#dirty, dirty hack
62+
for i in range(len(text)):
63+
try:
64+
self.setFormat(i,1,self.formatter.data[p+i])
65+
except IndexError:
66+
pass
67+
68+
except:
69+
log_warn("Pygments not installed, no syntax highlighting enabled.")
70+
Pylighter=None
71+
72+
73+
def bnformat(color, style=''):
74+
"""Return a QTextCharFormat with the given attributes."""
75+
color = eval('getThemeColor(ThemeColor.%s)' % color)
76+
77+
format = QTextCharFormat()
78+
format.setForeground(color)
79+
if 'bold' in style:
80+
format.setFontWeight(QFont.Bold)
81+
if 'italic' in style:
82+
format.setFontItalic(True)
83+
84+
return format
85+
86+
# Most of these aren't needed but after fighting pygments for so long I figure they can't hurt.
87+
bnstyles = {
88+
'Token.Literal.Number': bnformat('NumberColor'),
89+
'Token.Literal.Number.Bin': bnformat('NumberColor'),
90+
'Token.Literal.Number.Float': bnformat('NumberColor'),
91+
'Token.Literal.Number.Integer': bnformat('NumberColor'),
92+
'Token.Literal.Number.Integer.Long': bnformat('NumberColor'),
93+
'Token.Literal.Number.Hex': bnformat('NumberColor'),
94+
'Token.Literal.Number.Oct': bnformat('NumberColor'),
95+
96+
'Token.Literal.String': bnformat('StringColor'),
97+
'Token.Literal.String.Single': bnformat('StringColor'),
98+
'Token.Literal.String.Char': bnformat('StringColor'),
99+
'Token.Literal.String.Backtick': bnformat('StringColor'),
100+
'Token.Literal.String.Delimiter': bnformat('StringColor'),
101+
'Token.Literal.String.Double': bnformat('StringColor'),
102+
'Token.Literal.String.Heredoc': bnformat('StringColor'),
103+
'Token.Literal.String.Affix': bnformat('StringColor'),
104+
'Token.String': bnformat('StringColor'),
105+
106+
'Token.Comment': bnformat('CommentColor', 'italic'),
107+
'Token.Comment.Hashbang': bnformat('CommentColor', 'italic'),
108+
'Token.Comment.Single': bnformat('CommentColor', 'italic'),
109+
'Token.Comment.Special': bnformat('CommentColor', 'italic'),
110+
'Token.Comment.PreprocFile': bnformat('CommentColor', 'italic'),
111+
'Token.Comment.Multiline': bnformat('CommentColor', 'italic'),
112+
113+
'Token.Keyword': bnformat('StackVariableColor'),
114+
115+
'Token.Operator': bnformat('TokenHighlightColor'),
116+
'Token.Punctuation': bnformat('UncertainColor'),
117+
118+
#This is the most important and hardest to get right. No way to get theme palettes!
119+
'Token.Name': bnformat('OutlineColor'),
120+
121+
'Token.Name.Namespace': bnformat('OutlineColor'),
122+
123+
'Token.Name.Variable': bnformat('DataSymbolColor'),
124+
'Token.Name.Class': bnformat('DataSymbolColor'),
125+
'Token.Name.Constant': bnformat('DataSymbolColor'),
126+
'Token.Name.Entity': bnformat('DataSymbolColor'),
127+
'Token.Name.Other': bnformat('DataSymbolColor'),
128+
'Token.Name.Tag': bnformat('DataSymbolColor'),
129+
'Token.Name.Decorator': bnformat('DataSymbolColor'),
130+
'Token.Name.Label': bnformat('DataSymbolColor'),
131+
'Token.Name.Variable.Magic': bnformat('DataSymbolColor'),
132+
'Token.Name.Variable.Instance': bnformat('DataSymbolColor'),
133+
'Token.Name.Variable.Class': bnformat('DataSymbolColor'),
134+
'Token.Name.Variable.Global': bnformat('DataSymbolColor'),
135+
'Token.Name.Property': bnformat('DataSymbolColor'),
136+
'Token.Name.Function': bnformat('DataSymbolColor'),
137+
'Token.Name.Builtin': bnformat('ImportColor'),
138+
'Token.Name.Builtin.Pseudo': bnformat('ImportColor'),
139+
140+
'Token.Escape': bnformat('ImportColor'),
141+
142+
'Token.Keyword': bnformat('GotoLabelColor'),
143+
'Token.Operator.Word': bnformat('GotoLabelColor'),
144+
145+
'numberBar': getThemeColor(ThemeColor.BackgroundHighlightDarkColor),
146+
'blockSelected': getThemeColor(ThemeColor.TokenHighlightColor),
147+
'blockNormal': getThemeColor(ThemeColor.TokenSelectionColor),
148+
}
149+
150+
151+
class QCodeEditor(QPlainTextEdit):
152+
class NumberBar(QWidget):
153+
154+
def __init__(self, editor):
155+
QWidget.__init__(self, editor)
156+
global bnstyles
157+
158+
self.editor = editor
159+
self.editor.blockCountChanged.connect(self.updateWidth)
160+
self.editor.updateRequest.connect(self.updateContents)
161+
self.font = editor.currentCharFormat().font()
162+
self.numberBarColor = bnstyles["numberBar"]
163+
self.updateWidth()
164+
165+
def paintEvent(self, event):
166+
painter = QPainter(self)
167+
painter.fillRect(event.rect(), self.numberBarColor)
168+
169+
block = self.editor.firstVisibleBlock()
170+
171+
# Iterate over all visible text blocks in the document.
172+
while block.isValid():
173+
blockNumber = block.blockNumber()
174+
block_top = self.editor.blockBoundingGeometry(block).translated(self.editor.contentOffset()).top()
175+
176+
# Check if the position of the block is out side of the visible area.
177+
if not block.isVisible() or block_top >= event.rect().bottom():
178+
break
179+
180+
# We want the line number for the selected line to be bold.
181+
if blockNumber == self.editor.textCursor().blockNumber():
182+
self.font.setBold(True)
183+
painter.setPen(bnstyles["blockSelected"])
184+
else:
185+
self.font.setBold(False)
186+
painter.setPen(bnstyles["blockNormal"])
187+
painter.setFont(self.font)
188+
189+
# Draw the line number left justified at the position of the line.
190+
paint_rect = QRect(0, block_top, self.width(), self.editor.fontMetrics().height())
191+
painter.drawText(paint_rect, Qt.AlignLeft, str(blockNumber + 3)) # Offset so that the lines are correct to the file
192+
193+
block = block.next()
194+
195+
painter.end()
196+
197+
QWidget.paintEvent(self, event)
198+
199+
def getWidth(self):
200+
count = self.editor.blockCount()
201+
width = self.fontMetrics().horizontalAdvance(str(count)) + 10
202+
return width
203+
204+
def updateWidth(self):
205+
width = self.getWidth()
206+
if self.width() != width:
207+
self.setFixedWidth(width)
208+
self.editor.setViewportMargins(width, 0, 0, 0)
209+
210+
def updateContents(self, rect, scroll):
211+
if scroll:
212+
self.scroll(0, scroll)
213+
else:
214+
self.update(0, rect.y(), self.width(), rect.height())
215+
216+
if rect.contains(self.editor.viewport().rect()):
217+
self.updateWidth()
218+
219+
220+
def __init__(self, DISPLAY_LINE_NUMBERS=True, HIGHLIGHT_CURRENT_LINE=True,
221+
SyntaxHighlighter=Pylighter, lang="python", font_size=11, delimeter=" ", *args):
222+
super(QCodeEditor, self).__init__()
223+
224+
font = getMonospaceFont(self)
225+
font.setPointSize(font_size)
226+
self.setFont(font)
227+
self.setLineWrapMode(QPlainTextEdit.NoWrap)
228+
self.completionState = 0
229+
self.completing = False
230+
self.delimeter = delimeter
231+
self.completer = bncompleter.Completer()
232+
self.cursorPositionChanged.connect(self.resetCompletion)
233+
234+
self.DISPLAY_LINE_NUMBERS = DISPLAY_LINE_NUMBERS
235+
236+
if DISPLAY_LINE_NUMBERS:
237+
self.number_bar = self.NumberBar(self)
238+
239+
if SyntaxHighlighter is not None: # add highlighter to textdocument
240+
self.highlighter = SyntaxHighlighter(self.document(), lang)
241+
242+
def resetCompletion(self):
243+
if not self.completing:
244+
self.completionState = 0
245+
246+
def isStart(self):
247+
tempCursor = self.textCursor()
248+
if tempCursor.positionInBlock() == 0:
249+
return True
250+
startText = tempCursor.block().text()[0:tempCursor.positionInBlock()]
251+
delim = set(self.delimeter)
252+
if set(startText) - delim == set():
253+
#only delimeters before cursor, not worrying about varying lengths of spaces for now
254+
return True
255+
return False
256+
257+
def setDelimeter(self, delimeter):
258+
self.deliemter = delimeter;
259+
260+
def replaceBlockAtCursor(self, newText):
261+
cursor=self.textCursor()
262+
cursor.select(QTextCursor.BlockUnderCursor)
263+
if cursor.selectionStart() != 0:
264+
newText = "\n" + newText
265+
cursor.removeSelectedText()
266+
cursor.insertText(newText)
267+
268+
def keyPressEvent(self, event):
269+
if event.key() == Qt.Key_Backtab and self.textCursor().hasSelection():
270+
startCursor = self.textCursor()
271+
startCursor.beginEditBlock()
272+
startPos = startCursor.selectionStart()
273+
startCursor.setPosition(startPos)
274+
startCursor.movePosition(QTextCursor.StartOfLine, QTextCursor.MoveAnchor)
275+
startCursor.clearSelection()
276+
277+
endCursor = self.textCursor()
278+
endPos = endCursor.selectionEnd()
279+
endCursor.setPosition(endPos)
280+
endCursor.movePosition(QTextCursor.StartOfLine)
281+
282+
while startCursor.anchor() != endCursor.position():
283+
startCursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, len(self.delimeter))
284+
if startCursor.selectedText() == self.delimeter:
285+
startCursor.removeSelectedText()
286+
startCursor.movePosition(QTextCursor.NextBlock, QTextCursor.MoveAnchor)
287+
startCursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, len(self.delimeter))
288+
if startCursor.selectedText() == self.delimeter:
289+
startCursor.removeSelectedText()
290+
startCursor.endEditBlock()
291+
return
292+
293+
if event.key() == Qt.Key_Tab and self.textCursor().hasSelection():
294+
startCursor = self.textCursor()
295+
startCursor.beginEditBlock()
296+
startPos = startCursor.selectionStart()
297+
startCursor.setPosition(startPos)
298+
startCursor.movePosition(QTextCursor.StartOfLine)
299+
300+
endCursor = self.textCursor()
301+
endPos = endCursor.selectionEnd()
302+
endCursor.setPosition(endPos)
303+
endCursor.movePosition(QTextCursor.StartOfLine)
304+
305+
while startCursor.position() != endCursor.position():
306+
startCursor.insertText(self.delimeter)
307+
startCursor.movePosition(QTextCursor.NextBlock)
308+
309+
startCursor.insertText(self.delimeter)
310+
startCursor.endEditBlock()
311+
return
312+
313+
if event.key() == Qt.Key_Escape and self.completionState > 0:
314+
self.completionState = 0
315+
cursor = self.textCursor()
316+
cursor.beginEditBlock()
317+
self.replaceBlockAtCursor(self.origText)
318+
cursor.endEditBlock()
319+
self.origText == None
320+
return
321+
322+
if event.key() == Qt.Key_Tab:
323+
if self.isStart():
324+
self.textCursor().insertText(self.delimeter)
325+
else:
326+
cursor = self.textCursor()
327+
cursor.beginEditBlock()
328+
self.completing = True
329+
if self.completionState == 0:
330+
self.origText = self.textCursor().block().text()
331+
if self.completionState > 0:
332+
self.replaceBlockAtCursor(self.origText)
333+
newText = self.completer.complete(self.origText, self.completionState)
334+
if newText:
335+
if newText.find("(") > 0:
336+
newText = newText[0:newText.find("(") + 1]
337+
self.completionState += 1
338+
self.replaceBlockAtCursor(newText)
339+
else:
340+
self.completionState = 0
341+
self.replaceBlockAtCursor(self.origText)
342+
self.origText == None
343+
cursor.endEditBlock()
344+
self.completing = False
345+
return
346+
347+
return super().keyPressEvent(event)
348+
349+
350+
def resizeEvent(self, *e):
351+
'''overload resizeEvent handler'''
352+
353+
if self.DISPLAY_LINE_NUMBERS: # resize number_bar widget
354+
cr = self.contentsRect()
355+
rec = QRect(cr.left(), cr.top(), self.number_bar.getWidth(), cr.height())
356+
self.number_bar.setGeometry(rec)
357+
358+
QPlainTextEdit.resizeEvent(self, *e)

0 commit comments

Comments
 (0)