|
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