|
9 | 9 | from datetime import datetime
|
10 | 10 | from pathlib import Path
|
11 | 11 |
|
12 |
| -import binaryninjaui |
13 |
| -from binaryninjaui import (getMonospaceFont, UIAction, UIActionHandler, Menu, UIContext) |
14 |
| -from PySide6.QtWidgets import (QLineEdit, QPushButton, QApplication, QWidget, |
15 |
| - QVBoxLayout, QHBoxLayout, QDialog, QFileSystemModel, QTreeView, QLabel, QSplitter, |
16 |
| - QInputDialog, QMessageBox, QHeaderView, QKeySequenceEdit, QCheckBox) |
17 |
| -from PySide6.QtCore import (QDir, Qt, QFileInfo, QItemSelectionModel, QSettings, QUrl) |
18 |
| -from PySide6.QtGui import (QFontMetrics, QDesktopServices, QKeySequence, QIcon, QColor) |
19 | 12 | from binaryninja import user_plugin_path, core_version
|
20 | 13 | from binaryninja.plugin import BackgroundTaskThread
|
21 | 14 | from binaryninja.log import (log_error, log_debug, log_alert, log_warn)
|
22 | 15 | from binaryninja.settings import Settings
|
23 | 16 | from binaryninja.interaction import get_directory_name_input
|
24 |
| -import numbers |
| 17 | +import binaryninjaui |
| 18 | +from binaryninjaui import (getMonospaceFont, UIAction, UIActionHandler, Menu, UIContext) |
| 19 | +from PySide6.QtWidgets import (QLineEdit, QPushButton, QApplication, QWidget, |
| 20 | + QVBoxLayout, QHBoxLayout, QDialog, QFileSystemModel, QTreeView, QLabel, QSplitter, |
| 21 | + QInputDialog, QMessageBox, QHeaderView, QKeySequenceEdit, QCheckBox, QMenu) |
| 22 | +from PySide6.QtCore import (QDir, Qt, QFileInfo, QItemSelectionModel, QSettings, QUrl) |
| 23 | +from PySide6.QtGui import (QFontMetrics, QDesktopServices, QKeySequence, QIcon, QColor, QAction) |
25 | 24 | from .QCodeEditor import QCodeEditor, Pylighter
|
26 | 25 |
|
27 | 26 | Settings().register_group("snippets", "Snippets")
|
28 | 27 | Settings().register_setting("snippets.syntaxHighlight", """
|
29 | 28 | {
|
30 |
| - "title" : "Syntax highlighting for snippets", |
| 29 | + "title" : "Syntax Highlighting", |
31 | 30 | "type" : "boolean",
|
32 | 31 | "default" : true,
|
33 | 32 | "description" : "Whether to syntax highlight (may be performance problems with very large snippets and the current highlighting implementation.)",
|
|
36 | 35 | """)
|
37 | 36 | Settings().register_setting("snippets.indentation", """
|
38 | 37 | {
|
39 |
| - "title" : "Indentation Syntax highlighting for snippets", |
| 38 | + "title" : "Indentation Syntax Highlighting", |
40 | 39 | "type" : "string",
|
41 | 40 | "default" : " ",
|
42 | 41 | "description" : "String to use for indentation in snippets (tip: to use a tab, copy/paste a tab from another text field and paste here)",
|
@@ -129,7 +128,7 @@ def setupGlobals(context):
|
129 | 128 | snippetGlobals['current_basic_block'] = None
|
130 | 129 | snippetGlobals['current_address'] = context.address
|
131 | 130 | snippetGlobals['here'] = context.address
|
132 |
| - if context.address is not None and isinstance(context.length, numbers.Integral): |
| 131 | + if context.address is not None and isinstance(context.length, int): |
133 | 132 | snippetGlobals['current_selection'] = (context.address, context.address+context.length)
|
134 | 133 | else:
|
135 | 134 | snippetGlobals['current_selection'] = None
|
@@ -249,6 +248,8 @@ def __init__(self, context, parent=None):
|
249 | 248 | self.tree = QTreeView()
|
250 | 249 | self.tree.setModel(self.files)
|
251 | 250 | self.tree.setSortingEnabled(True)
|
| 251 | + self.tree.setContextMenuPolicy(Qt.CustomContextMenu) |
| 252 | + self.tree.customContextMenuRequested.connect(self.contextMenu) |
252 | 253 | self.tree.hideColumn(2)
|
253 | 254 | self.tree.sortByColumn(0, Qt.AscendingOrder)
|
254 | 255 | self.tree.setRootIndex(self.files.index(snippetPath))
|
@@ -496,6 +497,30 @@ def deleteSnippet(self):
|
496 | 497 | self.files.remove(selection)
|
497 | 498 | self.registerAllSnippets()
|
498 | 499 |
|
| 500 | + def duplicateSnippet(self): |
| 501 | + selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row |
| 502 | + snippetName = self.files.fileName(selection) |
| 503 | + (newname, ok) = QInputDialog.getText(self, self.tr("New Snippet Name"), self.tr("New Snippet Name:")) |
| 504 | + if ok and snippetName: |
| 505 | + (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) |
| 506 | + if not snippetName.endswith(".py"): |
| 507 | + snippetName += ".py" |
| 508 | + index = self.tree.selectionModel().currentIndex() |
| 509 | + selection = self.files.filePath(index) |
| 510 | + if QFileInfo(selection).isDir(): |
| 511 | + path = os.path.join(selection, snippetName) |
| 512 | + else: |
| 513 | + path = os.path.join(snippetPath, snippetName) |
| 514 | + self.readOnly(False) |
| 515 | + open(path, "w").close() |
| 516 | + self.snippetName.setText(os.path.basename(self.currentFile)) |
| 517 | + self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("") |
| 518 | + self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence("")) |
| 519 | + self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("") |
| 520 | + self.save() |
| 521 | + self.tree.setCurrentIndex(self.files.index(path)) |
| 522 | + self.registerAllSnippets() |
| 523 | + |
499 | 524 | def snippetChanged(self):
|
500 | 525 | if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()):
|
501 | 526 | return False
|
@@ -691,6 +716,14 @@ def main(bv):
|
691 | 716 | def clearHotkey(self):
|
692 | 717 | self.keySequenceEdit.clear()
|
693 | 718 |
|
| 719 | + def contextMenu(self, position): |
| 720 | + menu = QMenu() |
| 721 | + delete = menu.addAction("Delete") |
| 722 | + delete.triggered.connect(self.deleteSnippet) |
| 723 | + duplicate = menu.addAction("Duplicate") |
| 724 | + duplicate.triggered.connect(self.duplicateSnippet) |
| 725 | + menu.exec_(self.mapToGlobal(position)) |
| 726 | + |
694 | 727 |
|
695 | 728 | snippets = None
|
696 | 729 |
|
|
0 commit comments