From 13ff200ba134b8704f37eca99c42b70dab0d17dc Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 1 Jul 2021 12:34:30 +0800 Subject: Remove snippet and kaitai plugin. Note their individual links. --- python/examples/snippets/LICENSE | 7 - python/examples/snippets/QCodeEditor.py | 379 ------------------------ python/examples/snippets/README.md | 50 ---- python/examples/snippets/__init__.py | 444 ---------------------------- python/examples/snippets/media/snippets.gif | Bin 4105422 -> 0 bytes python/examples/snippets/plugin.json | 1 - 6 files changed, 881 deletions(-) delete mode 100644 python/examples/snippets/LICENSE delete mode 100644 python/examples/snippets/QCodeEditor.py delete mode 100644 python/examples/snippets/README.md delete mode 100644 python/examples/snippets/__init__.py delete mode 100644 python/examples/snippets/media/snippets.gif delete mode 100644 python/examples/snippets/plugin.json (limited to 'python/examples/snippets') diff --git a/python/examples/snippets/LICENSE b/python/examples/snippets/LICENSE deleted file mode 100644 index edd8f6bc..00000000 --- a/python/examples/snippets/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2019 Vector 35 Inc - -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: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -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. \ No newline at end of file diff --git a/python/examples/snippets/QCodeEditor.py b/python/examples/snippets/QCodeEditor.py deleted file mode 100644 index 7b0c0e09..00000000 --- a/python/examples/snippets/QCodeEditor.py +++ /dev/null @@ -1,379 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -''' -Licensed under the terms of the MIT License -https://github.com/luchko/QCodeEditor -@author: Ivan Luchko (luchko.ivan@gmail.com) - -Python Highlighting added by: -https://github.com/unihernandez22/QCodeEditor -@author: unihernandez22 - -Adapted to Binary Ninja by: -@author: Jordan Wiens (https://github.com/psifertex) - -Integrating syntax highlighting from: -https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting -Released under the Modified BSD License: http://directory.fsf.org/wiki/License:BSD_3Clause - -Note that this will not be merged back to the parent repositories as it's been -modified to be heavily dependent on the BN theme system. -''' - -from PySide2.QtCore import Qt, QRect, QRegExp -from PySide2.QtWidgets import QWidget, QTextEdit, QPlainTextEdit -from PySide2.QtGui import (QPainter, QFont, QSyntaxHighlighter, QTextFormat, QTextCharFormat) -from binaryninjaui import (getMonospaceFont, getThemeColor, ThemeColor) - - -def format(color, style=''): - """Return a QTextCharFormat with the given attributes.""" - _color = eval('getThemeColor(ThemeColor.%s)' % color) - - _format = QTextCharFormat() - _format.setForeground(_color) - if 'bold' in style: - _format.setFontWeight(QFont.Bold) - if 'italic' in style: - _format.setFontItalic(True) - - return _format - -STYLES = { - 'keyword': format('StackVariableColor'), - 'operator': format('TokenHighlightColor'), - 'brace': format('LinearDisassemblySeparatorColor'), - 'defclass': format('DataSymbolColor'), - 'string': format('StringColor'), - 'string2': format('TypeNameColor'), - 'comment': format('AnnotationColor', 'italic'), - 'self': format('KeywordColor', 'italic'), - 'numbers': format('NumberColor'), - 'numberbar': getThemeColor(ThemeColor.BackgroundHighlightDarkColor), - 'blockselected': getThemeColor(ThemeColor.TokenHighlightColor), - 'blocknormal': getThemeColor(ThemeColor.TokenSelectionColor) -} - -class PythonHighlighter (QSyntaxHighlighter): - """Syntax highlighter for the Python language. - """ - # Python keywords - keywords = [ - 'and', 'assert', 'break', 'class', 'continue', 'def', - 'del', 'elif', 'else', 'except', 'exec', 'finally', - 'for', 'from', 'global', 'if', 'import', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', - 'raise', 'return', 'try', 'while', 'yield', - 'None', 'True', 'False', - ] - - # Python operators - operators = [ - '=', - # Comparison - '==', '!=', '<', '<=', '>', '>=', - # Arithmetic - '\+', '-', '\*', '/', '//', '\%', '\*\*', - # In-place - '\+=', '-=', '\*=', '/=', '\%=', - # Bitwise - '\^', '\|', '\&', '\~', '>>', '<<', - ] - - # Python braces - braces = [ - '\{', '\}', '\(', '\)', '\[', '\]', - ] - def __init__(self, document): - QSyntaxHighlighter.__init__(self, document) - - # Multi-line strings (expression, flag, style) - # FIXME: The triple-quotes in these two lines will mess up the - # syntax highlighting from this point onward - self.tri_single = (QRegExp("'''"), 1, STYLES['string2']) - self.tri_double = (QRegExp('"""'), 2, STYLES['string2']) - - rules = [] - - # Keyword, operator, and brace rules - rules += [(r'\b%s\b' % w, 0, STYLES['keyword']) - for w in PythonHighlighter.keywords] - rules += [(r'%s' % o, 0, STYLES['operator']) - for o in PythonHighlighter.operators] - rules += [(r'%s' % b, 0, STYLES['brace']) - for b in PythonHighlighter.braces] - - # All other rules - rules += [ - # 'self' - (r'\bself\b', 0, STYLES['self']), - - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']), - - # 'def' followed by an identifier - (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']), - # 'class' followed by an identifier - (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']), - - # From '#' until a newline - (r'#[^\n]*', 0, STYLES['comment']), - - # Numeric literals - (r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']), - (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']), - (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']), - ] - - # Build a QRegExp for each pattern - self.rules = [(QRegExp(pat), index, fmt) - for (pat, index, fmt) in rules] - - - def highlightBlock(self, text): - """Apply syntax highlighting to the given block of text. - """ - # Do other syntax formatting - for expression, nth, format in self.rules: - index = expression.indexIn(text, 0) - - while index >= 0: - # We actually want the index of the nth match - index = expression.pos(nth) - length = len(expression.cap(nth)) - self.setFormat(index, length, format) - index = expression.indexIn(text, index + length) - - self.setCurrentBlockState(0) - - # Do multi-line strings - in_multiline = self.match_multiline(text, *self.tri_single) - if not in_multiline: - in_multiline = self.match_multiline(text, *self.tri_double) - - - def match_multiline(self, text, delimiter, in_state, style): - """Do highlighting of multi-line strings. ``delimiter`` should be a - ``QRegExp`` for triple-single-quotes or triple-double-quotes, and - ``in_state`` should be a unique integer to represent the corresponding - state changes when inside those strings. Returns True if we're still - inside a multi-line string when this function is finished. - """ - # If inside triple-single quotes, start at 0 - if self.previousBlockState() == in_state: - start = 0 - add = 0 - # Otherwise, look for the delimiter on this line - else: - start = delimiter.indexIn(text) - # Move past this match - add = delimiter.matchedLength() - - # As long as there's a delimiter match on this line... - while start >= 0: - # Look for the ending delimiter - end = delimiter.indexIn(text, start + add) - # Ending delimiter on this line? - if end >= add: - length = end - start + add + delimiter.matchedLength() - self.setCurrentBlockState(0) - # No; multi-line string - else: - self.setCurrentBlockState(in_state) - length = len(text) - start + add - # Apply formatting - self.setFormat(start, length, style) - # Look for the next match - start = delimiter.indexIn(text, start + length) - - # Return True if still inside a multi-line string, False otherwise - if self.currentBlockState() == in_state: - return True - else: - return False - - -class QCodeEditor(QPlainTextEdit): - ''' - QCodeEditor inherited from QPlainTextEdit providing: - - numberBar - set by DISPLAY_LINE_NUMBERS flag equals True - curent line highligthing - set by HIGHLIGHT_CURRENT_LINE flag equals True - setting up QSyntaxHighlighter - - references: - https://john.nachtimwald.com/2009/08/19/better-qplaintextedit-with-line-numbers/ - http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html - - ''' - class NumberBar(QWidget): - '''class that deifnes textEditor numberBar''' - - def __init__(self, editor): - QWidget.__init__(self, editor) - - self.editor = editor - self.editor.blockCountChanged.connect(self.updateWidth) - self.editor.updateRequest.connect(self.updateContents) - self.font = QFont() - self.numberBarColor = STYLES["numberbar"] - - def paintEvent(self, event): - - painter = QPainter(self) - painter.fillRect(event.rect(), self.numberBarColor) - - block = self.editor.firstVisibleBlock() - - # Iterate over all visible text blocks in the document. - while block.isValid(): - blockNumber = block.blockNumber() - block_top = self.editor.blockBoundingGeometry(block).translated(self.editor.contentOffset()).top() - - # Check if the position of the block is out side of the visible area. - if not block.isVisible() or block_top >= event.rect().bottom(): - break - - # We want the line number for the selected line to be bold. - if blockNumber == self.editor.textCursor().blockNumber(): - self.font.setBold(True) - painter.setPen(STYLES["blockselected"]) - else: - self.font.setBold(False) - painter.setPen(STYLES["blocknormal"]) - painter.setFont(self.font) - - # Draw the line number right justified at the position of the line. - paint_rect = QRect(0, block_top, self.width(), self.editor.fontMetrics().height()) - painter.drawText(paint_rect, Qt.AlignLeft, str(blockNumber+1)) - - block = block.next() - - painter.end() - - QWidget.paintEvent(self, event) - - def getWidth(self): - count = self.editor.blockCount() - width = self.fontMetrics().width(str(count)) + 10 - return width - - def updateWidth(self): - width = self.getWidth() - if self.width() != width: - self.setFixedWidth(width) - self.editor.setViewportMargins(width, 0, 0, 0); - - def updateContents(self, rect, scroll): - if scroll: - self.scroll(0, scroll) - else: - self.update(0, rect.y(), self.width(), rect.height()) - - if rect.contains(self.editor.viewport().rect()): - fontSize = self.editor.currentCharFormat().font().pointSize() - self.font.setPointSize(fontSize) - self.font.setStyle(QFont.StyleNormal) - self.updateWidth() - - - def __init__(self, DISPLAY_LINE_NUMBERS=True, HIGHLIGHT_CURRENT_LINE=True, - SyntaxHighlighter=None, *args): - ''' - Parameters - ---------- - DISPLAY_LINE_NUMBERS : bool - switch on/off the presence of the lines number bar - HIGHLIGHT_CURRENT_LINE : bool - switch on/off the current line highliting - SyntaxHighlighter : QSyntaxHighlighter - should be inherited from QSyntaxHighlighter - - ''' - super(QCodeEditor, self).__init__() - - self.setFont(QFont("Ubuntu Mono", 11)) - self.setLineWrapMode(QPlainTextEdit.NoWrap) - - self.DISPLAY_LINE_NUMBERS = DISPLAY_LINE_NUMBERS - - if DISPLAY_LINE_NUMBERS: - self.number_bar = self.NumberBar(self) - - if HIGHLIGHT_CURRENT_LINE: - self.currentLineNumber = None - self.currentLineColor = STYLES['currentLine'] - self.cursorPositionChanged.connect(self.highligtCurrentLine) - - if SyntaxHighlighter is not None: # add highlighter to textdocument - self.highlighter = SyntaxHighlighter(self.document()) - - def resizeEvent(self, *e): - '''overload resizeEvent handler''' - - if self.DISPLAY_LINE_NUMBERS: # resize number_bar widget - cr = self.contentsRect() - rec = QRect(cr.left(), cr.top(), self.number_bar.getWidth(), cr.height()) - self.number_bar.setGeometry(rec) - - QPlainTextEdit.resizeEvent(self, *e) - - def highligtCurrentLine(self): - newCurrentLineNumber = self.textCursor().blockNumber() - if newCurrentLineNumber != self.currentLineNumber: - self.currentLineNumber = newCurrentLineNumber - hi_selection = QTextEdit.ExtraSelection() - hi_selection.format.setBackground(self.currentLineColor) - hi_selection.format.setProperty(QTextFormat.FullWidthSelection, True) - hi_selection.cursor = self.textCursor() - hi_selection.cursor.clearSelection() - self.setExtraSelections([hi_selection]) - -############################################################################## - -if __name__ == '__main__': - - # TESTING - - def run_test(): - - from PySide2.QtGui import QApplication - import sys - - app = QApplication([]) - - editor = QCodeEditor(DISPLAY_LINE_NUMBERS=True, - HIGHLIGHT_CURRENT_LINE=True, - SyntaxHighlighter=PythonHighlighter) - -# text = ''' -# -# -# 1.0 0.0 0.0 -# 0.0 1.0 0.0 -# -# -# -# -# -# -# -# -# ''' - text = """\ -def hello(text): - print(text) - -hello('Hello World') - -# Comment""" - editor.setPlainText(text) - editor.resize(400,250) - editor.show() - - sys.exit(app.exec_()) - - - run_test() diff --git a/python/examples/snippets/README.md b/python/examples/snippets/README.md deleted file mode 100644 index 00274c3a..00000000 --- a/python/examples/snippets/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Snippet UI Plugin (v1.5) -Author: **Vector 35 Inc** - -_Powerful code-editing plugin for writing and managing python code-snippets with syntax highlightingd, hotkey binding and other features_ - -## Description: - -The snippet editor started as a simple example UI plugin to demonstrate new features available to UI plugins. It has turned into a functionally useful plugin in its own right. The snippet editor allows you to write small bits of code that might not be big enough to warrant the effort of a full plugin but are longer enough that you don't want to retype them every time in the python-console! - -As an added bonus, all snippets are added to the snippets menu and hot-keys can be associated with them as they make use of the action system. All action-system items are also available through the command-palette (CTL/CMD-p). - -![](https://github.com/Vector35/snippets/blob/master/media/snippets.gif?raw=true) - -. - - -## Installation Instructions - -### Darwin - -no special instructions, package manager is recommended - -### Linux - -no special instructions, package manager is recommended - -### Windows - -no special instructions, package manager is recommended - -## Minimum Version - -This plugin requires the following minimum version of Binary Ninja: - -* 1528 - - - -## Required Dependencies - -The following dependencies are required for this plugin: - - - -## License - -This plugin is released under a MIT license. -## Metadata Version - -2 diff --git a/python/examples/snippets/__init__.py b/python/examples/snippets/__init__.py deleted file mode 100644 index f751f303..00000000 --- a/python/examples/snippets/__init__.py +++ /dev/null @@ -1,444 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import sys -import os -import re -import codecs -from PySide2.QtWidgets import (QLineEdit, QPushButton, QApplication, QTextEdit, QWidget, - QVBoxLayout, QHBoxLayout, QDialog, QFileSystemModel, QTreeView, QLabel, QSplitter, - QInputDialog, QMessageBox, QHeaderView, QMenu, QAction, QKeySequenceEdit, - QPlainTextEdit) -from PySide2.QtCore import (QDir, QObject, Qt, QFileInfo, QItemSelectionModel, QSettings, QUrl) -from PySide2.QtGui import (QFont, QFontMetrics, QDesktopServices, QKeySequence, QIcon) -from binaryninja import user_plugin_path -from binaryninja.plugin import PluginCommand, MainThreadActionHandler -from binaryninja.mainthread import execute_on_main_thread -from binaryninja.log import (log_error, log_debug) -from binaryninjaui import (getMonospaceFont, UIAction, UIActionHandler, Menu, DockHandler, - getThemeColor, ThemeColor) -import numbers -from .QCodeEditor import QCodeEditor, PythonHighlighter - -snippetPath = os.path.realpath(os.path.join(user_plugin_path(), "..", "snippets")) -try: - if not os.path.exists(snippetPath): - os.mkdir(snippetPath) -except IOError: - log_error("Unable to create %s" % snippetPath) - - -def includeWalk(dir, includeExt): - filePaths = [] - for (root, dirs, files) in os.walk(dir): - for f in files: - if os.path.splitext(f)[1] in includeExt: - filePaths.append(os.path.join(root, f)) - return filePaths - - -def loadSnippetFromFile(snippetPath): - try: - snippetText = codecs.open(snippetPath, 'r', "utf-8").readlines() - except: - return ("", "", "") - if (len(snippetText) < 3): - return ("", "", "") - else: - qKeySequence = QKeySequence(snippetText[1].strip()[1:]) - if qKeySequence.isEmpty(): - qKeySequence = None - return (snippetText[0].strip()[1:], - qKeySequence, - ''.join(snippetText[2:]) - ) - - -def actionFromSnippet(snippetName, snippetDescription): - if not snippetDescription: - shortName = os.path.basename(snippetName) - if shortName.endswith('.py'): - shortName = shortName[:-3] - return "Snippets\\" + shortName - else: - return "Snippets\\" + snippetDescription - - -def executeSnippet(code, context): - snippetGlobals = {} - if context.binaryView == None: - dock = DockHandler.getActiveDockHandler() - if not dock: - log_error("Snippet triggered with no context and no dock handler. This should not happen. Please report reproduction steps if possible.") - return - viewFrame = dock.getViewFrame() - if not viewFrame: - log_error("Snippet triggered with no context and no view frame. Snippets require at least one open binary.") - return - viewInterface = viewFrame.getCurrentViewInterface() - context.binaryView = viewInterface.getData() - snippetGlobals['current_view'] = context.binaryView - snippetGlobals['bv'] = context.binaryView - if not context.function: - if not context.lowLevelILFunction: - if not context.mediumLevelILFunction: - snippetGlobals['current_mlil'] = None - snippetGlobals['current_function'] = None - snippetGlobals['current_llil'] = None - else: - snippetGlobals['current_mlil'] = context.mediumLevelILFunction - snippetGlobals['current_function'] = context.mediumLevelILFunction.source_function - snippetGlobals['current_llil'] = context.mediumLevelILFunction.source_function.llil - else: - snippetGlobals['current_llil'] = context.lowLevelILFunction - snippetGlobals['current_function'] = context.lowLevelILFunction.source_function - snippetGlobals['current_mlil'] = context.lowLevelILFunction.source_function.mlil - else: - snippetGlobals['current_function'] = context.function - snippetGlobals['current_mlil'] = context.function.mlil - snippetGlobals['current_llil'] = context.function.llil - snippetGlobals['current_token'] = context.function.llil - - if context.function is not None: - snippetGlobals['current_basic_block'] = context.function.get_basic_block_at(context.address) - else: - snippetGlobals['current_basic_block'] = None - snippetGlobals['current_address'] = context.address - snippetGlobals['here'] = context.address - if context.address is not None and isinstance(context.length, numbers.Integral): - snippetGlobals['current_selection'] = (context.address, context.address+context.length) - else: - snippetGlobals['current_selection'] = None - snippetGlobals['uicontext'] = context - - exec("from binaryninja import *", snippetGlobals) - exec(code, snippetGlobals) - if snippetGlobals['here'] != context.address: - context.binaryView.file.navigate(context.binaryView.file.view, snippetGlobals['here']) - if snippetGlobals['current_address'] != context.address: - context.binaryView.file.navigate(context.binaryView.file.view, snippetGlobals['current_address']) - - -def makeSnippetFunction(code): - return lambda context: executeSnippet(code, context) - -class Snippets(QDialog): - - def __init__(self, context, parent=None): - super(Snippets, self).__init__(parent) - # Create widgets - self.setWindowModality(Qt.ApplicationModal) - self.title = QLabel(self.tr("Snippet Editor")) - self.saveButton = QPushButton(self.tr("&Save")) - self.saveButton.setShortcut(QKeySequence(self.tr("Ctrl+S"))) - self.runButton = QPushButton(self.tr("&Run")) - self.runButton.setShortcut(QKeySequence(self.tr("Ctrl+R"))) - self.closeButton = QPushButton(self.tr("Close")) - self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey")) - self.setWindowTitle(self.title.text()) - #self.newFolderButton = QPushButton("New Folder") - self.browseButton = QPushButton("Browse Snippets") - self.browseButton.setIcon(QIcon.fromTheme("edit-undo")) - self.deleteSnippetButton = QPushButton("Delete") - self.newSnippetButton = QPushButton("New Snippet") - self.edit = QCodeEditor(HIGHLIGHT_CURRENT_LINE=False, SyntaxHighlighter=PythonHighlighter) - self.edit.setPlaceholderText("python code") - self.resetting = False - self.columns = 3 - self.context = context - - self.keySequenceEdit = QKeySequenceEdit(self) - self.currentHotkey = QKeySequence() - self.currentHotkeyLabel = QLabel("") - self.currentFileLabel = QLabel() - self.currentFile = "" - self.snippetDescription = QLineEdit() - self.snippetDescription.setPlaceholderText("optional description") - - #Set Editbox Size - font = getMonospaceFont(self) - self.edit.setFont(font) - font = QFontMetrics(font) - self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API - - #Files - self.files = QFileSystemModel() - self.files.setRootPath(snippetPath) - self.files.setNameFilters(["*.py"]) - - #Tree - self.tree = QTreeView() - self.tree.setModel(self.files) - self.tree.setSortingEnabled(True) - self.tree.hideColumn(2) - self.tree.sortByColumn(0, Qt.AscendingOrder) - self.tree.setRootIndex(self.files.index(snippetPath)) - for x in range(self.columns): - #self.tree.resizeColumnToContents(x) - self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) - treeLayout = QVBoxLayout() - treeLayout.addWidget(self.tree) - treeButtons = QHBoxLayout() - #treeButtons.addWidget(self.newFolderButton) - treeButtons.addWidget(self.browseButton) - treeButtons.addWidget(self.newSnippetButton) - treeButtons.addWidget(self.deleteSnippetButton) - treeLayout.addLayout(treeButtons) - treeWidget = QWidget() - treeWidget.setLayout(treeLayout) - - # Create layout and add widgets - buttons = QHBoxLayout() - buttons.addWidget(self.clearHotkeyButton) - buttons.addWidget(self.keySequenceEdit) - buttons.addWidget(self.currentHotkeyLabel) - buttons.addWidget(self.closeButton) - buttons.addWidget(self.runButton) - buttons.addWidget(self.saveButton) - - description = QHBoxLayout() - description.addWidget(QLabel(self.tr("Description: "))) - description.addWidget(self.snippetDescription) - - vlayoutWidget = QWidget() - vlayout = QVBoxLayout() - vlayout.addLayout(description) - vlayout.addWidget(self.edit) - vlayout.addLayout(buttons) - vlayoutWidget.setLayout(vlayout) - - hsplitter = QSplitter() - hsplitter.addWidget(treeWidget) - hsplitter.addWidget(vlayoutWidget) - - hlayout = QHBoxLayout() - hlayout.addWidget(hsplitter) - - self.showNormal() #Fixes bug that maximized windows are "stuck" - #Because you can't trust QT to do the right thing here - if (sys.platform == "darwin"): - self.settings = QSettings("Vector35", "Snippet Editor") - else: - self.settings = QSettings("Vector 35", "Snippet Editor") - if self.settings.contains("ui/snippeteditor/geometry"): - self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry")) - else: - self.edit.setMinimumWidth(80 * font.averageCharWidth()) - self.edit.setMinimumHeight(30 * font.lineSpacing()) - - # Set dialog layout - self.setLayout(hlayout) - - # Add signals - self.saveButton.clicked.connect(self.save) - self.closeButton.clicked.connect(self.close) - self.runButton.clicked.connect(self.run) - self.clearHotkeyButton.clicked.connect(self.clearHotkey) - self.tree.selectionModel().selectionChanged.connect(self.selectFile) - self.newSnippetButton.clicked.connect(self.newFileDialog) - self.deleteSnippetButton.clicked.connect(self.deleteSnippet) - #self.newFolderButton.clicked.connect(self.newFolder) - self.browseButton.clicked.connect(self.browseSnippets) - - if self.settings.contains("ui/snippeteditor/selected"): - selectedName = self.settings.value("ui/snippeteditor/selected") - self.tree.selectionModel().select(self.files.index(selectedName), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) - if self.tree.selectionModel().hasSelection(): - self.selectFile(self.tree.selectionModel().selection(), None) - self.edit.setFocus() - cursor = self.edit.textCursor() - cursor.setPosition(self.edit.document().characterCount()-1) - self.edit.setTextCursor(cursor) - else: - self.readOnly(True) - else: - self.readOnly(True) - - - @staticmethod - def registerAllSnippets(): - for action in list(filter(lambda x: x.startswith("Snippets\\"), UIAction.getAllRegisteredActions())): - if action == "Snippets\\Snippet Editor...": - continue - UIActionHandler.globalActions().unbindAction(action) - Menu.mainMenu("Tools").removeAction(action) - UIAction.unregisterAction(action) - - for snippet in includeWalk(snippetPath, ".py"): - snippetKeys = None - (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(snippet) - actionText = actionFromSnippet(snippet, snippetDescription) - if snippetCode: - if snippetKeys == None: - UIAction.registerAction(actionText) - else: - UIAction.registerAction(actionText, snippetKeys) - UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode))) - Menu.mainMenu("Tools").addAction(actionText, "Snippets") - - def clearSelection(self): - self.keySequenceEdit.clear() - self.currentHotkey = QKeySequence() - self.currentHotkeyLabel.setText("") - self.currentFileLabel.setText("") - self.snippetDescription.setText("") - self.edit.clear() - self.tree.clearSelection() - self.currentFile = "" - - def reject(self): - self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry()) - - if self.snippetChanged(): - question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?")) - if question != QMessageBox.StandardButton.Yes: - return - self.accept() - - def browseSnippets(self): - url = QUrl.fromLocalFile(snippetPath) - QDesktopServices.openUrl(url); - - def newFolder(self): - (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: ")) - if ok and folderName: - index = self.tree.selectionModel().currentIndex() - selection = self.files.filePath(index) - if QFileInfo(selection).isDir(): - QDir(selection).mkdir(folderName) - else: - QDir(snippetPath).mkdir(folderName) - - def selectFile(self, new, old): - if (self.resetting): - self.resetting = False - return - if len(new.indexes()) == 0: - self.clearSelection() - self.currentFile = "" - self.readOnly(True) - return - newSelection = self.files.filePath(new.indexes()[0]) - self.settings.setValue("ui/snippeteditor/selected", newSelection) - if QFileInfo(newSelection).isDir(): - self.readOnly(True) - self.clearSelection() - self.currentFile = "" - return - - if old and old.length() > 0: - oldSelection = self.files.filePath(old.indexes()[0]) - if not QFileInfo(oldSelection).isDir() and self.snippetChanged(): - question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?")) - if question != QMessageBox.StandardButton.Yes: - self.resetting = True - self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) - return False - - self.currentFile = newSelection - self.loadSnippet() - - def loadSnippet(self): - self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName()) - (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) - self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("") - self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence("")) - self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("") - self.readOnly(False) - - def newFileDialog(self): - (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: ")) - if ok and snippetName: - if not snippetName.endswith(".py"): - snippetName += ".py" - index = self.tree.selectionModel().currentIndex() - selection = self.files.filePath(index) - if QFileInfo(selection).isDir(): - path = os.path.join(selection, snippetName) - else: - path = os.path.join(snippetPath, snippetName) - self.readOnly(False) - open(path, "w").close() - self.tree.setCurrentIndex(self.files.index(path)) - log_debug("Snippet %s created." % snippetName) - - def readOnly(self, flag): - self.keySequenceEdit.setEnabled(not flag) - self.snippetDescription.setReadOnly(flag) - self.edit.setReadOnly(flag) - if flag: - self.snippetDescription.setDisabled(True) - self.edit.setDisabled(True) - else: - self.snippetDescription.setEnabled(True) - self.edit.setEnabled(True) - - def deleteSnippet(self): - selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row - snippetName = self.files.fileName(selection) - question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName) - if (question == QMessageBox.StandardButton.Yes): - log_debug("Deleting snippet %s." % snippetName) - self.clearSelection() - self.files.remove(selection) - self.registerAllSnippets() - - def snippetChanged(self): - if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()): - return False - (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) - if snippetKeys == None and not self.keySequenceEdit.keySequence().isEmpty(): - return True - if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence().toString(): - return True - return self.edit.toPlainText() != snippetCode or \ - self.snippetDescription.text() != snippetDescription - - def save(self): - log_debug("Saving snippet %s" % self.currentFile) - outputSnippet = codecs.open(self.currentFile, "w", "utf-8") - outputSnippet.write("#" + self.snippetDescription.text() + "\n") - outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") - outputSnippet.write(self.edit.toPlainText()) - outputSnippet.close() - self.registerAllSnippets() - - def run(self): - if self.context == None: - log_warn("Cannot run snippets outside of the UI at this time.") - return - if self.snippetChanged(): - question = QMessageBox.question(self, self.tr("Confirm"), self.tr("You have unsaved changes, must save first. Save?")) - if (question == QMessageBox.StandardButton.No): - return - else: - self.save() - actionText = actionFromSnippet(self.currentFile, self.snippetDescription.text()) - UIActionHandler.globalActions().executeAction(actionText, self.context) - - log_debug("Saving snippet %s" % self.currentFile) - outputSnippet = codecs.open(self.currentFile, "w", "utf-8") - outputSnippet.write("#" + self.snippetDescription.text() + "\n") - outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") - outputSnippet.write(self.edit.toPlainText()) - outputSnippet.close() - self.registerAllSnippets() - - def clearHotkey(self): - self.keySequenceEdit.clear() - - -def launchPlugin(context): - snippets = Snippets(context) - snippets.exec_() - - -if __name__ == '__main__': - app = QApplication(sys.argv) - snippets = Snippets(None) - snippets.show() - sys.exit(app.exec_()) -else: - Snippets.registerAllSnippets() - UIAction.registerAction("Snippets\\Snippet Editor...") - UIActionHandler.globalActions().bindAction("Snippets\\Snippet Editor...", UIAction(launchPlugin)) - Menu.mainMenu("Tools").addAction("Snippets\\Snippet Editor...", "Snippet") diff --git a/python/examples/snippets/media/snippets.gif b/python/examples/snippets/media/snippets.gif deleted file mode 100644 index 08ea5766..00000000 Binary files a/python/examples/snippets/media/snippets.gif and /dev/null differ diff --git a/python/examples/snippets/plugin.json b/python/examples/snippets/plugin.json deleted file mode 100644 index 27517a26..00000000 --- a/python/examples/snippets/plugin.json +++ /dev/null @@ -1 +0,0 @@ -{"pluginmetadataversion": 2, "name": "Snippet UI Plugin", "type": ["ui"], "api": ["python2", "python3"], "description": "Powerful code-editing plugin for writing and managing python code-snippets with syntax highlightingd, hotkey binding and other features", "longdescription": "The snippet editor started as a simple example UI plugin to demonstrate new features available to UI plugins. It has turned into a functionally useful plugin in its own right. The snippet editor allows you to write small bits of code that might not be big enough to warrant the effort of a full plugin but are longer enough that you don't want to retype them every time in the python-console!\n\nAs an added bonus, all snippets are added to the snippets menu and hot-keys can be associated with them as they make use of the action system. All action-system items are also available through the command-palette (CTL/CMD-p).\n\n![](https://github.com/Vector35/snippets/blob/master/media/snippets.gif?raw=true)\n\n.", "license": {"name": "MIT", "text": "Copyright (c) 2019 Vector 35 Inc\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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."}, "platforms": ["Darwin", "Linux", "Windows"], "installinstructions": {"Darwin": "no special instructions, package manager is recommended", "Linux": "no special instructions, package manager is recommended", "Windows": "no special instructions, package manager is recommended"}, "dependencies": {}, "version": "1.5", "author": "Vector 35 Inc", "minimumbinaryninjaversion": 1528} \ No newline at end of file -- cgit v1.3.1