├── LICENSE.md ├── MANIFEST ├── MANIFEST.in ├── README.md ├── escs.py ├── escs.sh ├── requirements.txt ├── screenshot-1.jpg ├── setup.cfg ├── setup.py ├── tests.sh ├── vy ├── vy.png └── vyapp ├── __init__.py ├── app.py ├── areavi.py ├── ask.py ├── base.py ├── completion.py ├── dap.py ├── mixins.py ├── notebook.py ├── panel.py ├── plugins ├── __init__.py ├── assoc.py ├── blink_pair.py ├── block_sel.py ├── builtin_modes.py ├── c_mode.py ├── caps.py ├── clip_path.py ├── clipboard.py ├── cmd.py ├── cmd_search.py ├── cmd_utils.py ├── codec.py ├── count_words.py ├── cplusplus_mode.py ├── cursor_status.py ├── data_del.py ├── data_sel.py ├── deadcode.py ├── delve.py ├── editrc.py ├── find.py ├── fsearch.py ├── fsniffer.py ├── fstmt.py ├── gdb.py ├── gists.py ├── gohints.py ├── golang_mode.py ├── hlink.py ├── home.py ├── html_mode.py ├── ibash.py ├── inline_comment.py ├── io.py ├── io_status.py ├── iocmd.py ├── javascript_mode.py ├── jedi.py ├── jsdebugger.py ├── jsonfmt.py ├── line_feed.py ├── line_index.py ├── line_scroll.py ├── line_strips.py ├── main_jumps.py ├── mc.py ├── mode_shortcut.py ├── mypy.py ├── outputs.py ├── page_scroll.py ├── pair_sel.py ├── pane_jumps.py ├── pane_resize.py ├── pdb.py ├── plugin_tools.py ├── project.py ├── python_mode.py ├── quick_jumps.py ├── quick_search.py ├── quick_sel.py ├── range_sel.py ├── rope.py ├── ruby_mode.py ├── seek_symbol.py ├── shift.py ├── snakerr.py ├── sniper.py ├── spacing.py ├── spawn │ ├── __init__.py │ ├── base_spawn.py │ ├── cross_platform.py │ └── unix_platform.py ├── splits.py ├── symbol_jumps.py ├── syntax │ ├── __init__.py │ ├── keys.py │ ├── spider.py │ ├── styles │ │ ├── __init__.py │ │ └── vy.py │ └── tools.py ├── syslog.py ├── tab_search.py ├── tabs.py ├── ternjs │ ├── __init__.py │ ├── completer.py │ └── tern-config ├── text_anchors.py ├── text_jumps.py ├── text_spots.py ├── tidy.py ├── undo.py ├── untwisted.py ├── urlsrc.py ├── word_completion.py ├── word_search.py ├── xdg_open.py ├── ycmd │ ├── __init__.py │ ├── client.py │ ├── default_settings.json │ └── ycm_extra_conf.py └── ysnippet.py ├── regutils.py ├── statusbar.py ├── stderr.py ├── stdout.py ├── tools.py ├── vyrc └── widgets.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Iury de oliveira gomes figueiredo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | LICENSE.md 3 | README.md 4 | requirements.txt 5 | screenshot-1.jpg 6 | setup.cfg 7 | setup.py 8 | vy 9 | vy.png 10 | vyapp/__init__.py 11 | vyapp/app.py 12 | vyapp/areavi.py 13 | vyapp/ask.py 14 | vyapp/base.py 15 | vyapp/completion.py 16 | vyapp/dap.py 17 | vyapp/mixins.py 18 | vyapp/notebook.py 19 | vyapp/panel.py 20 | vyapp/regutils.py 21 | vyapp/statusbar.py 22 | vyapp/tools.py 23 | vyapp/vyrc 24 | vyapp/widgets.py 25 | vyapp/plugins/__init__.py 26 | vyapp/plugins/anchors.py 27 | vyapp/plugins/assoc.py 28 | vyapp/plugins/blink_pair.py 29 | vyapp/plugins/block_sel.py 30 | vyapp/plugins/builtin_modes.py 31 | vyapp/plugins/c_mode.py 32 | vyapp/plugins/caps.py 33 | vyapp/plugins/clip_path.py 34 | vyapp/plugins/clipboard.py 35 | vyapp/plugins/cmd.py 36 | vyapp/plugins/cmd_search.py 37 | vyapp/plugins/cmd_utils.py 38 | vyapp/plugins/codec.py 39 | vyapp/plugins/count_words.py 40 | vyapp/plugins/cplusplus_mode.py 41 | vyapp/plugins/cursor_status.py 42 | vyapp/plugins/data_del.py 43 | vyapp/plugins/data_sel.py 44 | vyapp/plugins/deadcode.py 45 | vyapp/plugins/delve.py 46 | vyapp/plugins/editrc.py 47 | vyapp/plugins/find.py 48 | vyapp/plugins/fsearch.py 49 | vyapp/plugins/fsniffer.py 50 | vyapp/plugins/fstmt.py 51 | vyapp/plugins/gdb.py 52 | vyapp/plugins/gists.py 53 | vyapp/plugins/gohints.py 54 | vyapp/plugins/golang_mode.py 55 | vyapp/plugins/hlink.py 56 | vyapp/plugins/home.py 57 | vyapp/plugins/html_mode.py 58 | vyapp/plugins/ibash.py 59 | vyapp/plugins/inline_comment.py 60 | vyapp/plugins/io.py 61 | vyapp/plugins/io_status.py 62 | vyapp/plugins/iocmd.py 63 | vyapp/plugins/javascript_mode.py 64 | vyapp/plugins/jedi.py 65 | vyapp/plugins/jsdebugger.py 66 | vyapp/plugins/jsonfmt.py 67 | vyapp/plugins/line_feed.py 68 | vyapp/plugins/line_index.py 69 | vyapp/plugins/line_scroll.py 70 | vyapp/plugins/line_strips.py 71 | vyapp/plugins/main_jumps.py 72 | vyapp/plugins/mc.py 73 | vyapp/plugins/mode_shortcut.py 74 | vyapp/plugins/mypy.py 75 | vyapp/plugins/outputs.py 76 | vyapp/plugins/page_scroll.py 77 | vyapp/plugins/pair_sel.py 78 | vyapp/plugins/pdb.py 79 | vyapp/plugins/plugin_tools.py 80 | vyapp/plugins/project.py 81 | vyapp/plugins/python_mode.py 82 | vyapp/plugins/quick_jumps.py 83 | vyapp/plugins/quick_search.py 84 | vyapp/plugins/quick_sel.py 85 | vyapp/plugins/range_sel.py 86 | vyapp/plugins/rope.py 87 | vyapp/plugins/ruby_mode.py 88 | vyapp/plugins/seek_symbol.py 89 | vyapp/plugins/shift.py 90 | vyapp/plugins/snakerr.py 91 | vyapp/plugins/sniper.py 92 | vyapp/plugins/spacing.py 93 | vyapp/plugins/splits.py 94 | vyapp/plugins/symbol_jumps.py 95 | vyapp/plugins/syslog.py 96 | vyapp/plugins/tab_search.py 97 | vyapp/plugins/tabs.py 98 | vyapp/plugins/text_jumps.py 99 | vyapp/plugins/text_spots.py 100 | vyapp/plugins/tidy.py 101 | vyapp/plugins/undo.py 102 | vyapp/plugins/untwisted.py 103 | vyapp/plugins/urls.py 104 | vyapp/plugins/word_completion.py 105 | vyapp/plugins/word_search.py 106 | vyapp/plugins/xdg_open.py 107 | vyapp/plugins/ysnippet.py 108 | vyapp/plugins/spawn/__init__.py 109 | vyapp/plugins/spawn/base_spawn.py 110 | vyapp/plugins/spawn/cross_platform.py 111 | vyapp/plugins/spawn/unix_platform.py 112 | vyapp/plugins/syntax/__init__.py 113 | vyapp/plugins/syntax/keys.py 114 | vyapp/plugins/syntax/spider.py 115 | vyapp/plugins/syntax/tools.py 116 | vyapp/plugins/syntax/styles/__init__.py 117 | vyapp/plugins/syntax/styles/vy.py 118 | vyapp/plugins/ternjs/__init__.py 119 | vyapp/plugins/ternjs/completer.py 120 | vyapp/plugins/ternjs/tern-config 121 | vyapp/plugins/ycmd/__init__.py 122 | vyapp/plugins/ycmd/client.py 123 | vyapp/plugins/ycmd/default_settings.json 124 | vyapp/plugins/ycmd/ycm_extra_conf.py 125 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | include vy.png 3 | include *.jpg 4 | include requirements.txt 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![vy](vy.png) vy 2 | ================ 3 | 4 | A powerful modal editor written in python. 5 | 6 | vy is a modal editor with a very modular architecture. 7 | vy is built on top of Tkinter which is one of the most productive graphical toolkits; It permits vy 8 | to have such a great programming interface for plugins. Python is such an amazing language; 9 | it turns vy such a powerful application because its plugin API is high level naturally. 10 | 11 | In vy it is easy to create modes like it is in emacs, modes that support programming languages, 12 | provide all kind of functionalities that varies from accessing irc or email checking. 13 | The set of keys used in vy was carefully chosen to be handy although it is possible to make vy look like vim or emacs. 14 | 15 | The syntax highlighting plugin is very minimalistic and extremely fast. It supports syntax highlighting 16 | for all languages that python-pygments supports. The source code of the syntax highlighting plugin is about 17 | 120 lines of code. It is faster than the syntax highlighting plugins of both vim and emacs. :) 18 | It is possible to easily implement new syntax highlighting themes that work for all languages because it uses 19 | python pygments styles scheme. 20 | 21 | There is a simple and consistent terminal-like plugin in vy that turns it possible to talk to external processes. 22 | Such a feature is very handy when dealing with interpreters. One can just drop pieces of code to an interpreter 23 | then check the results. 24 | 25 | vy implements a Python debugger plugin and auto completion that permits debugging Python code easily and in a very cool way. 26 | One can set break points, remove break points, run the code then see the cursor jumping to the line 27 | that is being executed and much more. 28 | 29 | It is possible to open multiple vertical/horizontal panes to edit different files. Such a feature makes it possible 30 | to edit multiple files in a given tab. vy supports multiple tabs as well with a handy scheme of keys 31 | to switch focus between tabs and panes. 32 | 33 | There is a vyrc file written in Python that is very well documented and organized to make it simple to load 34 | plugins and set stuff at startup. You can take the best out of vy with no need to learn some odd language 35 | like vimscript or Emacs Lisp; since vy is written in Python, you use Python to develop for it. 36 | 37 | All built-in functions are well documented, which simplifies the process of plugin development as well as personalizing stuff. 38 | The plugins are documented: the documentation can be accessed from vy by dropping Python code to the interpreter. 39 | 40 | ![screenshot-1](screenshot-1.jpg) 41 | 42 | Features/Plugins 43 | ================ 44 | 45 | - **Python PDB Debugger** 46 | 47 | - **Golang Delve Debugger** 48 | * https://github.com/go-delve/delve 49 | 50 | - **GDB Debugger** 51 | 52 | - **Nodejs inspect Debugger** 53 | 54 | - **Rope Refactoring Tools** 55 | * https://github.com/python-rope/rope 56 | 57 | - **Fuzzy Search** 58 | 59 | - **Incremental Search** 60 | 61 | - **Python Pyflakes Integration** 62 | * https://github.com/PyCQA/pyflakes 63 | 64 | - **Tabs/Panes** 65 | 66 | - **Self documenting** 67 | 68 | - **HTML Tidy Integration** 69 | * http://tidy.sourceforge.net/ 70 | 71 | - **Powerful plugin API** 72 | 73 | - **Syntax highlighting for 300+ languages** 74 | 75 | - **Handy Shortcuts** 76 | 77 | - **Ycmd/YouCompleteMe Auto Completion** 78 | * https://github.com/ycm-core/ycmd 79 | 80 | - **Easily customizable (vyrc in python)** 81 | 82 | - **Quick Snippet Search** 83 | 84 | - **Smart Search with The Silver Searcher** 85 | * https://github.com/ggreer/the_silver_searcher 86 | 87 | - **File Manager** 88 | 89 | - **Python Static Type Checker** 90 | * http://mypy-lang.org/ 91 | 92 | - **Terminal-like** 93 | 94 | - **Irc Client Plugin** 95 | * https://github.com/vyapp/vyirc 96 | 97 | - **Find Function/Class Definition** 98 | 99 | - **Python Vulture Integration** 100 | * https://github.com/jendrikseipp/vulture 101 | 102 | - **Python Auto Completion** 103 | * https://github.com/davidhalter/jedi 104 | 105 | - **Ruby Auto Completion** 106 | * https://github.com/vyapp/rsense 107 | 108 | - **Golang Auto Completion** 109 | * https://github.com/nsf/gocode 110 | 111 | - **Javascript Auto Completion** 112 | * https://github.com/nsf/gocode 113 | 114 | The github organization https://github.com/vyapp is meant 115 | to hold vy related projects. 116 | 117 | Basic Install 118 | ============= 119 | 120 | **Note:** 121 | vy requires Python3 to run, python2 support is no longer available. 122 | 123 | ~~~ 124 | cd /tmp/ 125 | pip download vy 126 | tar -zxvf vy-* 127 | cd vy-*/ 128 | pip install -r requirements.txt 129 | python setup.py install 130 | ~~~ 131 | 132 | **Note:** 133 | As vy is in development there may occur some changes to the vyrc file format, it is important to remove 134 | your ~/.vy directory before a new installation in order to upgrade to a new version. 135 | 136 | Documentation 137 | ============= 138 | 139 | The vy docs may be outdated sometimes, i struggle to do my best to keep it all fine. There also 140 | exists many features which weren't documented yet. 141 | 142 | ### [Vy Book](https://github.com/iogf/vy/wiki) 143 | 144 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | vulture 2 | pyflakes 3 | untwisted==3.0.0 4 | jedi 5 | pygments 6 | future 7 | rope 8 | mypy -------------------------------------------------------------------------------- /screenshot-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyapp/vy/4ba0d379e21744fd79a740e8aeaba3a0a779973c/screenshot-1.jpg -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | 2 | [metadata] 3 | description-file = README.md 4 | 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup(name="vy", 6 | version="4.3.1", 7 | description="A vim-like in python made from scratch.", 8 | packages=["vyapp", 9 | "vyapp.plugins", 10 | "vyapp.plugins.syntax", 11 | "vyapp.plugins.spawn", 12 | "vyapp.plugins.syntax.styles", 13 | "vyapp.plugins.ycmd", 14 | "vyapp.plugins.ternjs"], 15 | scripts=['vy'], 16 | package_data={'vyapp': ['vyrc'], 17 | 'vyapp.plugins.ycmd': ['default_settings.json', 'ycm_extra_conf.py'], 18 | 'vyapp.plugins.ternjs':['tern-config']}, 19 | author="Iury O. G. Figueiredo", 20 | author_email="last.src@gmail.com", 21 | url='https://github.com/vyapp/vy', 22 | download_url='https://github.com/vyapp/vy/releases', 23 | keywords=['vy', 'vi', 'vim', 'emacs', 'sublime', 'atom', 'nano', 'vim-like'], 24 | classifiers=[]) 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /tests.sh: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # test ycmd. 3 | 4 | cd /usr/share/vim/vimfiles/third_party/ycmd/ 5 | ls 6 | vy -v ~/dd.py 7 | vy -v ~/foo.c 8 | 9 | 10 | -------------------------------------------------------------------------------- /vy: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import vyapp.app 4 | 5 | if __name__ == '__main__': 6 | from vyapp.app import root 7 | root.mainloop() 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /vy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyapp/vy/4ba0d379e21744fd79a740e8aeaba3a0a779973c/vy.png -------------------------------------------------------------------------------- /vyapp/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vyapp/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module exposes the vyapp.app.root attribute that is a variable pointing 3 | to the App class instance. The App class instance holds all vy editor's widgets. 4 | """ 5 | 6 | from vyapp.stderr import QUIET, logger 7 | from itertools import groupby 8 | from vyapp.base import App 9 | import argparse 10 | import logging 11 | import sys 12 | 13 | parser = argparse.ArgumentParser() 14 | 15 | parser.add_argument('files', nargs='*', help='Files') 16 | parser.add_argument('-t', '--tab', dest='scheme', 17 | action='append_const', const=None, default=[], 18 | help='Instantiate a new tab. Ex: -t -p file0 file1') 19 | 20 | parser.add_argument('-p', '--pane', 21 | action='append', dest='scheme', default=[], nargs='+', 22 | help='Open files in vertical/horizontal ways -p file0 file1 -p file2') 23 | 24 | parser.add_argument('-v', '--verbose', action='store_true', 25 | help='Show exceptions and messages.') 26 | 27 | args = parser.parse_args() 28 | 29 | lst = [list(g) for k, g in groupby(args.scheme, 30 | lambda x: not x) if not k] 31 | 32 | print('Loading vyrc...') 33 | # It points to the root toplevel window of vy. 34 | # It is the one whose AreaVi instances 35 | # are placed on. 36 | root = App() 37 | lst = lst + [[[ind]] for ind in args.files] 38 | 39 | if not args.verbose: 40 | logger.setLevel(logging.ERROR) 41 | 42 | # It has to be called from here otherwise the plugins will not 43 | # be able to import vyapp.app.root variable. 44 | root.create_vyrc() 45 | 46 | if not lst: 47 | root.note.create('none') 48 | else: 49 | root.note.load(*lst) 50 | 51 | root.event_generate('<>') 52 | 53 | def tk_xhook(exctype, value, tb): 54 | logger.exception('', exc_info=(exctype, value, tb)) 55 | root.report_callback_exception = tk_xhook 56 | 57 | if args.verbose: 58 | logger.setLevel(logging.DEBUG) 59 | else: 60 | logger.setLevel(QUIET) -------------------------------------------------------------------------------- /vyapp/ask.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module implements basic input data scheme. 3 | """ 4 | 5 | # from tkinter import * 6 | from tkinter import Frame, Entry, BOTH 7 | from vyapp.app import root 8 | from vyapp.mixins import DataEvent, IdleEvent 9 | 10 | class AskCancel(Exception): 11 | pass 12 | 13 | class InputBox: 14 | def __init__(self, default_data=''): 15 | self.default_data = default_data 16 | 17 | self.area = root.focus_get() 18 | self.frame = Frame(root, border=1, padx=3, pady=3) 19 | self.entry = Entry(self.frame) 20 | self.entry.config(background='grey') 21 | self.entry.focus_set() 22 | 23 | # Maybe there is a more elegant way. 24 | self.entry.bind('', lambda event: self.entry.focus_set()) 25 | self.entry.insert('end', default_data) 26 | self.entry.pack(side='left', expand=True, fill=BOTH) 27 | self.frame.grid(row=1, sticky='we') 28 | 29 | def done(self): 30 | self.entry.destroy() 31 | self.frame.destroy() 32 | self.area.focus_set() 33 | 34 | class Get(InputBox, DataEvent, IdleEvent): 35 | def __init__(self, events={}, default_data=''): 36 | InputBox.__init__(self, default_data) 37 | DataEvent.__init__(self, self.entry) 38 | IdleEvent.__init__(self, self.entry) 39 | 40 | self.entry.bindtags(('Entry', self.entry, '.', 'all')) 41 | for indi, indj in events.items(): 42 | self.entry.bind(indi, lambda event, handle=indj: 43 | self.dispatch(handle) , add=True) 44 | 45 | def dispatch(self, handle): 46 | is_done = handle(self.entry) 47 | if is_done == True: 48 | self.done() 49 | 50 | class Ask(InputBox): 51 | """ 52 | Used to grab user input from the user. 53 | 54 | Usage: 55 | 56 | ask = Ask('Default value') 57 | 58 | # The data inputed by the user. 59 | ask.data 60 | 61 | When the input data process is canceled then it raises 62 | AskCancel exception. 63 | 64 | The Ask widget would be used in situations where user input is necessary 65 | to proceed with the task thus raising an exception when user cancels is 66 | suitable. 67 | """ 68 | 69 | def __init__(self, default_data =''): 70 | InputBox.__init__(self, default_data) 71 | self.entry.bind('', lambda event: self.on_success()) 72 | self.entry.bind('', lambda event: self.cancel()) 73 | self.data = None 74 | self.area.wait_window(self.frame) 75 | 76 | if self.data == None: 77 | raise AskCancel('Canceled input!') 78 | 79 | def on_success(self): 80 | self.data = self.entry.get() 81 | InputBox.done(self) 82 | 83 | def cancel(self): 84 | """ 85 | Called on , the self.data attribute 86 | is set to None which means the user just canceled 87 | the action. 88 | """ 89 | 90 | self.data = None 91 | InputBox.done(self) 92 | 93 | def __str__(self): 94 | return self.data 95 | 96 | __repr__ = __str__ 97 | 98 | 99 | -------------------------------------------------------------------------------- /vyapp/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | """ 3 | 4 | from os.path import expanduser, join, exists, dirname 5 | from vyapp.statusbar import StatusBar 6 | from vyapp.notebook import NoteVi 7 | from vyapp.plugins import ENV 8 | from shutil import copyfile 9 | from tkinter import Tk, Grid 10 | from os import mkdir 11 | import sys 12 | 13 | class App(Tk): 14 | """ 15 | This class implements the most basic vy editor widget. 16 | It holds a NoteViwidget instance and a StatusBar widget instance. 17 | Plugins that demand accessing the NoteVi instance could use: 18 | 19 | from vyapp.app import root 20 | area = root.note.create('filename') 21 | 22 | Plugins that demand accessing the StatusBar instance could use. 23 | 24 | from vyapp.app import root 25 | root.status.set_msg('Message!') 26 | """ 27 | 28 | def __init__(self, *args, **kwargs): 29 | """ 30 | App class constructor. The arguments are passed 31 | to Tk class widget. 32 | """ 33 | 34 | Tk.__init__(self, *args, **kwargs) 35 | self.note = None 36 | self.status = None 37 | self.title('Vy') 38 | self.create_widgets() 39 | 40 | def create_vyrc(self): 41 | self.dir = join(expanduser('~'), '.vy') 42 | self.rc = join(self.dir, 'vyrc') 43 | 44 | if not exists(self.dir): 45 | mkdir(self.dir) 46 | 47 | if not exists(self.rc): 48 | copyfile(join(dirname(__file__), 'vyrc'), self.rc) 49 | exec(compile(open(self.rc).read(), self.rc, 'exec'), ENV) 50 | 51 | def create_widgets(self): 52 | self.note = NoteVi(master=self, takefocus=0) 53 | self.note.grid(row=0, sticky='wens') 54 | 55 | self.status = StatusBar(master=self) 56 | self.status.grid(row=2, sticky='we') 57 | Grid.rowconfigure(self, 0, weight=1) 58 | Grid.columnconfigure(self, 0, weight=1) 59 | 60 | -------------------------------------------------------------------------------- /vyapp/dap.py: -------------------------------------------------------------------------------- 1 | from untwisted.expect import Expect, LOAD, CLOSE 2 | from os.path import abspath 3 | from vyapp.areavi import AreaVi 4 | from vyapp.app import root 5 | import sys 6 | 7 | class DAP: 8 | """ 9 | Debugger adapter pattern. 10 | 11 | This class makes it simple to implement new debuggers in vy. 12 | It follows a specific approach that is not necessarily strict. 13 | 14 | It makes usage of Untwisted Framework's usage to implement its basic 15 | logic. 16 | """ 17 | 18 | setup={'background':'blue', 'foreground':'yellow'} 19 | encoding='utf8' 20 | 21 | def __init__(self): 22 | self.expect = None 23 | 24 | def create_process(self, args): 25 | self.expect = Expect(*args) 26 | 27 | # Note: The data has to be decoded using the area charset 28 | # because the area contents would be sometimes printed along 29 | # the debugging. 30 | self.expect.add_map(LOAD, lambda con, 31 | data: sys.stdout.write(data.decode(self.area.charset))) 32 | 33 | # The expect has to be passed here otherwise when 34 | # starting the new one gets terminated. 35 | 36 | self.expect.add_map(CLOSE, self.on_bkpipe) 37 | 38 | self.install_handles(self.expect) 39 | root.protocol("WM_DELETE_WINDOW", self.on_tk_quit) 40 | 41 | def on_bkpipe(self, expect): 42 | """ 43 | On broken pipe. 44 | """ 45 | expect.terminate() 46 | root.status.set_msg('Debugger: CLOSED!') 47 | 48 | def on_tk_quit(self): 49 | """ 50 | Necessary otherwise the thread hangs. 51 | """ 52 | self.expect.terminate() 53 | root.destroy() 54 | 55 | def quit_db(self, event): 56 | self.kill_process() 57 | event.widget.chmode('NORMAL') 58 | 59 | def run(self, event): 60 | """ 61 | To be implemented. 62 | """ 63 | 64 | def run_args(self, event): 65 | """ 66 | To be implemented. 67 | """ 68 | 69 | def kill_process(self): 70 | if self.expect: 71 | self.expect.terminate() 72 | 73 | def install_handles(self, device): 74 | """ 75 | This method is meant to be implemented. It is supposed to 76 | extract necessary attributes from the underlying debugger output 77 | to be dispatched to these methods: 78 | 79 | Debugger has hit a given line: 80 | 81 | self.handle_line 82 | 83 | """ 84 | 85 | def handle_line(self, device, filename, line): 86 | """ 87 | 88 | """ 89 | filename = abspath(filename) 90 | 91 | wids = AreaVi.get_opened_files(root) 92 | area = wids.get(filename) 93 | 94 | if area: root.note.set_line(area, line) 95 | area.tag_delete('(DebuggerBP)') 96 | area.tag_add('(DebuggerBP)', '%s.0 linestart' % line, '%s.0 lineend' % line) 97 | area.tag_config('(DebuggerBP)', **self.setup) 98 | root.status.set_msg('Debugger stopped at: %s:%s' % (filename, line)) 99 | 100 | 101 | def send(self, data): 102 | """ 103 | To implement: 104 | 105 | Example: 106 | self.expect.send(data.encode(self.encoding)) 107 | 108 | """ 109 | -------------------------------------------------------------------------------- /vyapp/mixins.py: -------------------------------------------------------------------------------- 1 | class IdleEvent: 2 | def __init__(self, widget): 3 | self.widget.bind('<>', self.on_data, add=True) 4 | self.widget = widget 5 | self.timeout = 1200 6 | self.funcid = '' 7 | 8 | def on_data(self, event): 9 | # Make sure self.funcid is initialized before calling after_cancel. 10 | # The idea here it is to have <> spawned once when the user 11 | # stopped typing. 12 | 13 | if self.funcid: 14 | self.widget.after_cancel(self.funcid) 15 | self.funcid = self.widget.after(self.timeout, self.send_idle) 16 | 17 | def send_idle(self): 18 | self.widget.event_generate('<>') 19 | 20 | class Echo: 21 | """ 22 | 23 | """ 24 | 25 | def __init__(self, area): 26 | self.area = area 27 | self.bind('', self.on_backspace) 28 | self.bind('', self.dispatch) 29 | 30 | def dispatch(self, event): 31 | if event.char: 32 | self.on_char(event.char) 33 | 34 | def on_char(self, char): 35 | self.area.insert('insert', char) 36 | 37 | def on_backspace(self, event): 38 | self.area.delete('insert -1c', 'insert') 39 | self.on_delete(event) 40 | 41 | def on_delete(self, event): 42 | pass 43 | 44 | class DataEvent: 45 | def __init__(self, widget): 46 | self.widget = widget 47 | self.widget.bind('', self.dispatch_data, add=True) 48 | 49 | def dispatch_data(self, event): 50 | if event.char: 51 | self.widget.event_generate('<>') 52 | 53 | -------------------------------------------------------------------------------- /vyapp/notebook.py: -------------------------------------------------------------------------------- 1 | from vyapp.panel import PanedVerticalWindow 2 | from vyapp.areavi import AreaVi 3 | from tkinter.ttk import Notebook 4 | from tkinter import BOTH 5 | import sys 6 | 7 | class NoteVi(Notebook): 8 | """ 9 | This class implements vy tabs. 10 | """ 11 | 12 | def __init__(self, *args, **kwargs): 13 | Notebook.__init__(self, *args, **kwargs) 14 | self.bindtags((self, '.', 'all')) 15 | 16 | def set_line(self, area, line, col=0): 17 | """ 18 | This function receives an AreaVi widget instance and a line number 19 | then sets the focus to the AreaVi widget and the cursor at line. 20 | """ 21 | 22 | sys.stderr.write(area.filename + '\n') 23 | self.select(area.master.master.master) 24 | area.focus() 25 | area.setcur(line, col) 26 | 27 | def create(self, filename): 28 | """ 29 | This method creates a new tab whose title is the string 30 | passed as filename. 31 | """ 32 | 33 | base = PanedVerticalWindow(master=self) 34 | area = base.create(filename) 35 | self.add(base, text=filename) 36 | return area 37 | 38 | def open(self, filename): 39 | base = PanedVerticalWindow(master=self) 40 | self.add(base) 41 | area = base.open(filename) 42 | return area 43 | 44 | def load(self, *args): 45 | """ 46 | This method opens the files that are specified in args into new tabs and panes. 47 | The structure of args is like: 48 | 49 | args = ((('file1', 'file2'), ('file3', 'file4')), (('file5', 'file6'), )) 50 | 51 | It would create two tabs, the first tab would have four panes, the second tab 52 | would be two panes. 53 | """ 54 | 55 | for indi in args: 56 | base = PanedVerticalWindow(master=self) 57 | base.pack(side='left', expand=True, fill=BOTH) 58 | self.add(base) 59 | for indj in indi: 60 | base.load(*indj) 61 | 62 | def next(self, func): 63 | """ 64 | """ 65 | 66 | tabs = self.tabs() 67 | index = self.index(self.select()) 68 | 69 | for ind in tabs[index + 1:]: 70 | if func(self.tab(ind, 'text')): 71 | yield ind 72 | 73 | def back(self, func): 74 | """ 75 | """ 76 | 77 | tabs = self.tabs() 78 | index = self.index(self.select()) 79 | tabs = tabs[:index] 80 | 81 | for ind in reversed(tabs): 82 | if func(self.tab(ind, 'text')): 83 | yield ind 84 | 85 | def find(self, func): 86 | for ind in self.tabs(): 87 | if func(self.tab(ind, 'text')): 88 | yield ind 89 | 90 | def set_area_focus(self): 91 | wid = self.nametowidget(self.select()) 92 | wid.focused_area.focus_set() 93 | 94 | def restore_area_focus(self): 95 | """ 96 | When an AreaVi is destroyed, the focused_area 97 | is a dead widget, so it gives focus to the first AreaVi 98 | in the active tab. 99 | """ 100 | 101 | wid = self.nametowidget(self.select()) 102 | seq = AreaVi.areavi_widgets(wid) 103 | area = next(seq) 104 | area.focus_set() 105 | 106 | def on(self, *args): 107 | """ 108 | When the method Notebook.select is called it sets the application 109 | focus to the last visible widget in the selected tab. This method 110 | calls select then restores the focus. It may sound like a bug in tkinter. 111 | """ 112 | 113 | wid=self.focus_get() 114 | self.select(*args) 115 | self.after(30, lambda : wid.focus_set()) 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /vyapp/panel.py: -------------------------------------------------------------------------------- 1 | from tkinter import PanedWindow, RAISED, BOTH, HORIZONTAL 2 | from tkinter import Frame, Scrollbar, Y, VERTICAL 3 | from vyapp.areavi import AreaVi 4 | 5 | class PanedHorizontalWindow(PanedWindow): 6 | """ 7 | This widget is used to create horizontal panes. 8 | """ 9 | 10 | def __init__(self, *args, **kwargs): 11 | PanedWindow.__init__(self, orient=HORIZONTAL, *args, **kwargs) 12 | 13 | def create(self, filename='none'): 14 | """ 15 | This method creates a horizontal AreaVi widget. It returns the 16 | AreaVi widget that was created. It as well installs the plugins 17 | that appear in the vyrc file in the AreaVi widget. 18 | """ 19 | frame = Frame(master=self) 20 | scrollbar = Scrollbar(master=frame) 21 | area = AreaVi(filename, frame , border=3, relief=RAISED, 22 | yscrollcommand=scrollbar.set) 23 | scrollbar.config(command=area.yview) 24 | scrollbar.pack(side='right', fill=Y) 25 | 26 | from vyapp.plugins import HANDLE 27 | 28 | for handle, args, kwargs in HANDLE: 29 | handle(area, *args, **kwargs) 30 | 31 | area.focus_set() 32 | area.pack(expand=True, side='left', fill=BOTH) 33 | self.add(frame) 34 | 35 | def save_focus(event): 36 | self.master.focused_area = area 37 | 38 | self.master.focused_area = area 39 | area.bind('', save_focus) 40 | return area 41 | 42 | def load(self, filename): 43 | """ 44 | It creates a horizontal split and loads the content of filename 45 | into the new AreaVi instance. It returns the AreaVi widget that 46 | was created. 47 | """ 48 | 49 | area = self.create() 50 | area.load_data(filename) 51 | return area 52 | 53 | class PanedVerticalWindow(PanedWindow): 54 | """ 55 | This widget implements vertical panes. 56 | """ 57 | 58 | def __init__(self, *args, **kwargs): 59 | PanedWindow.__init__(self, orient=VERTICAL, *args, **kwargs) 60 | self.focused_area = None 61 | 62 | def create(self, filename='none'): 63 | """ 64 | This method creates a new horizontal window in which 65 | it is possible to create new horizontal splits. The argument filename 66 | is set as attribute for the new AreaVi widget. 67 | """ 68 | 69 | base = PanedHorizontalWindow(master=self) 70 | self.add(base) 71 | area = base.create(filename) 72 | return area 73 | 74 | def open(self, filename): 75 | base = PanedHorizontalWindow(master=self) 76 | self.add(base) 77 | area = base.load(filename) 78 | return area 79 | 80 | def load(self, *args): 81 | """ 82 | This method adds a new horizontal window and loads 83 | the content of the files passed as args in the new AreaVi 84 | 85 | widgets. 86 | """ 87 | 88 | base = PanedHorizontalWindow(master=self) 89 | self.add(base) 90 | 91 | for ind in args: 92 | base.load(ind) 93 | return base 94 | 95 | -------------------------------------------------------------------------------- /vyapp/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | # ENV is a dict holding plugins objects, like functions, classes etc. 4 | # Plugins should install their handles in ENV. 5 | HANDLE = [] 6 | ENV = {} 7 | 8 | def autoload(plugin, *args, **kwargs): 9 | HANDLE.append((plugin.install, args, kwargs)) 10 | 11 | def autocall(handle, *args, **kwargs): 12 | HANDLE.append((handle, args, kwargs)) 13 | 14 | def mapset(namespace, map): 15 | HANDLE.append((lambda area: 16 | area.update_map(namespace, map), (), {})) 17 | 18 | class Command: 19 | area = None 20 | def __init__(self, name=None): 21 | self.name = name 22 | 23 | def __call__(self, handle): 24 | name = self.name if self.name else handle.__name__ 25 | @wraps(handle) 26 | def wrapper(*args, **kwargs): 27 | return handle(Command.area, *args, **kwargs) 28 | ENV[name] = wrapper 29 | return wrapper 30 | 31 | @classmethod 32 | def set_target(cls, area): 33 | cls.area = area 34 | -------------------------------------------------------------------------------- /vyapp/plugins/assoc.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Overview 4 | ======== 5 | 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: assoc 11 | 12 | Mode: 13 | Event: 14 | Description: 15 | 16 | """ 17 | 18 | from vyapp.app import root 19 | 20 | def install(area): 21 | area.install('assoc', ('NORMAL', '', 22 | lambda event: root.status.set_msg('\n'.join( 23 | event.widget.get_assoc_data())))) 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /vyapp/plugins/blink_pair.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements a mechanism to highligh pairs of ( ) [ ] { }. 6 | 7 | 8 | """ 9 | from vyapp.areavi import AreaVi 10 | from vyapp.app import root 11 | 12 | class BlinkPair: 13 | setup={'background':'pink', 14 | 'foreground':'black'} 15 | max=1000 16 | pairs = ('(', ')'), ('[', ']'), ('{', '}') 17 | 18 | def __init__(self, area): 19 | area.tag_config('(BLINK)', **self.setup) 20 | self.area = area 21 | area.bind('<>', self.blink, add=True) 22 | 23 | def blink(self, event): 24 | index0 = 'insert -%sc' % self.max 25 | index1 = 'insert +%sc' % self.max 26 | 27 | self.area.tag_remove('(BLINK)', index0, index1) 28 | for lhs, lhr in self.pairs: 29 | index = self.area.case_pair('insert', self.max, lhs, lhr) 30 | if index: 31 | self.area.tag_add('(BLINK)', index, '%s +1c' % index) 32 | 33 | install = BlinkPair -------------------------------------------------------------------------------- /vyapp/plugins/block_sel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements block selection of text. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: block-sel 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Add block selection one line up. 15 | 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Add block selection one line down. 20 | 21 | Mode: NORMAL 22 | Event: 23 | Description: Add block selection one char left. 24 | 25 | Mode: NORMAL 26 | Event: 27 | Description: Add block selection one char right. 28 | 29 | """ 30 | from vyapp.app import root 31 | 32 | class BlockSel: 33 | def __init__(self, area): 34 | area.install('block-sel', 35 | ('NORMAL', '', self.block_up), 36 | ('NORMAL', '', self.block_down), 37 | ('NORMAL', '', self.block_left), 38 | ('NORMAL', '', self.block_right), 39 | ('NORMAL', '', self.start_block_selection)) 40 | area.mark_set('(BLOCK_SEL_MARK)', '1.0') 41 | 42 | self.area = area 43 | 44 | def addblock(self, index0, index1): 45 | """ 46 | It adds block selection from index0 to index1. 47 | """ 48 | 49 | index2 = self.area.min(index0, index1) 50 | index3 = self.area.max(index0, index1) 51 | a, b = self.area.indexsplit(index2) 52 | c, d = self.area.indexsplit(index3) 53 | 54 | for ind in range(a, c + 1): 55 | self.area.addsel('%s.%s' % (ind, min(b, d)), 56 | '%s.%s' % (ind, max(b, d))) 57 | 58 | def rmblock(self, index0, index1): 59 | """ 60 | It removes block selection from index0 to index1. 61 | """ 62 | 63 | index2 = self.area.min(index0, index1) 64 | index3 = self.area.max(index0, index1) 65 | 66 | a, b = self.area.indexsplit(index2) 67 | c, d = self.area.indexsplit(index3) 68 | 69 | for ind in range(a, c + 1): 70 | self.area.rmsel('%s.%s' % (ind, min(b, d)), 71 | '%s.%s' % (ind, max(b, d))) 72 | 73 | def block_down(self, event): 74 | """ 75 | It adds or removes block selection one line down. 76 | """ 77 | 78 | a, b = self.area.indexref('(CURSOR_LAST_COL)') 79 | c, d = self.area.indexref() 80 | 81 | index = self.area.index('(BLOCK_SEL_MARK)') 82 | self.rmblock(index, '%s.%s' % (c, b)) 83 | self.area.down() 84 | 85 | a, b = self.area.indexref('(CURSOR_LAST_COL)') 86 | c, d = self.area.indexref() 87 | 88 | self.addblock(index, '%s.%s' % (c, b)) 89 | 90 | def block_up(self, event): 91 | """ 92 | It adds or removes block selection one line up. 93 | """ 94 | 95 | a, b = self.area.indexref('(CURSOR_LAST_COL)') 96 | c, d = self.area.indexref() 97 | index = self.area.index('(BLOCK_SEL_MARK)') 98 | 99 | self.rmblock(index, '%s.%s' % (c, b)) 100 | self.area.up() 101 | 102 | a, b = self.area.indexref('(CURSOR_LAST_COL)') 103 | c, d = self.area.indexref() 104 | 105 | self.addblock(index, '%s.%s' % (c, b)) 106 | 107 | def block_left(self, event): 108 | """ 109 | It adds block selection to the left. 110 | """ 111 | 112 | a, b = self.area.indexref('(CURSOR_LAST_COL)') 113 | c, d = self.area.indexref() 114 | 115 | index = self.area.index('(BLOCK_SEL_MARK)') 116 | self.rmblock(index, '%s.%s' % (c, b)) 117 | self.area.left() 118 | 119 | a, b = self.area.indexref('(CURSOR_LAST_COL)') 120 | c, d = self.area.indexref() 121 | 122 | self.addblock(index, '%s.%s' % (c, b)) 123 | 124 | def block_right(self, event): 125 | """ 126 | It adds/removes block selection to the right. 127 | """ 128 | 129 | a, b = self.area.indexref('(CURSOR_LAST_COL)') 130 | c, d = self.area.indexref() 131 | 132 | index = self.area.index('(BLOCK_SEL_MARK)') 133 | self.rmblock(index, '%s.%s' % (c, b)) 134 | self.area.right() 135 | 136 | a, b = self.area.indexref('(CURSOR_LAST_COL)') 137 | c, d = self.area.indexref() 138 | 139 | self.addblock(index, '%s.%s' % (c, b)) 140 | 141 | def start_block_selection(self, event): 142 | """ 143 | Start block selection. 144 | """ 145 | 146 | self.area.mark_set('(BLOCK_SEL_MARK)', 'insert') 147 | root.status.set_msg('Dropped block selection mark.') 148 | 149 | install = BlockSel 150 | 151 | -------------------------------------------------------------------------------- /vyapp/plugins/builtin_modes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements the NORMAL mode that is the mode in which most 6 | editing keycommands are implemented. 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: normal-mode 12 | 13 | Mode: -1 14 | Event: 15 | Description: Get the focused AreaVi instance in NORMAL mode. 16 | 17 | Mode: -1 18 | Event: 19 | Description: Get the focused AreaVi instance in EXTRA mode. 20 | 21 | Mode: NORMAL 22 | Event: 23 | Description: Get the focused AreaVi instance in INSERT mode. 24 | 25 | Mode: NORMAL 26 | Event: 27 | Description: Get the AreaVi instance in BETA mode. 28 | 29 | Mode: NORMAL 30 | Event: 31 | Description: Get the AreaVi instance in DELTA mode. 32 | 33 | Mode: 34 | Event: 35 | Description: Get the AreaVi instance that has focus in GAMMA mode. 36 | 37 | Mode: NORMAL 38 | Event: 39 | Description: Get the AreaVi instance that is focused in ALPHA mode. 40 | """ 41 | 42 | class BuiltinModes: 43 | def __init__(self, area): 44 | self.area = area 45 | 46 | area.add_mode('NORMAL') 47 | area.chmode('NORMAL') 48 | 49 | area.add_mode('INSERT', opt=True) 50 | area.add_mode('DELTA') 51 | area.add_mode('GAMMA') 52 | area.add_mode('BETA') 53 | area.add_mode('ALPHA') 54 | area.add_mode('EXTRA') 55 | 56 | area.install('builtin-modes', 57 | (-1, '', self.switch_normal), 58 | (-1, '', self.switch_extra), 59 | ('NORMAL', '', self.switch_insert), 60 | ('NORMAL', '', self.switch_delta), 61 | ('NORMAL', '', self.switch_gamma), 62 | ('NORMAL', '', self.switch_beta), 63 | ('NORMAL', '', self.switch_alpha)) 64 | 65 | def switch_delta(self, event): 66 | """ 67 | """ 68 | self.area.chmode('DELTA') 69 | 70 | def switch_gamma(self, event): 71 | """ 72 | """ 73 | 74 | self.area.chmode('GAMMA') 75 | 76 | def switch_beta(self, event): 77 | """ 78 | """ 79 | 80 | self.area.chmode('BETA') 81 | 82 | def switch_alpha(self, event): 83 | """ 84 | """ 85 | 86 | self.area.chmode('ALPHA') 87 | 88 | def switch_normal(self, event): 89 | """ 90 | """ 91 | self.area.chmode('NORMAL') 92 | self.area.clear_selection() 93 | 94 | def switch_insert(self, event): 95 | """ 96 | """ 97 | 98 | self.area.chmode('INSERT') 99 | self.area.clear_selection() 100 | 101 | def switch_extra(self, event): 102 | """ 103 | """ 104 | 105 | self.area.chmode('EXTRA') 106 | return 'break' 107 | 108 | install = BuiltinModes 109 | 110 | 111 | -------------------------------------------------------------------------------- /vyapp/plugins/c_mode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Extra mode for c programming language. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: c-mode 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Switch to C mode. 15 | """ 16 | 17 | def c_mode(area): 18 | area.chmode('C') 19 | 20 | def install(area): 21 | area.add_mode('C') 22 | area.install('c-mode', ('NORMAL', '', 23 | lambda event: c_mode(area))) 24 | 25 | 26 | -------------------------------------------------------------------------------- /vyapp/plugins/caps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements python functions to get text in lower case and upper case. 6 | 7 | Commands 8 | ======== 9 | 10 | Command: lower() 11 | Description: Turn the selected text into lower case. 12 | 13 | Command: upper() 14 | Description: Turn the selected text into upper case. 15 | """ 16 | 17 | from vyapp.plugins import Command 18 | 19 | @Command() 20 | def lower(area): 21 | """ 22 | """ 23 | 24 | map = area.tag_ranges('sel') 25 | for index in range(0, len(map) - 1, 2): 26 | area.swap(area.get(map[index], 27 | map[index + 1]).lower(), map[index], map[index + 1]) 28 | 29 | @Command() 30 | def upper(area): 31 | """ 32 | """ 33 | 34 | map = area.tag_ranges('sel') 35 | for index in range(0, len(map) - 1, 2): 36 | area.swap(area.get(map[index], 37 | map[index + 1]).upper(), map[index], map[index + 1]) 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /vyapp/plugins/clip_path.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | It is handy to quickly get the absolute path of the file that is being edited. This plugin 6 | implements a Key-Command for that. 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: clip-path 12 | 13 | Mode: EXTRA 14 | Event: 15 | Description: Copies the complete path of the file to the clipboard. 16 | 17 | """ 18 | 19 | from vyapp.app import root 20 | 21 | def clip_ph(area): 22 | """ Sends filename path to clipboard. """ 23 | area.clipboard_clear() 24 | area.clipboard_append(area.filename) 25 | root.status.set_msg('File path copied to the clipboard.') 26 | 27 | def install(area): 28 | area.install('clip-path', ('EXTRA', '', lambda event: clip_ph(event.widget))) 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /vyapp/plugins/clipboard.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements a set of basic functionalities to deal with text. Like copying, 6 | cutting, deleting, pasting text to the clipboard. 7 | 8 | 9 | Key-Commands 10 | ============ 11 | 12 | Namespace: clipboard 13 | 14 | Mode: NORMAL 15 | Event: 16 | Description: Copy selection to the clipboard. 17 | 18 | 19 | Mode: NORMAL 20 | Event: 21 | Description: Cut selection then add to the clipboard. 22 | 23 | 24 | Mode: NORMAL 25 | Event: 26 | Description: Paste text from the clipboard in the cursor position. 27 | 28 | 29 | Mode: NORMAL 30 | Event: 31 | Description: Paste text from the clipboard one line down. 32 | 33 | 34 | Mode: NORMAL 35 | Event: 36 | Description: Paste text from the clipboard one line up. 37 | 38 | Mode: EXTRA 39 | Event: 40 | Description: Add selection to the clipboard with a separator \n. 41 | 42 | Mode: EXTRA 43 | Event: 44 | Description: Split clipboard content on \n and insert each one of the lines 45 | in its corresponding line index's based on the cursor column. 46 | 47 | Clipboard: 48 | 49 | ab 50 | cd 51 | ef 52 | 53 | Text: 54 | 55 | alpha 56 | beta 57 | gamma 58 | 59 | If the cursor is on column l then text will be: 60 | 61 | Text: 62 | 63 | aablpha 64 | bcdeta 65 | gefamma 66 | 67 | Mode: EXTRA 68 | Event: 69 | Description: Cut selection and add to the clipboard with a separator \n. 70 | 71 | """ 72 | 73 | from vyapp.app import root 74 | 75 | class Clipboard: 76 | def __init__(self, area): 77 | area.install('clipboard', 78 | ('NORMAL', '', self.copysel), 79 | ('NORMAL', '', self.cutsel), 80 | ('NORMAL', '', self.ptsel), 81 | ('NORMAL', '', self.ptsel_after), 82 | ('NORMAL', '', self.ptsel_before), 83 | ('EXTRA', '', self.cpsel_with_sep), 84 | ('EXTRA', '', self.ptsel_block), 85 | ('EXTRA', '', self.cutsel_with_sep)) 86 | self.area = area 87 | 88 | def cutsel(self, event): 89 | """ 90 | Cut selection. 91 | """ 92 | 93 | event.widget.ctsel() 94 | root.status.set_msg('Text was cut!') 95 | 96 | def copysel(self, event): 97 | """ 98 | Copy selection. 99 | """ 100 | 101 | event.widget.cpsel() 102 | root.status.set_msg('Text was copied!') 103 | 104 | def cpsel_with_sep(self, event): 105 | """ 106 | Copy selection. 107 | """ 108 | self.area.cpsel('\n') 109 | root.status.set_msg('Text was copied with sep: \\n!') 110 | self.area.chmode('NORMAL') 111 | 112 | def cutsel_with_sep(self, event): 113 | self.area.ctsel('\n') 114 | root.status.set_msg('Text was cut with sep: \\n!') 115 | self.area.chmode('NORMAL') 116 | 117 | def ptsel(self, event): 118 | """ 119 | Paste text at the cursor position. 120 | """ 121 | 122 | data = self.area.clipboard_get() 123 | self.area.edit_separator() 124 | self.area.insert('insert', data) 125 | root.status.set_msg('Text was pasted!') 126 | 127 | def ptsel_after(self, event): 128 | """ 129 | Paste text one line down the cursor position. 130 | """ 131 | 132 | data = self.area.clipboard_get() 133 | self.area.edit_separator() 134 | self.area.insert('insert +1l linestart', data) 135 | root.status.set_msg('Text was pasted!') 136 | 137 | 138 | def ptsel_before(self, event): 139 | """ 140 | Paste text one line up the cursor position. 141 | """ 142 | 143 | data = self.area.clipboard_get() 144 | self.area.edit_separator() 145 | self.area.insert('insert linestart', data) 146 | root.status.set_msg('Text was pasted!') 147 | 148 | def ptsel_block(self, event): 149 | data = self.area.clipboard_get() 150 | data = data.split('\n') 151 | line, col = self.area.indexref() 152 | 153 | self.area.edit_separator() 154 | for ind in range(0, len(data)): 155 | self.area.insert('%s.%s' % (line + ind, col), data[ind]) 156 | 157 | self.area.chmode('NORMAL') 158 | root.status.set_msg('Text block was pasted') 159 | 160 | install = Clipboard 161 | 162 | -------------------------------------------------------------------------------- /vyapp/plugins/cmd.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | It implements functionalities to execute python code that affects vy state. 6 | 7 | With this plugin it is possible to drop the output to a given 8 | Line.Col inside an AreaVi instance. 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Namespace: cmd 14 | 15 | Mode: Global 16 | Event: 17 | Description: Set an AreaVi instance as target for commands. 18 | 19 | Mode: NORMAL 20 | Event: 21 | Description: Executes a sequence of python code that is selected. 22 | 23 | Mode: Global 24 | Event: 25 | Description: Open an input box in order to type inline python code to be executed. 26 | 27 | """ 28 | 29 | from traceback import print_exc as debug 30 | from vyapp.plugins import Command 31 | from vyapp.tools import e_stop 32 | from vyapp.ask import Ask 33 | from vyapp.plugins import ENV 34 | from vyapp.app import root 35 | import re 36 | import sys 37 | 38 | class Cmd: 39 | TAGCONF = {'background':'#313131'} 40 | 41 | def __init__(self, area): 42 | self.area = area 43 | 44 | area.tag_configure('(CODE)', **Cmd.TAGCONF) 45 | area.install('cmd', 46 | (-1, '', self.exec_cmd), 47 | ('NORMAL', '', self.exec_region), 48 | ('NORMAL', '', self.toggle_code), 49 | (-1, '', self.set_target)) 50 | 51 | @e_stop 52 | def exec_cmd(self, event): 53 | ask = Ask() 54 | Command.set_target(self.area) 55 | print('\n(cmd) Executed code:\n>>> %s\n' % ask.data) 56 | 57 | data = ask.data.encode('utf-8') 58 | self.runcode(data, ENV) 59 | 60 | def toggle_code(self, event): 61 | range = self.area.tag_bounds('(CODE)', 'insert') 62 | selected = self.area.tag_ranges('sel') 63 | 64 | if range and not selected: 65 | self.area.tag_remove('(CODE)', *range) 66 | elif selected: 67 | self.tag_code() 68 | 69 | def tag_code(self): 70 | index0 = self.area.index('sel.first') 71 | index1 = self.area.index('sel.last') 72 | 73 | self.area.clear_selection() 74 | self.area.tag_add('(CODE)', index0, index1) 75 | 76 | def runcode(self, data, env): 77 | # It has to be set before because if some data code catches 78 | # an exception then prints use print_exc it will go to sys.__stderr__. 79 | tmp = sys.stderr 80 | sys.stderr = sys.stdout 81 | 82 | try: 83 | exec(data, env) 84 | except Exception as e: 85 | debug() 86 | root.status.set_msg('Error: %s' % e) 87 | finally: 88 | sys.stderr = tmp 89 | 90 | def exec_region(self, event): 91 | # data = self.area.join_ranges('sel') 92 | range = self.area.tag_bounds('(CODE)', 'insert') 93 | if range: 94 | self.fmtexec(self.area.get(*range)) 95 | 96 | def fmtexec(self, data): 97 | fmtdata = re.sub(r'^|\n', '\n>>> ', data) 98 | fmtdata = '(cmd) Executed code:\n%s\n' % fmtdata 99 | sys.stdout.write(fmtdata) 100 | 101 | data = data.encode('utf-8') 102 | self.runcode(data, ENV) 103 | self.area.clear_selection() 104 | 105 | def set_target(self, event): 106 | Command.set_target(self.area) 107 | 108 | root.status.set_msg('Set command target !') 109 | return 'break' 110 | 111 | install = Cmd 112 | -------------------------------------------------------------------------------- /vyapp/plugins/cmd_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements command search tools. It is possible to execute python 6 | functions to highlight patterns of text and replace patterns. 7 | 8 | """ 9 | 10 | from vyapp.plugins import Command 11 | from vyapp.areavi import AreaVi 12 | 13 | @Command() 14 | def find(area, regex, handle, index='1.0', stopindex='end', 15 | backwards=False, exact=False, regexp=True, nocase=False, 16 | elide=False, nolinestop=False, step=''): 17 | 18 | """ 19 | Execute handle for each one of the regex matches in the target 20 | Areavi instance. The handle receives: 21 | 22 | Example: 23 | 24 | def handle(data, start, end): 25 | print('Match:' data, 'Start:%s', 'End:', end) 26 | 27 | find('foobar', handle) 28 | 29 | """ 30 | 31 | seq = area.find(regex, index, stopindex, backwards, exact, 32 | regexp, nocase, elide, nolinestop, step) 33 | 34 | for ind in seq: 35 | handle(*ind) 36 | 37 | @Command() 38 | def sniff(area, regex, handle, index='1.0', stopindex='end', exact=False, 39 | regexp=True, nocase=False, elide=False, nolinestop=False, step=''): 40 | 41 | """ 42 | Perform regex match in the selected text. 43 | 44 | """ 45 | 46 | seq = area.collect('sel', regex, index, stopindex, 47 | exact, regexp, nocase, elide, nolinestop, step) 48 | 49 | for ind in seq: 50 | handle(*ind) 51 | 52 | @Command() 53 | def sel(area, regex, index='1.0', stopindex='end', exact=False, 54 | regexp=True, nocase=False, elide=False, nolinestop=False, step=''): 55 | 56 | """ 57 | Add selection to all regex matches in the AreaVi instance. 58 | """ 59 | matches = area.find(regex, index, stopindex, False, exact, 60 | regexp, nocase, elide, nolinestop, step) 61 | 62 | for _, index0, index1 in matches: 63 | area.tag_add('sel', index0, index1) 64 | 65 | @Command() 66 | def split(area, *args, **kwargs): 67 | """ 68 | """ 69 | area.select_matches('sel', 70 | area.split(*args, **kwargs)) 71 | 72 | @Command() 73 | def lsub(area, regex, data, exact=False, regexp=True, 74 | nocase=False, elide=False, nolinestop=False, step=''): 75 | 76 | """ 77 | Replae text in a selected region of an AreaVi instance. 78 | 79 | """ 80 | 81 | area.replace_ranges('sel', regex, data, exact=False, 82 | regexp=True, nocase=False, elide=False, nolinestop=False) 83 | 84 | Command('gsub')(AreaVi.replace_all) 85 | Command('get')(AreaVi.get) 86 | -------------------------------------------------------------------------------- /vyapp/plugins/cmd_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Just some misc utils. 6 | 7 | Commands 8 | ======== 9 | 10 | Command: cpsel(sep='\n') 11 | Description: Copy selected region of text using sep. 12 | 13 | Command: ctsel(sep='\n') 14 | Description: Cut selected region of text using sep. 15 | 16 | Command: chmode(sep='\n') 17 | Description: Switch modes for an AreaVi instance. 18 | 19 | """ 20 | 21 | from vyapp.plugins import Command 22 | 23 | @Command() 24 | def cpsel(area, sep='\n'): 25 | """ 26 | Copy the selected region to the clipboard. 27 | """ 28 | 29 | area.cpsel(sep) 30 | 31 | @Command() 32 | def ctsel(area, sep='\n'): 33 | """ 34 | Cut the selected region to the clipboard. 35 | """ 36 | area.ctsel(sep) 37 | 38 | @Command() 39 | def chmode(area, id): 40 | """ 41 | Switch modes for an AreaVi instance set as target. 42 | """ 43 | area.chmode(id) 44 | -------------------------------------------------------------------------------- /vyapp/plugins/codec.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements functions to adjust AreaVi widget encoding. 6 | 7 | 8 | Commands 9 | ======== 10 | 11 | Command: charset(name) 12 | Description: Set the default encoding to save files. 13 | name = The name of the encoding. 14 | 15 | Command: decode(name) 16 | Description: Adjust an AreaVi widget text encoding. 17 | name = The name of the encoding. 18 | """ 19 | 20 | from vyapp.plugins import Command 21 | from vyapp.app import root 22 | 23 | @Command() 24 | def decode(area, name): 25 | try: 26 | area.decode(name) 27 | except UnicodeDecodeError: 28 | root.status.set_msg('Failed! Charset %s' % name) 29 | 30 | @Command() 31 | def charset(area, name): 32 | area.charset = name 33 | root.status.set_msg('Charset %s set.' % name) 34 | -------------------------------------------------------------------------------- /vyapp/plugins/count_words.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements word tools. 6 | 7 | Commands 8 | ======== 9 | 10 | Command: cw() 11 | Description: Count the number of words that appear in the AreaVi widget that was 12 | set as command target. The result would appear at the statusbar. 13 | """ 14 | 15 | from vyapp.plugins import Command 16 | from vyapp.app import root 17 | from re import findall 18 | 19 | @Command() 20 | def cw(area): 21 | data = area.get('1.0', 'end') 22 | root.status.set_msg('Count of words:%s' % len(findall('\W+', data))) 23 | -------------------------------------------------------------------------------- /vyapp/plugins/cplusplus_mode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Extra mode for cplusplus programming language. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: cplusplus-mode 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Switch to C++ mode. 15 | """ 16 | 17 | def cplusplus_mode(area): 18 | area.chmode('C++') 19 | 20 | def install(area): 21 | area.add_mode('C++') 22 | area.install('cplusplus-mode', ('NORMAL', '', 23 | lambda event: cplusplus_mode(area))) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /vyapp/plugins/cursor_status.py: -------------------------------------------------------------------------------- 1 | from vyapp.app import root 2 | 3 | class CursorStatus: 4 | def __init__(self, area, timeout=1000): 5 | self.area = area 6 | self.timeout = timeout 7 | self.funcid = None 8 | area.install('cursor-status', 9 | (-1, '', lambda event: self.update()), 10 | (-1, '', lambda event: self.area.after_cancel(self.funcid))) 11 | 12 | def update(self): 13 | """ 14 | It is used to update the line and col statusbar 15 | in TIME interval. 16 | """ 17 | 18 | row, col = self.area.indexref('insert') 19 | root.status.set_line(row) 20 | root.status.set_column(col) 21 | self.funcid = self.area.after(self.timeout, self.update) 22 | 23 | install = CursorStatus 24 | 25 | -------------------------------------------------------------------------------- /vyapp/plugins/data_del.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements keycommands to delete text selection, delete line, delete char. 6 | 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: data-del 12 | 13 | Mode: NORMAL 14 | Event: 15 | Description: Delete selection of text. 16 | 17 | 18 | Mode: NORMAL 19 | Event: 20 | Description: Delete a line where the cursor is on. 21 | 22 | 23 | Mode: NORMAL 24 | Event: 25 | Description: Delete a char from the cursor position. 26 | 27 | """ 28 | 29 | class DataDel: 30 | def __init__(self, area): 31 | area.install('data-del', 32 | ('NORMAL', '', self.del_sel), 33 | ('NORMAL', '', self.del_line), 34 | ('NORMAL', '', self.del_char)) 35 | self.area = area 36 | 37 | def del_line(self, event): 38 | """ 39 | It deletes the cursor line, makes the cursor visible 40 | and adds a separator to the undo stack. 41 | """ 42 | 43 | self.area.edit_separator() 44 | self.area.delete('insert linestart', 'insert +1l linestart') 45 | self.area.see('insert') 46 | 47 | def del_char(self, event): 48 | """ 49 | It deletes a char from the cursor position. 50 | """ 51 | 52 | self.area.edit_separator() 53 | self.area.delete('insert', 'insert +1c') 54 | 55 | def del_sel(self, event): 56 | """ 57 | It deletes all selected text. 58 | """ 59 | self.area.edit_separator() 60 | self.area.swap_ranges('sel', '', '1.0', 'end') 61 | 62 | install = DataDel 63 | 64 | -------------------------------------------------------------------------------- /vyapp/plugins/data_sel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements keycommands to select sequences of chars that 6 | match a special pattern, words, non blank sequences etc. 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: data-sel 12 | 13 | Mode: NORMAL 14 | Event: 15 | Description: Add selection to a line over the cursor. 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Add selection from the cursor position to the beginning of the line. 20 | 21 | Mode: NORMAL 22 | Event: 23 | Description: Add selection from the cursor position to the end of the line. 24 | 25 | Mode: NORMAL 26 | Event: 27 | Description: Add selection to a word where the cursor is placed on. 28 | 29 | Mode: NORMAL 30 | Event: 31 | Description: Select a sequence of non blank chars that is over 32 | the cursor. 33 | 34 | Mode: NORMAL 35 | Event: 36 | Description: Add selection from the cursor positon to the beginning of the file. 37 | 38 | 39 | Mode: NORMAL 40 | Event: 41 | Description: Add selection from the cursor position to the end of the file. 42 | 43 | 44 | Mode: NORMAL 45 | Event: 46 | Description: Add selection from the beginning to the end of the file. 47 | """ 48 | 49 | class DataSel: 50 | def __init__(self, area): 51 | area.install('data-sel', 52 | ('NORMAL', '', self.sel_seq), 53 | ('NORMAL', '', self.sel_word), 54 | ('NORMAL', '', self.sel_text_start), 55 | ('NORMAL', '', self.sel_text_end), 56 | ('NORMAL', '', self.sel_all), 57 | ('NORMAL', '', self.sel_line), 58 | ('NORMAL', '', self.sel_line_start), 59 | ('NORMAL', '', self.sel_line_end)) 60 | self.area = area 61 | 62 | def sel_seq(self, event): 63 | """ 64 | Select the closest sequence of non blank characters from the cursor. 65 | """ 66 | 67 | index1, index2 = self.area.get_seq_range() 68 | self.area.tag_add('sel', index1, index2) 69 | 70 | def sel_word(self, event): 71 | """ 72 | Select the closest word from the cursor. 73 | """ 74 | 75 | index1, index2 = self.area.get_word_range() 76 | self.area.tag_add('sel', index1, index2) 77 | 78 | def sel_all(self, event): 79 | """ 80 | It selects all text. 81 | """ 82 | 83 | self.area.tag_add('sel', '1.0', 'end') 84 | 85 | def sel_text_start(self, event): 86 | """ 87 | It selects all text from cursor position to the start position 88 | of the text. 89 | 90 | """ 91 | 92 | index = self.area.index('insert') 93 | self.area.mark_set('insert', '1.0') 94 | self.area.see('insert') 95 | self.area.addsel(index, 'insert') 96 | 97 | def sel_text_end(self, event): 98 | """ 99 | It selects all text from the cursor position to the end of the text. 100 | """ 101 | 102 | index = self.area.index('insert') 103 | self.area.mark_set('insert', 'end linestart') 104 | self.area.see('insert') 105 | self.area.addsel(index, 'insert') 106 | 107 | def sel_line_start(self, event): 108 | """ 109 | It adds selection from the cursor position to the 110 | start of the line. 111 | """ 112 | 113 | index = self.area.index('insert') 114 | self.area.mark_set('insert', 'insert linestart') 115 | self.area.addsel(index, 'insert') 116 | 117 | def sel_line_end(self, event): 118 | """ 119 | It selects all text from the cursor position to the end of the line. 120 | """ 121 | 122 | index = self.area.index('insert') 123 | self.area.mark_set('insert', 'insert lineend') 124 | self.area.addsel(index, 'insert') 125 | 126 | def sel_line(self, event): 127 | """ 128 | Toggle line selection. 129 | """ 130 | 131 | self.area.toggle_range('sel', 132 | 'insert linestart', 'insert +1l linestart') 133 | 134 | install = DataSel 135 | 136 | 137 | -------------------------------------------------------------------------------- /vyapp/plugins/deadcode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Offers syntax checking for python with vulture. 6 | 7 | Extern dependencies: 8 | vulture 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Namespace: deadcode 14 | 15 | Mode: PYTHON 16 | 17 | 18 | Mode: PYTHON 19 | Event: 20 | Description: Show previous Vulture reports. 21 | 22 | Event: 23 | Description: Highlight all lines which were reported 24 | by vulture on the current file. 25 | 26 | Mode: PYTHON 27 | Event: 28 | Description: Highlight all lines which were reported 29 | by vulture on all files. 30 | 31 | Commands 32 | ======== 33 | 34 | Command: py_analysis() 35 | Description: Run vulture over the current file and highlighs 36 | all incoherent code. It is possible to jump upwards/downwards 37 | using the same keys as defined in text_spots plugin. 38 | 39 | """ 40 | 41 | from vyapp.plugins import Command 42 | from subprocess import Popen, STDOUT, PIPE 43 | from os.path import relpath 44 | from vyapp.widgets import LinePicker 45 | from vyapp.tools import get_project_root 46 | from vyapp.stderr import printd 47 | from vyapp.app import root 48 | from re import findall 49 | import sys 50 | 51 | class PythonAnalysis: 52 | options = LinePicker() 53 | path = 'vulture' 54 | 55 | def __init__(self, area): 56 | self.area = area 57 | area.install('deadcode', ('PYTHON', '', self.check_module), 58 | ('PYTHON', '', lambda event: self.options.display()), 59 | ('PYTHON', '', self.check_all)) 60 | 61 | @classmethod 62 | def c_path(cls, path): 63 | printd('Deadcode - Setting Vulture path = ', cls.path) 64 | cls.path = path 65 | 66 | def check_all(self, event=None): 67 | path = get_project_root(self.area.filename) 68 | child = Popen([self.path, path], 69 | stdout=PIPE, stderr=STDOUT, encoding=self.area.charset) 70 | output = child.communicate()[0] 71 | 72 | regex = '(.+):([0-9]+):?[0-9]*:(.+)' 73 | ranges = findall(regex, output) 74 | sys.stdout.write('Vulture found global errors:\n%s\n' % output) 75 | self.area.chmode('NORMAL') 76 | 77 | root.status.set_msg('Vulture errors: %s' % len(ranges)) 78 | if ranges: 79 | self.options(ranges) 80 | 81 | def check_module(self, event=None): 82 | path = get_project_root(self.area.filename) 83 | child = Popen([self.path, path], 84 | stdout=PIPE, stderr=STDOUT, encoding=self.area.charset) 85 | output = child.communicate()[0] 86 | 87 | regex = '(%s):([0-9]+):?[0-9]*:(.+)' % relpath(self.area.filename) 88 | ranges = findall(regex, output) 89 | 90 | sys.stdout.write('%s errors:\n%s\n' % (self.area.filename, output)) 91 | self.area.chmode('NORMAL') 92 | 93 | root.status.set_msg('Vulture errors: %s' % len(ranges)) 94 | if ranges: 95 | self.options(ranges) 96 | 97 | install = PythonAnalysis 98 | @Command() 99 | def py_analysis(area): 100 | python_analysis = PythonAnalysis(area) 101 | python_analysis.check_all() 102 | 103 | -------------------------------------------------------------------------------- /vyapp/plugins/editrc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Quickly edit your vyrc file. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Mode: EXTRA 11 | Event: 12 | Description: Load your ~/.vy/vyrc file in the current 13 | AreaVi instance. 14 | 15 | """ 16 | 17 | from vyapp.app import root 18 | 19 | def loadrc(event): 20 | event.widget.load_data(root.rc) 21 | event.widget.chmode('NORMAL') 22 | 23 | def install(area): 24 | area.install('editrc', ('EXTRA', '', loadrc)) 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /vyapp/plugins/fsearch.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Run locate command and drop output on the current AreaVi instance. 6 | The locate comand is run with lax like regex. 7 | 8 | The main difference from fsniffer it consists of fsearch displaying 9 | also dirs and being useful with mc plugin. 10 | 11 | Key-Commands 12 | ============ 13 | 14 | Namespace: fsearch 15 | 16 | Mode: NORMAL 17 | Event: 18 | Description: Insert locale command output in the current AreaVi instance. 19 | 20 | 21 | Mode: NORMAL 22 | Event: 23 | Description: Ask for a filename pattern to be located using unix locate command. 24 | 25 | """ 26 | 27 | from vyapp.regutils import build_regex 28 | from subprocess import Popen, STDOUT, PIPE 29 | from vyapp.ask import Get 30 | from vyapp.app import root 31 | 32 | 33 | class FSearch: 34 | def __init__(self, area): 35 | self.area = area 36 | self.output = '' 37 | 38 | area.install('fsearch', 39 | ('NORMAL', '', self.display), 40 | ('NORMAL', '', lambda event: Get(events={ 41 | '' : self.find, '<>': self.update_pattern, 42 | '': lambda wid: True}))) 43 | 44 | def display(self, event): 45 | self.area.swap(self.output, '1.0', 'end') 46 | root.status.set_msg('Previous located files.') 47 | 48 | def update_pattern(self, wid): 49 | pattern = build_regex(wid.get(), '.*') 50 | root.status.set_msg('File pattern: %s' % pattern) 51 | 52 | def run_cmd(self, pattern): 53 | cmd = ['locate', '--limit', '200'] 54 | regex = build_regex(pattern, '.*') 55 | cmd.extend(['--regexp', regex]) 56 | 57 | child = Popen(cmd, stdout=PIPE, stderr=STDOUT, 58 | encoding=self.area.charset) 59 | 60 | output = child.communicate()[0] 61 | return output 62 | 63 | def find(self, wid): 64 | pattern = wid.get() 65 | self.output = self.run_cmd(pattern) 66 | self.area.swap(self.output, '1.0', 'end') 67 | root.status.set_msg('Locate results: %s' % self.output.count('\n')) 68 | return True 69 | 70 | install = FSearch 71 | -------------------------------------------------------------------------------- /vyapp/plugins/fsniffer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Quickly open files in vy by using locate command. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: fsniffer 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Ask for a filename pattern to be located using unix locate command. 15 | 16 | 17 | Mode: INPUT 18 | Event: 19 | Description: Display possible file matches on a line picker widget. 20 | 21 | Mode: INPUT 22 | Event: 23 | Description: Set wide search. In wide search the locate command will 24 | search for files in the whole file system. When an AreaVi instance has 25 | no project path set then wide search as false will have no efect. 26 | When wide search is False and project path is set then it searches in the 27 | current file project dirs. 28 | 29 | """ 30 | 31 | from vyapp.regutils import build_regex 32 | from subprocess import Popen, STDOUT, PIPE 33 | from vyapp.widgets import LinePicker 34 | from vyapp.ask import Get 35 | from vyapp.app import root 36 | from os.path import basename 37 | 38 | 39 | class FSniffer: 40 | options = LinePicker() 41 | wide = True 42 | 43 | def __init__(self, area): 44 | self.area = area 45 | area.install('fsniffer', 46 | ('NORMAL', '', lambda e: self.options.display()), 47 | ('NORMAL', '', lambda event: Get(events={'' : self.find, 48 | '':self.set_wide, '<>': self.update_pattern, 49 | '': lambda wid: True}))) 50 | 51 | @classmethod 52 | def set_wide(cls, event): 53 | FSniffer.wide = False if FSniffer.wide else True 54 | root.status.set_msg('Set wide search: %s' % FSniffer.wide) 55 | 56 | def update_pattern(self, wid): 57 | pattern = build_regex(wid.get(), '.*') 58 | root.status.set_msg('File pattern: %s' % pattern) 59 | 60 | def make_cmd(self, pattern): 61 | # When FSniffer.wide is False it searches in the current 62 | # Areavi instance project. 63 | cmd = ['locate', '--limit', '200'] 64 | regex = build_regex(pattern, '.*') 65 | 66 | if self.wide or not self.area.project: 67 | cmd.extend(['--regexp', regex]) 68 | else: 69 | cmd.extend(['--regexp', '%s.*%s' % ( 70 | self.area.project, regex)]) 71 | 72 | # Used to filter only files because locate doesn't support 73 | # searching only for files. 74 | cmd = '%s | %s' % (' '.join(cmd), '''while read -r file; do 75 | [ -d "$file" ] || printf '%s\n' "$file"; done''') 76 | return cmd 77 | 78 | def run_cmd(self, pattern): 79 | cmd = self.make_cmd(pattern) 80 | child = Popen(cmd, stdout=PIPE, stderr=STDOUT, 81 | encoding=self.area.charset, shell=True) 82 | 83 | output = child.communicate()[0] 84 | return output 85 | 86 | def find(self, wid): 87 | pattern = wid.get() 88 | output = self.run_cmd(pattern) 89 | if output: 90 | self.fmt_output(output) 91 | else: 92 | root.status.set_msg('No results:%s!' % pattern) 93 | return True 94 | 95 | def fmt_output(self, output): 96 | output = output.strip('\n').rstrip('\n') 97 | ranges = output.split('\n') 98 | ranges = [ind for ind in ranges 99 | if ranges] 100 | 101 | ranges = [(ind, '0', basename(ind)) for ind in ranges] 102 | 103 | self.options(ranges) 104 | 105 | install = FSniffer 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /vyapp/plugins/fstmt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Find where patterns are found, this plugin uses silver searcher to search 6 | for word patterns. It is useful to find where functions/methods 7 | are used over multiple files. 8 | 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Namespace: fstmt 14 | 15 | Mode: NORMAL 16 | Event: 17 | Description: Same as but matches insensitively. 18 | 19 | Mode: NORMAL 20 | Event: 21 | Description: Open the previous found pattern occurrences. 22 | 23 | Mode: NORMAL 24 | Event: 25 | Description: Get the string under the cursor and perform 26 | a case sensitive and resursive search in the current project file directory. 27 | It grabs the string under the cursor only if there is no selected text. 28 | 29 | The search is performed in the current project folder, if fstmt cant find a .git, svn 30 | nor .hg it performs the search in the vy HOME directory. 31 | 32 | """ 33 | 34 | from subprocess import Popen, STDOUT, PIPE 35 | from vyapp.widgets import LinePicker 36 | from vyapp.areavi import AreaVi 37 | from re import findall, escape 38 | from vyapp.stderr import printd 39 | from vyapp.app import root 40 | 41 | class Fstmt: 42 | options = LinePicker() 43 | path = 'ag' 44 | 45 | def __init__(self, area): 46 | self.area = area 47 | 48 | area.install('fstmt', 49 | ('NORMAL', '', lambda event: self.options.display()), 50 | ('NORMAL', '', lambda event: self.picker('-i')), 51 | ('NORMAL', '', lambda event: self.picker('-s'))) 52 | 53 | @classmethod 54 | def c_path(cls, path='ag'): 55 | cls.path = path 56 | printd('Fstmt - Setting ag path = ', path) 57 | 58 | def catch_pattern(self): 59 | pattern = self.area.join_ranges('sel') 60 | pattern = pattern if pattern else self.area.get( 61 | *self.area.get_word_range()) 62 | 63 | pattern = escape(pattern) 64 | return pattern 65 | 66 | def make_cmd(self, pattern, dir, *args): 67 | cmd = [Fstmt.path, '--nocolor', '--nogroup', 68 | '--vimgrep', '--noheading'] 69 | cmd.extend(args) 70 | cmd.extend([pattern, dir]) 71 | return cmd 72 | 73 | def run_cmd(self, pattern, *args): 74 | dir = self.area.project 75 | dir = dir if dir else AreaVi.HOME 76 | dir = dir if dir else self.area.filename 77 | child = Popen(self.make_cmd(pattern, dir, *args), stdout=PIPE, 78 | stderr=STDOUT, encoding=self.area.charset) 79 | regex = '(.+):([0-9]+):[0-9]+:(.+)' 80 | ranges = findall(regex, child.communicate()[0]) 81 | 82 | if ranges: 83 | self.options(ranges) 84 | else: 85 | root.status.set_msg('No pattern found!') 86 | 87 | def picker(self, *args): 88 | pattern = self.catch_pattern() 89 | if not pattern: 90 | root.status.set_msg('No pattern set!') 91 | else: 92 | self.run_cmd(pattern, *args) 93 | 94 | 95 | -------------------------------------------------------------------------------- /vyapp/plugins/gdb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module provides functionalities to work with GDB. It allows 6 | watching the flow of the application being debugged, setting breakpoints, 7 | removing breakpoints with keystrokes. 8 | 9 | Key-Commands 10 | ============ 11 | 12 | Namespace: GDB 13 | 14 | Mode: C 15 | Event: 16 | Description: Ask for a compiled file to be debugged. It is necessary to have it compiled with -g flag. 17 | Example: 18 | 19 | gcc -g filename.c -o filename.out 20 | 21 | Mode: C 22 | Event: 23 | Description: Send a (c)ontinue to the debug process. 24 | Continue execution, only stop when a breakpoint is encountered. 25 | 26 | Mode: C 27 | Event: 28 | Description: Set a break point at the cursor line. 29 | 30 | Mode: C 31 | Event: 32 | Description: Send a run to start/restart from the beginning. 33 | For running with arguments just use: 34 | 35 | 36 | 37 | Then: 38 | 39 | run arg0 arg1 ... 40 | 41 | Mode: C 42 | Event: 43 | Description: Remove break point that is set at the cursor line. 44 | 45 | Mode: C 46 | Event: 47 | Description: Evaluate selected text. 48 | 49 | Mode: C 50 | Event: 51 | Description: Send a GDB command to be executed. 52 | 53 | Mode: C 54 | Event? 55 | Description: Ask for expression to be sent/evaluated. 56 | 57 | Mode: C 58 | Event: 59 | Description: Terminate the process. 60 | 61 | """ 62 | 63 | from tkinter.filedialog import askopenfilename 64 | from untwisted.splits import Terminator 65 | from vyapp.regutils import RegexEvent 66 | from vyapp.dap import DAP 67 | from vyapp.ask import Ask 68 | from vyapp.app import root 69 | import shlex 70 | 71 | class GDB(DAP): 72 | def __call__(self, area): 73 | self.area = area 74 | 75 | area.install('GDB', 76 | ('C', '', self.evaluate_selection), 77 | ('C', '', self.ask_gdb_exec), 78 | ('C', '', self.run), 79 | ('C', '', self.evaluate_expression), 80 | ('C', '', self.quit_db), 81 | ('C', '', self.send_continue), 82 | ('C', '', self.send_dcmd), 83 | ('C', '', self.clear_breakpoint), 84 | ('C', '', self.send_break)) 85 | 86 | def evaluate_expression(self, event): 87 | ask = Ask() 88 | 89 | self.send("print %s\r\n" % ask.data) 90 | root.status.set_msg('(GDB) Sent expression!') 91 | 92 | def run(self, event): 93 | self.send('run\r\n') 94 | root.status.set_msg('(GDB) Sent run!') 95 | 96 | def send_dcmd(self, event): 97 | ask = Ask() 98 | self.send('%s\r\n' % ask.data) 99 | root.status.set_msg('(GDB) Sent cmd!') 100 | 101 | def evaluate_selection(self, event): 102 | data = event.widget.join_ranges('sel', sep='\r\n') 103 | self.send('print %s' % data) 104 | event.widget.chmode('NORMAL') 105 | root.status.set_msg('(GDB) Sent selection !') 106 | 107 | def install_handles(self, device): 108 | Terminator(device, delim=b'\n') 109 | 110 | regstr0 = '\032\032(.+):([0-9]+):[0-9]+:.+:.+' 111 | RegexEvent(device, regstr0, 'LINE', self.encoding) 112 | device.add_map('LINE', self.handle_line) 113 | 114 | def ask_gdb_exec(self, event): 115 | root.status.set_msg('(GDB) Select a compiled file:') 116 | filename = askopenfilename() 117 | if filename: 118 | self.init_gdb(filename) 119 | event.widget.chmode('NORMAL') 120 | 121 | def init_gdb(self, filename): 122 | self.kill_process() 123 | self.create_process(shlex.split('gdb -f %s' % filename)) 124 | root.status.set_msg('(GDB) Started: %s' % filename) 125 | 126 | def send_break(self, event): 127 | line, col = event.widget.indexref('insert') 128 | 129 | # Make sure the name will be unique for removing it later. 130 | self.send('break %s:%s\r\n' % (event.widget.filename, line)) 131 | 132 | event.widget.chmode('NORMAL') 133 | root.status.set_msg('(GDB) Sent breakpoint !') 134 | 135 | def send(self, data): 136 | self.expect.send(data.encode(self.encoding)) 137 | print('GDB Cmd: ', data) 138 | 139 | def send_continue(self, event): 140 | """ 141 | """ 142 | 143 | self.send('continue\r\n') 144 | root.status.set_msg('(GDB) Sent continue !') 145 | 146 | def clear_breakpoint(self, event): 147 | """ 148 | """ 149 | 150 | line, col = event.widget.indexref('insert') 151 | self.send('clear %s:%s\r\n' % (event.widget.filename, line)) 152 | 153 | event.widget.chmode('NORMAL') 154 | root.status.set_msg('(GDB) Sent clear !') 155 | 156 | GDB = GDB() 157 | install = GDB 158 | 159 | 160 | -------------------------------------------------------------------------------- /vyapp/plugins/gists.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin is used to create gists on github. 6 | 7 | Commands 8 | ======== 9 | 10 | Command: cgist() 11 | Description: Create public gist. 12 | 13 | Command: rgist() 14 | Description: Create private gist. 15 | 16 | """ 17 | 18 | from vyapp.plugins import Command, ENV 19 | from os.path import basename 20 | import webbrowser 21 | import requests 22 | import json 23 | 24 | class GistCreate: 25 | GITHUB_API="https://api.github.com/gists" 26 | 27 | def __init__(self, api_token): 28 | self.api_token = api_token 29 | 30 | self.headers = { 31 | 'Authorization':'token %s' % api_token} 32 | 33 | ENV['cgist'] = self.cgist 34 | ENV['rgist'] = self.rgist 35 | 36 | def rgist(self, description=''): 37 | """ 38 | Create private gist. 39 | """ 40 | self.create(description, False) 41 | 42 | def cgist(self, description=''): 43 | """ 44 | Create public gist. 45 | """ 46 | self.create(description, True) 47 | 48 | def create(self, description='', public=False,): 49 | data = Command.area.get('1.0', 'end') 50 | payload = {"description": description, "public":True, "files": { 51 | basename(Command.area.filename): {'content': data}}} 52 | params = {'scope':'gist'} 53 | 54 | req = requests.post(self.GITHUB_API, headers=self.headers, 55 | params=params, data=json.dumps(payload)) 56 | 57 | rsp = json.loads(req.text) 58 | print('Gist URL:', rsp['html_url']) 59 | webbrowser.open(rsp['html_url']) 60 | 61 | -------------------------------------------------------------------------------- /vyapp/plugins/gohints.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements golang autocompletion using the gocode 6 | daemon. 7 | 8 | Github: https://github.com/nsf/gocode 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Namespace: gohints 14 | 15 | Mode: INSERT 16 | Event: 17 | Description: Open the completion window with possible golang words for 18 | completion. 19 | """ 20 | 21 | from vyapp.completion import CompletionWindow, Option 22 | from subprocess import Popen, PIPE 23 | import json 24 | import sys 25 | 26 | class GolangCompletionWindow(CompletionWindow): 27 | """ 28 | """ 29 | 30 | def __init__(self, area, *args, **kwargs): 31 | self.area = area 32 | tmp0 = area.get('1.0', 'insert') 33 | tmp1 = area.get('insert', 'end') 34 | source = tmp0 + tmp1 35 | offset = len(tmp0) 36 | 37 | completions = self.completions(source, offset, area.filename) 38 | CompletionWindow.__init__(self, area, completions, *args, **kwargs) 39 | 40 | self.bind('', lambda event: sys.stdout.write('%s\n%s\n' % ( 41 | '#' * 80, self.box.selection_docs()))) 42 | 43 | def completions(self, data, offset, filename): 44 | daemon = Popen('%s -f=json autocomplete %s %s' % (GolangCompletion.PATH, 45 | self.area.filename, offset), shell=1, stdin=PIPE, stdout=PIPE, stderr=PIPE, encoding=self.area.charset) 46 | stdout, stderr = daemon.communicate(data) 47 | return self.build(stdout) 48 | 49 | def build(self, data): 50 | data = json.loads(data) 51 | return [Option(ind['name'], 'Type:%s' % ind['type'], 52 | 'Class:%s' % ind['class']) for ind in data[1]] 53 | 54 | class GolangCompletion: 55 | PATH = 'gocode' 56 | 57 | def __init__(self, area): 58 | trigger = lambda event: area.hook('gohints', 'INSERT', '', 59 | lambda event: GolangCompletionWindow(event.widget), add=False) 60 | 61 | remove_trigger = lambda event: area.unhook('INSERT', '') 62 | area.install('gohints', (-1, '<>', trigger), 63 | (-1, '<>', trigger), (-1, '<>', remove_trigger), 64 | (-1, '<>', remove_trigger)) 65 | 66 | install = GolangCompletion 67 | 68 | -------------------------------------------------------------------------------- /vyapp/plugins/golang_mode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Extra mode for golang programming language. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: golang-mode 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Switch to GOLANG mode. 15 | """ 16 | 17 | def golang_mode(area): 18 | area.chmode('GOLANG') 19 | 20 | def install(area): 21 | area.add_mode('GOLANG') 22 | area.install('golang-mode', ('NORMAL', '', 23 | lambda event: golang_mode(area))) 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /vyapp/plugins/hlink.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements functionalities to highligh text that maps to URL links. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Mode: -1 11 | Event: <> 12 | Description: Highligh all URLS that appear in the file text. 13 | """ 14 | 15 | TAG_LINK = '_link_' 16 | REG_LINK = 'https?\:\/\/[^ ]+' 17 | 18 | def hlink(area): 19 | """ 20 | It highlighes links. 21 | 22 | """ 23 | 24 | seq = area.find(REG_LINK, '1.0', 'end') 25 | 26 | for (_, pos0, pos1) in seq: 27 | area.tag_add(TAG_LINK, pos0, pos1) 28 | 29 | 30 | 31 | def install(area, setup={'background':'yellow', 'foreground':'blue'}): 32 | area.tag_config(TAG_LINK, **setup) 33 | area.install((-1, '<>', lambda event: hlink(event.widget))) 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /vyapp/plugins/home.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Plugins that rely on the attribute project of AreaVi instances 6 | should use AreaVi.HOME as last resource of information. 7 | """ 8 | 9 | from vyapp.areavi import AreaVi 10 | from vyapp.app import root 11 | from vyapp.ask import Ask 12 | 13 | class Home: 14 | def __init__(self, area): 15 | self.area = area 16 | area.install('home', 17 | ('NORMAL', '', self.set_home)) 18 | 19 | def set_home(self, event): 20 | """ 21 | Set the AreaVi home dir. 22 | """ 23 | 24 | root.status.set_msg('Home dir:') 25 | ask = Ask(self.area.filename) 26 | 27 | AreaVi.HOME = ask.data 28 | root.status.set_msg('AreaVi HOME: %s' % AreaVi.HOME) 29 | 30 | install = Home 31 | -------------------------------------------------------------------------------- /vyapp/plugins/html_mode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Extra mode for dealing with HTML. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: html-mode 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Switch to HTML mode. 15 | """ 16 | 17 | def html_mode(area): 18 | area.chmode('HTML') 19 | 20 | def install(area): 21 | area.add_mode('HTML') 22 | area.install('html-mode', ('NORMAL', '', 23 | lambda event: html_mode(area))) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /vyapp/plugins/ibash.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin is used to control a bash process. It is possible to run commands 6 | and start processes through bash and send some unix signals to the child processes. 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: ibash 12 | 13 | Mode: NORMAL 14 | Event: 15 | Description: Send a SIGINT signal to the bash interpreter. 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Send the cursor line to the bash interpreter. 20 | 21 | Mode: NORMAL 22 | Event: 23 | Description: Send the region of code that is selected to the bash interpreter. 24 | 25 | Mode: NORMAL 26 | Event: 27 | Description: Ask for the user to type a command to be dropped to the bash interpreter. 28 | 29 | Commands 30 | ======== 31 | 32 | Command: lsh 33 | Description: Restart the underlying bash process. 34 | 35 | """ 36 | 37 | 38 | from untwisted.network import Device 39 | from vyapp.app import root 40 | from untwisted.event import CLOSE, LOAD 41 | from untwisted.file_reader import FileReader 42 | from untwisted.file_writer import FileWriter 43 | from vyapp.stderr import printd 44 | from vyapp.ask import Ask 45 | from subprocess import Popen, PIPE, STDOUT 46 | from os import environ, setsid, killpg 47 | from vyapp.plugins import ENV 48 | import sys 49 | 50 | class Process: 51 | def __call__(self, area): 52 | area.install('ibash', 53 | ('NORMAL', '', lambda event: self.dump_region(event.widget)), 54 | ('NORMAL', '', lambda event: self.dump_line(event.widget)), 55 | ('NORMAL', '', lambda event: self.ask_data_and_dump(event.widget)), 56 | ('NORMAL', '', self.dump_signal)) 57 | ENV['lsh'] = self.restart 58 | 59 | def __init__(self, cmd=['bash', '-i']): 60 | self.cmd = cmd 61 | self.start() 62 | 63 | def start(self): 64 | printd('(ibash) Bash process started...') 65 | self.child = Popen(self.cmd, shell=0, stdout=PIPE, stdin=PIPE, 66 | preexec_fn=setsid, stderr=STDOUT, env=environ) 67 | 68 | 69 | self.stdout = Device(self.child.stdout) 70 | self.stdin = Device(self.child.stdin) 71 | 72 | FileReader(self.stdout) 73 | FileWriter(self.stdin) 74 | 75 | self.stdout.add_map(LOAD, lambda con, data: sys.stdout.write(data.decode('utf8'))) 76 | self.stdin.add_map(CLOSE, lambda dev, err: lose(dev)) 77 | self.stdout.add_map(CLOSE, lambda dev, err: lose(dev)) 78 | 79 | def restart(self): 80 | """ 81 | Restart ibash process. 82 | """ 83 | 84 | self.child.kill() 85 | self.start() 86 | 87 | root.status.set_msg('(ibash) Process killed and started !') 88 | 89 | def dump_tab(self, area): 90 | data = area.get('insert linestart', 'insert -1c lineend') 91 | data = data.encode('utf-8') 92 | self.stdin.dump('%s\t\t' % data) 93 | 94 | def dump_region(self, area): 95 | data = area.join_ranges('sel') 96 | data = data.encode('utf-8') 97 | self.stdin.dump(data) 98 | root.status.set_msg('(ibash) Executed region!') 99 | 100 | def dump_line(self, area): 101 | data = area.get('insert linestart', 'insert +1l linestart') 102 | data = data.encode('utf-8') 103 | self.stdin.dump(data) 104 | area.down() 105 | root.status.set_msg('(ibash) Executed line!') 106 | 107 | def ask_data_and_dump(self, area): 108 | root.status.set_msg('(ibash) Type a command:') 109 | ask = Ask() 110 | 111 | self.stdin.dump(b'%s\n' % ask.data.encode('utf-8')) 112 | root.status.set_msg('(ibash) Executed command!') 113 | 114 | def dump_signal(self, event): 115 | root.status.set_msg('(ibash) Signal number SIGINT(2)/SIGQUIT(3):') 116 | ask = Ask() 117 | signal = None 118 | 119 | try: 120 | signal = int(ask.data) 121 | except ValueError as e: 122 | root.status.set_msg('(ibash) Invalid signal') 123 | killpg(self.child.pid, signal) 124 | 125 | process = Process() 126 | install = process 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /vyapp/plugins/inline_comment.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements Key-Commands to comment and uncomment blocks of code. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: inline-comment 11 | 12 | Mode: EXTRA 13 | Event: 14 | Description: Add inline comments to a selected block of text. 15 | 16 | Mode: EXTRA 17 | Event: 18 | Description: Remove inline comments from a selected block of text. 19 | 20 | """ 21 | 22 | import os.path 23 | 24 | table = { 25 | '.py' :'#', 26 | '.java' :'//', 27 | '.c' :'//', 28 | '.sh' :'#', 29 | '.js' :'//', 30 | '.cpp' :'//', 31 | '.html' :'//', 32 | '.go' :'//', 33 | '.rb' :'#', 34 | } 35 | 36 | class Clipboard: 37 | default = '#' 38 | 39 | def __init__(self, area): 40 | self.area = area 41 | 42 | area.install('inline-comment', 43 | ('EXTRA', '', self.remove_comment), 44 | ('EXTRA', '', self.add_comment)) 45 | 46 | def add_comment(self, event): 47 | """ 48 | It adds inline comment to selected lines based on the file extesion. 49 | """ 50 | 51 | comment = table.get(os.path.splitext(self.area.filename)[1], self.default) 52 | self.area.replace_ranges('sel', '^ *|^\t*', 53 | lambda data, index0, index1: '%s%s ' % (data, comment)) 54 | self.area.clear_selection() 55 | self.area.chmode('NORMAL') 56 | 57 | def remove_comment(self, event): 58 | """ 59 | It removes the inline comments. 60 | """ 61 | 62 | comment = table.get(os.path.splitext(self.area.filename)[1], self.default) 63 | self.area.replace_ranges('sel', '^ *%s ?|^\t*%s ?' % (comment, comment), 64 | lambda data, index0, index1: data.replace( 65 | '%s ' % comment, '').replace(comment, '')) 66 | self.area.clear_selection() 67 | self.area.chmode('NORMAL') 68 | 69 | install = Clipboard -------------------------------------------------------------------------------- /vyapp/plugins/io.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements basic Key-Commands to open/save files. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: io 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: It pops a file selection window to load the contents of a file. 15 | 16 | Mode: NORMAL 17 | Event: 18 | Description: It saves the content of the AreaVi instance into the opened file. 19 | 20 | Mode: NORMAL 21 | Event: 22 | Description: It pops a save file dialog to save the contents of the active AreaVi 23 | instance with a different filename. 24 | 25 | Mode: NORMAL 26 | Event: 27 | Description: Dump the contents of a file whose path is under the cursor line. 28 | 29 | Mode: NORMAL 30 | Event: 31 | Description: Rename the current AreaVi file. 32 | 33 | Mode: NORMAL 34 | Event: 35 | Description: Save and quit. 36 | 37 | """ 38 | 39 | # from tkinter.messagebox import * 40 | from tkinter.filedialog import askopenfilename, asksaveasfilename 41 | from vyapp.app import root 42 | from vyapp.ask import Ask 43 | import os 44 | 45 | class IO: 46 | def __init__(self, area): 47 | self.area = area 48 | 49 | area.install('io', 50 | ('NORMAL', '', self.save), 51 | ('NORMAL', '', self.save_as), 52 | ('NORMAL', '', self.ask_and_load), 53 | ('NORMAL', '', self.rename), 54 | ('NORMAL', '', self.load_path), 55 | ('NORMAL', '', self.save_quit)) 56 | 57 | def save_as(self, event): 58 | """ 59 | It pops a asksaveasfilename window to save the contents of 60 | the text area. 61 | """ 62 | 63 | filename = asksaveasfilename() 64 | 65 | if not filename: 66 | return 67 | 68 | try: 69 | self.area.save_data_as(filename) 70 | except Exception: 71 | root.status.set_msg('It failed to save data.') 72 | else: 73 | root.status.set_msg('Data saved.') 74 | 75 | 76 | def save_quit(self, event): 77 | """ 78 | It saves the contents of the text area then quits. 79 | """ 80 | 81 | try: 82 | self.area.save_data() 83 | except Exception: 84 | root.status.set_msg('It failed to save data.') 85 | else: 86 | self.area.quit() 87 | 88 | def ask_and_load(self, event): 89 | """ 90 | It pops a askopenfilename to find a file to drop 91 | the contents in the focused text area. 92 | """ 93 | 94 | filename = askopenfilename() 95 | 96 | # If i don't check it ends up cleaning up 97 | # the text area when one presses cancel. 98 | 99 | if not filename: 100 | return 101 | 102 | try: 103 | self.area.load_data(filename) 104 | except Exception: 105 | root.status.set_msg('It failed to load.') 106 | else: 107 | root.status.set_msg('File loaded.') 108 | 109 | 110 | def save(self, event): 111 | """ 112 | It just saves the text area contents into the 113 | actual opened file. 114 | """ 115 | 116 | try: 117 | self.area.save_data() 118 | except Exception: 119 | root.status.set_msg('It failed to save data.') 120 | else: 121 | root.status.set_msg('Data saved.') 122 | 123 | def rename(self, event): 124 | """ 125 | Rename the current AreaVi instance file. 126 | """ 127 | 128 | root.status.set_msg('Type a filename:') 129 | 130 | ask = Ask() 131 | dir = os.path.dirname(self.area.filename) 132 | dst = os.path.join(dir, ask.data) 133 | 134 | try: 135 | os.rename(self.area.filename, dst) 136 | except OSError: 137 | root.status.set_msg('Failed to rename!') 138 | else: 139 | self.area.filename = dst 140 | root.status.set_msg('File renamed') 141 | 142 | def load_path(self, event): 143 | """ 144 | Dump the contents of the file whose path is under the cursor. 145 | """ 146 | 147 | filename = self.area.get_line() 148 | root.note.load([[filename]]) 149 | 150 | 151 | install = IO -------------------------------------------------------------------------------- /vyapp/plugins/io_status.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Update statusbar when a given AreaVi instance load/save data from disk. 6 | It also updates the statusbar mode field. 7 | """ 8 | 9 | from vyapp.app import root 10 | from os.path import basename 11 | 12 | class IOStatus: 13 | def __init__(self, area): 14 | self.area = area 15 | area.install('io-status', 16 | (-1, '', self.clear_statusbar), 17 | (-1, '<>', self.update_title), 18 | (-1, '<>', self.update_title), 19 | (-1, '<>', self.update_title), 20 | (-1, '', self.update_title), 21 | (-1, '<>', self.update_tabname), 22 | (-1, '<>', self.update_tabname), 23 | (-1, '<>', self.update_tabname), 24 | (-1, '', self.update_tabname), 25 | (-1, '', self.update_mode), 26 | (-1, '<>', self.update_mode)) 27 | 28 | def clear_statusbar(self, event): 29 | root.status.set_msg('') 30 | 31 | def update_title(self, event): 32 | root.title('Vy %s' % self.area.filename) 33 | 34 | def update_tabname(self, event): 35 | root.note.tab(self.area.master.master.master, 36 | text=basename(self.area.filename)) 37 | 38 | def update_mode(self, event): 39 | root.status.set_mode(self.area.id) 40 | 41 | install = IOStatus 42 | 43 | -------------------------------------------------------------------------------- /vyapp/plugins/iocmd.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements Commands to save file and load files into AreaVi instances. 6 | 7 | Commands 8 | ======== 9 | 10 | Command: ss(filename) 11 | Description: It dumps the AreaVi Command target's content into a file whose name is 12 | specified. 13 | filename = The name of the file. 14 | 15 | Command: lo(filename) 16 | Description: Load the contents of filename into the AreaVi Command target. 17 | """ 18 | 19 | from vyapp.plugins import Command 20 | from vyapp.app import root 21 | 22 | @Command('s') 23 | def save(area): 24 | """ 25 | Save the contents of the targeted areavi to disk. 26 | """ 27 | area.save_data() 28 | root.status.set_msg('File saved!') 29 | 30 | @Command('q') 31 | def quit(area): 32 | """ 33 | """ 34 | 35 | root.quit() 36 | 37 | @Command('ss') 38 | def save_as(area, filename): 39 | """ 40 | """ 41 | 42 | area.save_data_as(filename) 43 | root.status.set_msg('File saved as %s!' % filename) 44 | 45 | @Command('lo') 46 | def load_split(area, filename): 47 | """ 48 | """ 49 | 50 | area.load_data(filename) 51 | root.status.set_msg('Loaded %s' % filename) 52 | 53 | @Command('to') 54 | def load_tab(area, filename): 55 | """ 56 | """ 57 | 58 | root.note.load([[filename]]) 59 | root.status.set_msg('Loaded %s' % filename) 60 | 61 | @Command('vsplit') 62 | def vsplit(area): 63 | """ 64 | """ 65 | area.master.master.master.create() 66 | 67 | @Command('hsplit') 68 | def hsplit(area): 69 | """ 70 | """ 71 | area.master.master.create() 72 | -------------------------------------------------------------------------------- /vyapp/plugins/javascript_mode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Extra mode for javascript programming language. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: javascript-mode 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Switch to PYTHON mode. 15 | """ 16 | 17 | def javascript_mode(area): 18 | area.chmode('JAVASCRIPT') 19 | 20 | def install(area): 21 | area.add_mode('JAVASCRIPT') 22 | area.install('javascript-mode', ('NORMAL', '', 23 | lambda event: javascript_mode(area))) 24 | 25 | 26 | -------------------------------------------------------------------------------- /vyapp/plugins/jedi.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin does autocompletion using jedi library. 6 | 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: python-completion 12 | 13 | Mode: INSERT 14 | Event: 15 | Description: Open the completion window with possible python words for 16 | completion. 17 | 18 | """ 19 | 20 | from vyapp.completion import CompletionWindow 21 | from jedi import Script 22 | from vyapp.plugins import Command 23 | 24 | class PythonCompletionWindow(CompletionWindow): 25 | """ 26 | """ 27 | 28 | def __init__(self, area, *args, **kwargs): 29 | source = area.get('1.0', 'end') 30 | line, col = area.indexref() 31 | script = Script(source, line, col, area.filename) 32 | completions = script.completions() 33 | CompletionWindow.__init__(self, area, completions, *args, **kwargs) 34 | 35 | def install(area): 36 | trigger = lambda event: area.hook('python-completion', 37 | 'INSERT', '', lambda event: PythonCompletionWindow( 38 | event.widget), add=False) 39 | 40 | remove_trigger = lambda event: area.unhook('INSERT', '') 41 | 42 | area.install('jedi', (-1, '<>', trigger), 43 | (-1, '<>', trigger), (-1, '<>', remove_trigger), 44 | (-1, '<>', remove_trigger)) 45 | 46 | @Command() 47 | def acp(area): 48 | """ 49 | Activate python completion when file extension is not 50 | detected automatically. 51 | """ 52 | area.hook('jedi', 'INSERT', '', 53 | lambda event: PythonCompletionWindow(event.widget), add=False) 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /vyapp/plugins/jsdebugger.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements basic functionalities of nodejs inspect debugger. It allows 6 | one to sent debug commands and also settting and removing breakpoints with keystrokes. 7 | When a given breakpoint is hit then the cursor is set accordingly. 8 | 9 | Key-Commands 10 | ============ 11 | 12 | Namespace: jsdebugger 13 | 14 | Mode: JAVASCRIPT 15 | Event: 16 | Description: It starts debugging the opened application with no command line arguments. 17 | 18 | Mode: JAVASCRIPT 19 | Event: 20 | Description: It starts the application with command line arguments that use shlex module to split the arguments. 21 | 22 | Mode: JAVASCRIPT 23 | Event: 24 | Description: Send a (c)ontinue to the debug process. 25 | Continue execution, only stop when a breakpoint is encountered. 26 | 27 | Mode: JAVASCRIPT 28 | Event: 29 | Description: Set a break point at the cursor line. 30 | 31 | Mode: JAVASCRIPT 32 | Event: 33 | Description: Remove break point that is set at the cursor line. 34 | 35 | Mode: JAVASCRIPT 36 | Event: 37 | Description: Evaluate selected text. 38 | 39 | Mode: JAVASCRIPT 40 | Event: 41 | Description: Ask for expression to be sent/evaluated. 42 | 43 | Mode: JAVASCRIPT 44 | Event: 45 | Description: Send a JSDebugger command to be executed. 46 | 47 | Mode: JAVASCRIPT 48 | Event: 49 | Description: Send restart command. 50 | 51 | Mode: JAVASCRIPT 52 | Event: 53 | Description: Terminate the process. 54 | 55 | """ 56 | 57 | from untwisted.splits import Terminator 58 | from vyapp.regutils import RegexEvent 59 | from vyapp.dap import DAP 60 | from vyapp.ask import Ask 61 | from vyapp.app import root 62 | import shlex 63 | 64 | class JSDebugger(DAP): 65 | def __call__(self, area): 66 | self.area = area 67 | 68 | area.install('jsdebugger', 69 | ('JAVASCRIPT', '', self.evaluate_selection), 70 | ('JAVASCRIPT', '', self.send_restart), 71 | ('JAVASCRIPT', '', self.send_exec), 72 | ('JAVASCRIPT', '', self.run), 73 | ('JAVASCRIPT', '', self.run_args), 74 | ('JAVASCRIPT', '', self.quit_db), 75 | ('JAVASCRIPT', '', self.send_continue), 76 | ('JAVASCRIPT', '', self.send_dcmd), 77 | ('JAVASCRIPT', '', self.remove_breakpoint), 78 | ('JAVASCRIPT', '', self.send_break)) 79 | 80 | def send_restart(self, event): 81 | self.send('restart\r\n') 82 | root.status.set_msg('JSDebugger: sent restart!') 83 | 84 | def send_dcmd(self, event): 85 | ask = Ask() 86 | 87 | self.send('%s\r\n' % ask.data) 88 | root.status.set_msg('JSDebugger: sent cmd!') 89 | 90 | def send_exec(self, event): 91 | ask = Ask() 92 | 93 | self.send("exec('%s')\r\n" % ask.data) 94 | root.status.set_msg('JSDebugger: sent exec cmd!') 95 | 96 | def evaluate_selection(self, event): 97 | data = event.widget.join_ranges('sel') 98 | self.send("exec('%s')\r\n" % data) 99 | event.widget.chmode('NORMAL') 100 | root.status.set_msg('JSDebugger: Sent selection!') 101 | 102 | def install_handles(self, device): 103 | Terminator(device, delim=b'\n') 104 | 105 | regstr0 = 'break in (.+):([0-9]+)' 106 | RegexEvent(device, regstr0, 'LINE', self.encoding) 107 | device.add_map('LINE', self.handle_line) 108 | 109 | # Should be case insensitive. 110 | regstr1 = 'Break on .+ in (.+):([0-9]+)' 111 | RegexEvent(device, regstr1, 'LINE', self.encoding) 112 | device.add_map('LINE', self.handle_line) 113 | 114 | def run(self, event): 115 | self.kill_process() 116 | 117 | self.create_process(['node', 'inspect', event.widget.filename]) 118 | root.status.set_msg('JSDebugger debug started !') 119 | event.widget.chmode('NORMAL') 120 | 121 | def run_args(self, event): 122 | ask = Ask() 123 | 124 | self.kill_process() 125 | 126 | self.create_process(shlex.split('node inspect %s %s' % ( 127 | event.widget.filename, ask.data))) 128 | 129 | root.status.set_msg('JSDebugger debug started: %s' % ask.data) 130 | event.widget.chmode('NORMAL') 131 | 132 | def send_break(self, event): 133 | line, col = event.widget.indexref('insert') 134 | self.send('sb("%s", %s)\r\n' % ( event.widget.filename, line)) 135 | event.widget.chmode('NORMAL') 136 | 137 | root.status.set_msg('JSDebugger: Sent breakpoint !') 138 | 139 | def send(self, data): 140 | self.expect.send(data.encode(self.encoding)) 141 | print('JSDebugger Cmd: ', data) 142 | 143 | def send_continue(self, event): 144 | """ 145 | """ 146 | 147 | self.send('cont\r\n') 148 | root.status.set_msg('Continue sent to JSDebugger !') 149 | 150 | def remove_breakpoint(self, event): 151 | """ 152 | """ 153 | 154 | line, col = event.widget.indexref('insert') 155 | self.send('cb("%s", %s)\r\n' % (event.widget.filename, line)) 156 | event.widget.chmode('NORMAL') 157 | root.status.set_msg('JSDebugger: Remove breakpoint sent!') 158 | 159 | install = JSDebugger() 160 | 161 | 162 | -------------------------------------------------------------------------------- /vyapp/plugins/jsonfmt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements a key-command to format JSON strings. You select 6 | the region containing the JSON then issue the key-command to format it will 7 | print any errors on the status bar. 8 | 9 | Key-Commands 10 | ============ 11 | 12 | Namespace: jsonfmt 13 | 14 | Mode: EXTRA 15 | Event: 16 | Description: Format the selected JSON data then switch to NORMAL mode. 17 | 18 | """ 19 | 20 | from subprocess import Popen, PIPE 21 | from vyapp.app import root 22 | 23 | class FmtJSON: 24 | def __init__(self, area, *args, **kwargs): 25 | self.area = area 26 | area.install('fmtjson', ('EXTRA', '', self.run_printer)) 27 | 28 | def run_printer(self, event): 29 | start = self.area.index('sel.first') 30 | end = self.area.index('sel.last') 31 | data = self.area.get(start, end) 32 | 33 | child = Popen('python -m json.tool', encoding=self.area.charset, 34 | stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=1) 35 | 36 | output, err = child.communicate(data) 37 | print('\nJSON Errors:\n', err) 38 | 39 | if child.returncode: 40 | root.status.set_msg('JSON Errors! Check its output.') 41 | else: 42 | self.area.swap(output, start, end) 43 | 44 | self.area.chmode('NORMAL') 45 | 46 | install = FmtJSON -------------------------------------------------------------------------------- /vyapp/plugins/line_feed.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | It is handy to have Key-Commands to insert blank lines up/down the cursor when in NORMAL mode. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: line-feed 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Insert a line down then goes insert mode. 15 | 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Insert a line up then goes insert mode. 20 | 21 | 22 | """ 23 | 24 | class LineFeed: 25 | def __init__(self, area): 26 | area.install('line-feed', 27 | ('NORMAL', '', self.insert_down), 28 | ('NORMAL', '', self.insert_up)) 29 | 30 | self.area = area 31 | 32 | def insert_down(self, event): 33 | """ 34 | It inserts one line down from the cursor position. 35 | """ 36 | 37 | self.area.edit_separator() 38 | self.area.insert('insert +1l linestart', '\n') 39 | self.area.mark_set('insert', 'insert +1l linestart') 40 | 41 | self.area.see('insert') 42 | self.area.chmode('INSERT') 43 | 44 | def insert_up(self, event): 45 | """ 46 | It inserts one line up. 47 | """ 48 | 49 | self.area.edit_separator() 50 | self.area.insert('insert linestart', '\n') 51 | self.area.mark_set('insert', 'insert -1l linestart') 52 | self.area.see('insert') 53 | 54 | self.area.chmode('INSERT') 55 | 56 | install = LineFeed 57 | -------------------------------------------------------------------------------- /vyapp/plugins/line_index.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements a way to place the cursor at a given row.col. 6 | 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: line-index 12 | 13 | Mode: NORMAL 14 | Event: 15 | Description: Shows an input text field to insert a Line.Col value to place the cursor at that position. 16 | 17 | """ 18 | from vyapp.ask import Ask 19 | from tkinter import TclError 20 | 21 | def go_to_pos(area): 22 | ask = Ask() 23 | 24 | try: 25 | area.seecur(ask.data) 26 | except TclError: 27 | pass 28 | 29 | try: 30 | area.setcur(ask.data) 31 | except TclError: 32 | pass 33 | 34 | install = lambda area: area.install('line-index', 35 | ('NORMAL', '', lambda event: go_to_pos(event.widget))) 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /vyapp/plugins/line_scroll.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements Key-Commands to scroll lines. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: line-scroll 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Scroll one line up. 15 | 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Scroll one line down. 20 | """ 21 | from tkinter import SCROLL 22 | 23 | class LineScroll: 24 | def __init__(self, area): 25 | area.install('line-scroll', 26 | ('NORMAL', '', self.scroll_up), 27 | ('NORMAL', '', self.scroll_down)) 28 | 29 | self.area = area 30 | 31 | def scroll_up(self, event): 32 | """ 33 | It scrolls one line up 34 | """ 35 | 36 | # should be rewritten. 37 | # it fails with append. 38 | 39 | self.area.yview(SCROLL, -1, 'units') 40 | is_visible = self.area.dlineinfo('insert') 41 | 42 | if not is_visible: 43 | self.area.mark_set('insert', 'insert -1l') 44 | 45 | def scroll_down(self, event): 46 | """ 47 | It scrolls one line down. 48 | """ 49 | 50 | self.area.yview(SCROLL, 1, 'units') 51 | is_visible = self.area.dlineinfo('insert') 52 | 53 | if not is_visible: 54 | self.area.mark_set('insert', 'insert +1l') 55 | 56 | install = LineScroll 57 | -------------------------------------------------------------------------------- /vyapp/plugins/line_strips.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements two commands to strip chars off selected 6 | regions of text. 7 | 8 | Commands 9 | ======== 10 | 11 | Command: def strip(area, chars=' '): 12 | Description: 13 | 14 | Command: def rstrip(area, chars=' '): 15 | Description: 16 | 17 | """ 18 | 19 | from vyapp.plugins import Command 20 | from re import escape 21 | 22 | @Command() 23 | def strip(area, chars=' '): 24 | """ 25 | Strip chars off the beginning of all selected lines. 26 | if chars is not given it removes spaces. 27 | """ 28 | 29 | area.replace_ranges('sel', '^[%s]+' % escape(chars), '') 30 | 31 | @Command() 32 | def rstrip(area, chars=' '): 33 | """ 34 | Strip chars off the end of all selected lines. 35 | if chars is not given it removes spaces. 36 | """ 37 | 38 | area.replace_ranges('sel', '[%s]+$' % escape(chars), '') 39 | -------------------------------------------------------------------------------- /vyapp/plugins/main_jumps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements the basic cursor movements. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: main-jumps 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Move the cursor one line down. 15 | 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Move the cursor one line up. 20 | 21 | 22 | Mode: NORMAL 23 | Event: 24 | Description: Move the cursor one character left. 25 | 26 | 27 | Mode: NORMAL 28 | Event: 29 | Description: Move the cursor one character right. 30 | 31 | 32 | """ 33 | 34 | 35 | def install(area): 36 | area.install('main-jumps', ('NORMAL', '', lambda event: event.widget.down()), 37 | ('NORMAL', '', lambda event: event.widget.up()), 38 | ('NORMAL', '', lambda event: event.widget.left()), 39 | ('NORMAL', '', lambda event: event.widget.right())) 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /vyapp/plugins/mode_shortcut.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Implement keycommands to pin a given mode and easily switch back to it. 6 | It is interesting to be used with modes like PDB etc. 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: mode-shortcut 12 | 13 | Mode: GLOBAL 14 | Event: 15 | Description: Pin the current mode. 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Switch to the previous pinned mode. 20 | 21 | """ 22 | 23 | from vyapp.app import root 24 | 25 | class ModeShortcut: 26 | target_id = 'NORMAL' 27 | 28 | def __init__(self, area): 29 | self.area = area 30 | area.install('mode-shortcut', ('NORMAL', '', 31 | lambda event: self.area.chmode(self.target_id)), 32 | ('-1', '', lambda event: self.save_mode())) 33 | 34 | def save_mode(self): 35 | self.target_id = self.area.id 36 | self.area.chmode('NORMAL') 37 | root.status.set_msg('Mode %s pinned!' % self.target_id) 38 | return 'break' 39 | 40 | install = ModeShortcut 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /vyapp/plugins/mypy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Run static typer checker on your project files. It uses mypy. 6 | 7 | Extern dependencies: 8 | http://mypy-lang.org/ 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Namespace: mypy 14 | 15 | Mode: PYTHON 16 | Event: 17 | Description: Show previous Mypy reports. 18 | 19 | Mode: PYTHON 20 | Event: 21 | Description: Run Mypy on the current file. 22 | with syntax errors. 23 | 24 | Mode: PYTHON 25 | Event: 26 | Description: Run Mypy on the whole current file project. 27 | 28 | Commands 29 | ======== 30 | 31 | Command: py_static() 32 | 33 | """ 34 | 35 | from vyapp.plugins import Command 36 | from subprocess import Popen, STDOUT, PIPE 37 | from os.path import relpath 38 | from vyapp.widgets import LinePicker 39 | from vyapp.tools import get_project_root 40 | from vyapp.app import root 41 | from vyapp.stderr import printd 42 | from re import findall 43 | import sys 44 | 45 | class StaticChecker: 46 | options = LinePicker() 47 | path = 'mypy' 48 | 49 | def __init__(self, area): 50 | self.area = area 51 | area.install('mypy', ('PYTHON', '', self.check_module), 52 | ('PYTHON', '', lambda event: self.options.display()), 53 | ('PYTHON', '', self.check_all)) 54 | 55 | @classmethod 56 | def c_path(cls, path): 57 | printd('Snakerr - Setting Mypy path = ', cls.path) 58 | cls.path = path 59 | 60 | def check_all(self, event=None): 61 | path = get_project_root(self.area.filename) 62 | child = Popen([self.path, path], 63 | stdout=PIPE, stderr=STDOUT, encoding=self.area.charset) 64 | output = child.communicate()[0] 65 | 66 | regex = '(.+?):([0-9]+):(.+)' 67 | ranges = findall(regex, output) 68 | 69 | sys.stdout.write('Mypy errors: \n%s\n' % output) 70 | self.area.chmode('NORMAL') 71 | 72 | root.status.set_msg('Mypy errors: %s' % len(ranges)) 73 | if ranges: 74 | self.options(ranges) 75 | 76 | def check_module(self, event=None): 77 | path = get_project_root(self.area.filename) 78 | child = Popen([self.path, path], 79 | stdout=PIPE, stderr=STDOUT, encoding=self.area.charset) 80 | output = child.communicate()[0] 81 | 82 | regex = '(%s):([0-9]+):(.+)' % relpath(self.area.filename) 83 | ranges = findall(regex, output) 84 | sys.stdout.write('Mypy errors: \n%s\n' % output) 85 | self.area.chmode('NORMAL') 86 | 87 | root.status.set_msg('Mypy errors: %s' % len(ranges)) 88 | if ranges: 89 | self.options(ranges) 90 | 91 | 92 | install = StaticChecker 93 | @Command() 94 | def py_static(area): 95 | checker = StaticChecker(area) 96 | checker.check_all() 97 | -------------------------------------------------------------------------------- /vyapp/plugins/outputs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements Key-Commands to redirect the default python sys.stdout object 6 | to an AreaVi instance. As it is possible to drop python code to vy in order 7 | to affect the editor state, the python interpreter writes output to 8 | sys.stdout. 9 | 10 | With this plugin it is possible to drop the output to a given 11 | Line.Col inside an AreaVi instance. 12 | 13 | Key-Commands 14 | ============ 15 | 16 | Namespace: outputs 17 | 18 | Mode: Global 19 | Event: 20 | Description: it restores sys.stdout. 21 | 22 | Mode: NORMAL 23 | Event: 24 | Description: It removes a given AreaVi instance from 25 | having output written in. 26 | 27 | Mode: Global 28 | Event: 29 | Description: It redirects output from sys.stdout to 30 | a given AreaVi instance. 31 | 32 | """ 33 | 34 | from vyapp.app import root 35 | import sys 36 | 37 | class Stdout: 38 | """ 39 | This class is used to wrap an AreaVi widget to be 40 | used as stdout channel. 41 | """ 42 | 43 | def __init__(self, area): 44 | self.area = area 45 | self.area.mark_set('(CODE_MARK)', 'insert') 46 | 47 | def write(self, data): 48 | """ 49 | This method is called to write data to the AreaVi widget. 50 | """ 51 | 52 | self.area.insert('(CODE_MARK)', data) 53 | self.area.see('insert') 54 | 55 | def __eq__(self, other): 56 | return self.area == other 57 | 58 | class Transmitter: 59 | """ 60 | This class would replace sys.stdout. It is used to dispatch 61 | data that is written to sys.stdout, the data is dispatched to its 62 | child objects that have a write method. 63 | """ 64 | 65 | def __init__(self, *default): 66 | self.base = [] 67 | self.default = [] 68 | self.default.extend(default) 69 | self.base.extend(default) 70 | 71 | def add_default(self, fd): 72 | """ 73 | """ 74 | 75 | try: 76 | self.default.remove(fd) 77 | except ValueError: 78 | pass 79 | finally: 80 | self.default.append(fd) 81 | 82 | def restore(self): 83 | """ 84 | It clears all children and restores the default 85 | child. 86 | """ 87 | self.base.clear() 88 | self.base.extend(self.default) 89 | 90 | def append(self, fd): 91 | try: 92 | self.base.remove(fd) 93 | except ValueError: 94 | pass 95 | finally: 96 | self.base.append(fd) 97 | 98 | def discard(self, fd): 99 | try: 100 | self.base.remove(fd) 101 | except ValueError: 102 | pass 103 | 104 | def write(self, data): 105 | """ 106 | This method is used to dispatch data to the 107 | children. 108 | 109 | If it occurs an exception when writing to some 110 | of the supposed fd's then it is removed from 111 | the list of fd's. 112 | 113 | Consider the situation 114 | that an areavi instance is added then 115 | destroyed. It will throw an exception 116 | when writing to that areavi. 117 | """ 118 | 119 | for ind in self.base[:]: 120 | try: 121 | ind.write(data) 122 | except Exception: 123 | self.discard(ind) 124 | 125 | def flush(self): 126 | """ 127 | """ 128 | 129 | pass 130 | 131 | class OutputController: 132 | def __init__(self, area): 133 | self.area = area 134 | 135 | area.install('outputs', 136 | ('NORMAL', '', self.add_output), 137 | ('NORMAL', '', self.rm_output), 138 | (-1, '', self.restore_output)) 139 | 140 | def add_output(self, event): 141 | sys.stdout.append(Stdout(self.area)) 142 | root.status.set_msg('Output set on: %s' % self.area.index('insert')) 143 | 144 | def rm_output(self, event): 145 | sys.stdout.discard(self.area) 146 | root.status.set_msg('Output removed!') 147 | 148 | def restore_output(self, event): 149 | sys.stdout.restore() 150 | root.status.set_msg('Stdout restored!') 151 | return 'break' 152 | 153 | sys.stdout = Transmitter(sys.__stdout__) 154 | install = OutputController 155 | 156 | 157 | -------------------------------------------------------------------------------- /vyapp/plugins/page_scroll.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin turns possible to scroll pages. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: page-scroll 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Scroll a page up. 15 | 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Scroll one page down. 20 | 21 | """ 22 | from tkinter import SCROLL 23 | 24 | class PageScroll: 25 | def __init__(self, area): 26 | area.install('page-scroll', 27 | ('NORMAL', '', self.scroll_up), 28 | ('NORMAL', '', self.scroll_down)) 29 | 30 | self.area = area 31 | 32 | def scroll_up(self, event): 33 | """ 34 | It scrolls one page up. 35 | """ 36 | 37 | self.area.yview(SCROLL, -1, 'page') 38 | self.area.mark_set('insert', '@0,0') 39 | 40 | def scroll_down(self, event): 41 | """ 42 | It scrolls one page down. 43 | """ 44 | 45 | self.area.yview(SCROLL, 1, 'page') 46 | self.area.mark_set('insert', '@0,0') 47 | 48 | 49 | install = PageScroll 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /vyapp/plugins/pair_sel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements a Key-Command to select text between pairs of () [] {}. 6 | 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: sym-pair-sel 12 | 13 | Mode: NORMAL 14 | Event: 15 | Description: Select text between pairs of ( ) [] {} when the cursor 16 | is placed over one of these characters. 17 | 18 | Mode: NORMAL 19 | Event: 20 | Description: Select text between pairs of ( ) [] {} including the pairs when the cursor 21 | is placed over one of these characters. 22 | """ 23 | 24 | class PairSel: 25 | def __init__(self, area, max=2500): 26 | area.install('pair-sel', 27 | ('NORMAL', '', self.sel_data), 28 | ('NORMAL', '', self.sel_pair),) 29 | self.max = max 30 | self.area = area 31 | 32 | def get_pos(self): 33 | index = self.area.case_pair('insert', self.max, '(', ')') 34 | if index: return index 35 | 36 | index = self.area.case_pair('insert', self.max, '[', ']') 37 | if index: return index 38 | 39 | index = self.area.case_pair('insert', self.max, '{', '}') 40 | if index: return index 41 | 42 | def sel_data(self, event): 43 | index = self.get_pos() 44 | if not index: return 45 | 46 | min = self.area.min(index, 'insert') 47 | max = self.area.max(index, 'insert') 48 | min = '%s +1c' % min 49 | 50 | self.area.tag_add('sel', min, max) 51 | 52 | def sel_pair(self, event): 53 | """ 54 | """ 55 | index = self.get_pos() 56 | if not index: return 57 | 58 | min = self.area.min(index, 'insert') 59 | max = self.area.max(index, 'insert') 60 | max = '%s +1c' % max 61 | 62 | self.area.tag_add('sel', min, max) 63 | 64 | install = PairSel 65 | 66 | 67 | -------------------------------------------------------------------------------- /vyapp/plugins/pane_jumps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | 6 | Commands 7 | ======== 8 | 9 | 10 | """ 11 | from vyapp.areavi import AreaVi 12 | from vyapp.app import root 13 | 14 | 15 | class PaneJumps: 16 | def __init__(self, area): 17 | self.area = area 18 | 19 | area.install('splits', 20 | (-1, '', self.jump_left), 21 | (-1, '', self.jump_right), 22 | (-1, '', self.jump_up), 23 | (-1, '', self.jump_down)) 24 | 25 | def jump_left(self, event): 26 | wids = self.area.master.master.panes() 27 | wids = [str(item) for item in wids] 28 | count = wids.index(str(self.area.master)) 29 | count = count - 1 30 | wid = self.area.nametowidget(wids[count]) 31 | wid = [ind for ind in wid.winfo_children() 32 | if isinstance(ind, AreaVi)] 33 | 34 | # as there is only one. 35 | wid[0].focus_set() 36 | return 'break' 37 | 38 | def jump_right(self, event): 39 | wids = self.area.master.master.panes() 40 | wids = [str(item) for item in wids] 41 | count = wids.index(str(self.area.master)) 42 | count = (count + 1) % len(wids) 43 | wid = self.area.nametowidget(wids[count]) 44 | wid = [ind for ind in wid.winfo_children() 45 | if isinstance(ind, AreaVi)] 46 | 47 | # as there is only one. 48 | wid[0].focus_set() 49 | return 'break' 50 | 51 | def jump_down(self, event): 52 | wids = self.area.master.master.panes() 53 | wids = [str(item) for item in wids] 54 | index = wids.index(str(self.area.master)) 55 | 56 | wids = self.area.master.master.master.panes() 57 | wids = [str(item) for item in wids] 58 | count = wids.index(str(self.area.master.master)) 59 | count = (count + 1) % len(wids) 60 | 61 | wid = self.area.nametowidget(wids[count]) 62 | size = len(wid.panes()) 63 | wid = self.area.nametowidget(wid.panes()[ 64 | index if index < size else (size - 1)]) 65 | 66 | wid = [ind for ind in wid.winfo_children() 67 | if isinstance(ind, AreaVi)] 68 | 69 | # as there is only one. 70 | wid[0].focus_set() 71 | return 'break' 72 | 73 | def jump_up(self, event): 74 | wids = self.area.master.master.panes() 75 | wids = [str(item) for item in wids] 76 | index = wids.index(str(self.area.master)) 77 | 78 | wids = self.area.master.master.master.panes() 79 | wids = [str(item) for item in wids] 80 | count = wids.index(str(self.area.master.master)) 81 | count = count - 1 82 | 83 | wid = self.area.nametowidget(wids[count]) 84 | size = len(wid.panes()) 85 | wid = self.area.nametowidget(wid.panes()[ 86 | index if index < size else (size - 1)]) 87 | 88 | wid = [ind for ind in wid.winfo_children() 89 | if isinstance(ind, AreaVi)] 90 | 91 | # as there is only one. 92 | wid[0].focus_set() 93 | return 'break' 94 | 95 | install = PaneJumps 96 | -------------------------------------------------------------------------------- /vyapp/plugins/pane_resize.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | 6 | Commands 7 | ======== 8 | 9 | 10 | """ 11 | 12 | 13 | class PaneResize: 14 | def __init__(self, area): 15 | self.area = area 16 | 17 | area.install('pane-resize', 18 | ('EXTRA', '', self.dec_vsash), 19 | ('EXTRA', '', self.inc_vsash), 20 | ('EXTRA', '', self.dec_hsash), 21 | ('EXTRA', '', self.inc_hsash)) 22 | 23 | 24 | def dec_vsash(self, event): 25 | wids = self.area.master.master.panes() 26 | wids = [str(item) for item in wids] 27 | count = wids.index(str(self.area.master)) 28 | count = count - 1 if count > 0 else 0 29 | 30 | pos = self.area.master.master.sash_coord(count) 31 | self.area.master.master.sash_place(count, pos[0] - 15, 0) 32 | 33 | def inc_vsash(self, event): 34 | wids = self.area.master.master.panes() 35 | wids = [str(item) for item in wids] 36 | count = wids.index(str(self.area.master)) 37 | count = count - 1 if count > 0 else 0 38 | 39 | pos = self.area.master.master.sash_coord(count) 40 | self.area.master.master.sash_place(count, pos[0] + 15, 0) 41 | 42 | def dec_hsash(self, event): 43 | wids = self.area.master.master.master.panes() 44 | wids = [str(item) for item in wids] 45 | count = wids.index(str(self.area.master.master)) 46 | count = count - 1 if count > 0 else 0 47 | pos = self.area.master.master.master.sash_coord(count) 48 | self.area.master.master.master.sash_place(count, 0, pos[1] - 15) 49 | 50 | def inc_hsash(self, event): 51 | wids = self.area.master.master.master.panes() 52 | wids = [str(item) for item in wids] 53 | count = wids.index(str(self.area.master.master)) 54 | count = count - 1 if count > 0 else 0 55 | 56 | pos = self.area.master.master.master.sash_coord(count) 57 | self.area.master.master.master.sash_place(count, 0, pos[1] + 15) 58 | 59 | 60 | install = PaneResize -------------------------------------------------------------------------------- /vyapp/plugins/plugin_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | """ 4 | 5 | from vyapp.plugins import ENV 6 | from vyapp.areavi import AreaVi 7 | from vyapp.plugins import autoload 8 | from vyapp.app import root 9 | 10 | def load(plugin, *args, **kwargs): 11 | autoload(plugin, *args, **kwargs) 12 | for ind in AreaVi.areavi_widgets(root): 13 | plugin.install(ind, *args, **kwargs) 14 | root.status.set_msg('Plugin loaded!') 15 | 16 | ENV['load'] = load 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /vyapp/plugins/project.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin attempt to set the actual project attribute 6 | for the current AreaVi instance. It tries to find 7 | project folders like .git, .svn, .hg or a ._ that's 8 | a vy project file. 9 | 10 | """ 11 | 12 | from os.path import exists, dirname, join 13 | from vyapp.stderr import printd 14 | 15 | def get_sentinel_file(path, *args): 16 | """ 17 | """ 18 | 19 | tmp = path 20 | while True: 21 | tmp = dirname(tmp) 22 | for ind in args: 23 | if exists(join(tmp, ind)): 24 | return tmp 25 | elif tmp == dirname(tmp): 26 | return '' 27 | 28 | class Project: 29 | sentinels = ('.git', '.svn', '.hg', '._') 30 | 31 | def __init__(self, area): 32 | self.area = area 33 | area.install('fstmt', (-1, '<>', self.set_path), 34 | (-1, '<>', self.set_path)) 35 | 36 | @classmethod 37 | def c_sentinels(cls, *sentinels): 38 | cls.sentinels = sentinels 39 | printd('Project - Setting sentinels = ', cls.sentinels) 40 | 41 | def set_path(self, event): 42 | """ 43 | Set the project root automatically. 44 | """ 45 | 46 | self.area.project = get_sentinel_file( 47 | self.area.filename, *Project.sentinels) 48 | printd('Project - Setting project path = ', self.area.project) 49 | 50 | install = Project 51 | 52 | -------------------------------------------------------------------------------- /vyapp/plugins/python_mode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Extra mode for python programming language. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: python-mode 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Switch to PYTHON mode. 15 | """ 16 | 17 | def python_mode(area): 18 | area.chmode('PYTHON') 19 | 20 | def install(area): 21 | area.add_mode('PYTHON') 22 | area.install('python-mode', ('NORMAL', '', 23 | lambda event: python_mode(area))) 24 | 25 | -------------------------------------------------------------------------------- /vyapp/plugins/quick_jumps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | It is used to remember an important position of the text and quickly switch to it. 6 | 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: quick-jumps 12 | 13 | Mode: NORMAL 14 | Event: 15 | Description: 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: 20 | 21 | """ 22 | 23 | class QuickJumps: 24 | def __init__(self, area): 25 | self.area = area 26 | area.install('quick-jumps', ('NORMAL', '', lambda event: self.area.mark_set('(PREV_POSITION)', 'insert')), 27 | ('NORMAL', '', lambda event: self.area.seecur('(PREV_POSITION)'))) 28 | 29 | install = QuickJumps 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /vyapp/plugins/quick_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements incremental search which is handy to quickly 6 | jump to specific positions of the text. 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: quick-search 12 | 13 | 14 | """ 15 | 16 | from vyapp.ask import Get 17 | from vyapp.regutils import build_regex 18 | from vyapp.stderr import printd 19 | from vyapp.app import root 20 | 21 | class QuickSearch: 22 | confs = { 23 | 'background':'yellow', 'foreground':'black' 24 | } 25 | 26 | def __init__(self, area, nocase=True): 27 | self.area = area 28 | self.nocase = nocase 29 | area.tag_config('(SEARCH_MATCH)', self.confs) 30 | 31 | area.install('quick-search', 32 | ('NORMAL', '', self.start_backwards), 33 | ('NORMAL', '', self.start_forwards)) 34 | 35 | @classmethod 36 | def c_appearance(cls, **confs): 37 | """ 38 | Used to set matched region properties. These properties 39 | can be background, foreground etc. 40 | 41 | Check Tkinter Text widget documentation on tags for more info. 42 | """ 43 | 44 | cls.confs.update(confs) 45 | printd('Quick Search - Setting confs = ', cls.confs) 46 | 47 | def start_forwards(self, event): 48 | self.index = self.area.index('insert') 49 | self.stopindex = 'end' 50 | self.backwards = False 51 | 52 | Get(events = { 53 | '':self.search_down, 54 | '': self.search_up, 55 | '<>': self.update, 56 | '': self.update, 57 | '': lambda wid: self.area.tag_remove( 58 | '(SEARCH_MATCH)', '1.0', 'end'), 59 | '': lambda wid: True}) 60 | 61 | def start_backwards(self, event): 62 | self.index = self.area.index('insert') 63 | self.backwards = True 64 | self.stopindex = '1.0' 65 | 66 | Get(events = { 67 | '':self.search_down, 68 | '': self.search_up, 69 | '<>': self.update, 70 | '': self.update, 71 | '': lambda wid: self.area.tag_remove( 72 | '(SEARCH_MATCH)', '1.0', 'end'), 73 | '': lambda wid: True}) 74 | 75 | def update(self, wid): 76 | """ 77 | 78 | """ 79 | data = wid.get() 80 | pattern = build_regex(data) 81 | root.status.set_msg('Pattern:%s' % pattern) 82 | self.area.ipick('(SEARCH_MATCH)', pattern, 83 | verbose=True, backwards=self.backwards, index=self.index, 84 | nocase=self.nocase, stopindex=self.stopindex) 85 | 86 | def search_up(self, wid): 87 | """ 88 | 89 | """ 90 | data = wid.get() 91 | pattern = build_regex(data) 92 | self.area.ipick('(SEARCH_MATCH)', pattern, index='insert', 93 | nocase=self.nocase, stopindex='1.0', backwards=True) 94 | 95 | def search_down(self, wid): 96 | """ 97 | 98 | """ 99 | data = wid.get() 100 | pattern = build_regex(data) 101 | self.area.ipick('(SEARCH_MATCH)', pattern, nocase=self.nocase, 102 | stopindex='end', index='insert') 103 | 104 | 105 | install = QuickSearch 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /vyapp/plugins/quick_sel.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /vyapp/plugins/range_sel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements range selection. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: range-sel 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Add/remove selection one line up from the initial selection mark. 15 | 16 | 17 | Mode: NORMAL 18 | Event: 19 | Description: Add/remove selection one line down from the initial selection mark. 20 | 21 | 22 | Mode: NORMAL 23 | Event: 24 | Description: Add/remove selection one character right from the initial selection mark. 25 | 26 | 27 | Mode: NORMAL 28 | Event: 29 | Description: Add/remove selection one character left from the initial selection mark. 30 | 31 | 32 | Mode: NORMAL 33 | Event: 34 | Description: Drop a selection mark. 35 | """ 36 | 37 | from vyapp.app import root 38 | 39 | class RangeSel: 40 | def __init__(self, area): 41 | area.install('range-sel', 42 | ('NORMAL', '', self.sel_up), 43 | ('NORMAL', '', self.sel_down), 44 | ('NORMAL', '', self.sel_left), 45 | ('NORMAL', '', self.sel_right), 46 | ('NORMAL', '', self.start_selection)) 47 | area.mark_set('(RANGE_SEL_MARK)', '1.0') 48 | 49 | self.area = area 50 | 51 | def start_selection(self, event): 52 | """ 53 | Start range selection. 54 | """ 55 | 56 | self.area.mark_set('(RANGE_SEL_MARK)', 'insert') 57 | root.status.set_msg('Dropped selection mark.') 58 | 59 | def sel_up(self, event): 60 | """ 61 | It adds 'sel' one line up the 'insert' position 62 | and sets the cursor one line up. 63 | """ 64 | 65 | self.area.rmsel('(RANGE_SEL_MARK)', 'insert') 66 | self.area.up() 67 | self.area.addsel('(RANGE_SEL_MARK)', 'insert') 68 | 69 | def sel_down(self, event): 70 | """ 71 | It adds or removes selection one line down. 72 | """ 73 | 74 | self.area.rmsel('(RANGE_SEL_MARK)', 'insert') 75 | self.area.down() 76 | self.area.addsel('(RANGE_SEL_MARK)', 'insert') 77 | 78 | def sel_right(self, event): 79 | """ 80 | It adds or removes selection one character right. 81 | """ 82 | 83 | 84 | self.area.rmsel('(RANGE_SEL_MARK)', 'insert') 85 | self.area.right() 86 | self.area.addsel('(RANGE_SEL_MARK)', 'insert') 87 | 88 | def sel_left(self, event): 89 | """ 90 | It adds or removes selection one character left. 91 | """ 92 | 93 | self.area.rmsel('(RANGE_SEL_MARK)', 'insert') 94 | self.area.left() 95 | self.area.addsel('(RANGE_SEL_MARK)', 'insert') 96 | 97 | install = RangeSel 98 | -------------------------------------------------------------------------------- /vyapp/plugins/rope.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Integrates with python rope refactoring tools. It allows users 6 | to rename variables, methods, classes, modules and also move python resources 7 | easily. 8 | 9 | Key-Commands 10 | ============ 11 | 12 | Namespace: rope 13 | 14 | Mode: PYTHON 15 | Event: 16 | Description: Rename a given python resource. Place the cursor 17 | over the resource string on the AreaVi instance then issue the keycommand 18 | to perform the renaming along the whole project. 19 | """ 20 | 21 | from vyapp.ask import Ask 22 | from rope.base.project import Project 23 | from vyapp.tools import get_project_root, error 24 | from vyapp.areavi import AreaVi 25 | from rope.refactor.rename import Rename 26 | from rope.base.libutils import path_to_resource 27 | from rope.base.change import MoveResource 28 | from vyapp.app import root 29 | from rope.base import libutils 30 | from rope.refactor.move import create_move 31 | 32 | class PythonRefactor: 33 | def __init__(self, area): 34 | self.area = area 35 | self.files = None 36 | area.install('rope', ('PYTHON', '', self.rename), 37 | ('PYTHON', '', self.static_analysis), 38 | ('PYTHON', '', self.move)) 39 | 40 | def get_root_path(self): 41 | if self.area.project: 42 | return self.area.project 43 | return get_project_root(self.area.filename) 44 | 45 | def static_analysis(self, event): 46 | path = self.get_root_path() 47 | project = Project(path) 48 | mod = path_to_resource(project, self.area.filename) 49 | 50 | libutils.analyze_module(project, mod) 51 | project.close() 52 | 53 | @error 54 | def move(self, event): 55 | """ 56 | """ 57 | ask = Ask() 58 | 59 | tmp0 = self.area.get('1.0', 'insert') 60 | offset = len(tmp0) 61 | 62 | path = self.get_root_path() 63 | project = Project(path) 64 | 65 | project = Project(path) 66 | mod = path_to_resource(project, self.area.filename) 67 | mover = create_move(project, mod, offset) 68 | destin = path_to_resource(project, ask.data) 69 | changes = mover.get_changes(destin) 70 | project.do(changes) 71 | 72 | self.update_instances(changes) 73 | project.close() 74 | 75 | self.area.chmode('NORMAL') 76 | root.status.set_msg('Resources moved!') 77 | 78 | 79 | def update_instances(self, changes): 80 | """ 81 | After changes it updates all AreaVi instances which 82 | were changed. 83 | """ 84 | 85 | # Avoid having to calculate it multiple times. 86 | self.files = AreaVi.get_opened_files(root) 87 | for ind in changes.changes: 88 | if isinstance(ind, (MoveResource,)): 89 | self.on_move_resource(ind) 90 | else: 91 | self.on_general_case(ind) 92 | 93 | def on_general_case(self, change): 94 | """ 95 | Should be called when self.files is updated. 96 | """ 97 | 98 | for ind in change.get_changed_resources(): 99 | instance = self.files.get(ind.real_path) 100 | if instance: 101 | instance.load_data(ind.real_path) 102 | 103 | def on_move_resource(self, change): 104 | """ 105 | Should be called when self.files is updated. 106 | """ 107 | old, new = change.get_changed_resources() 108 | instance = self.files.get(old.real_path) 109 | 110 | # When the file is not updated then no need to load it. 111 | if instance: 112 | instance.load_data(new.real_path) 113 | 114 | @error 115 | def rename(self, name): 116 | ask = Ask() 117 | 118 | tmp0 = self.area.get('1.0', 'insert') 119 | offset = len(tmp0) 120 | path = self.get_root_path() 121 | project = Project(path) 122 | mod = path_to_resource(project, self.area.filename) 123 | renamer = Rename(project, mod, offset) 124 | changes = renamer.get_changes(ask.data) 125 | project.do(changes) 126 | 127 | self.update_instances(changes) 128 | 129 | print('\nRope - Renamed resource ..\n') 130 | print(changes.get_description()) 131 | self.area.chmode('NORMAL') 132 | root.status.set_msg('Resources renamed!') 133 | project.close() 134 | 135 | install = PythonRefactor 136 | 137 | -------------------------------------------------------------------------------- /vyapp/plugins/ruby_mode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Extra mode for Ruby programming language. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: ruby-mode 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Switch to ruby mode. 15 | """ 16 | 17 | def ruby_mode(area): 18 | area.chmode('RUBY') 19 | 20 | def install(area): 21 | area.add_mode('RUBY') 22 | area.install('ruby-mode', ('NORMAL', '', 23 | lambda event: ruby_mode(area))) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /vyapp/plugins/seek_symbol.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements a mechanism to quickly make the cursor jump back/next to a position. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: seek-symbol 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Switch to JUMP_NEXT mode. 15 | 16 | Mode: NORMAL 17 | Event: 18 | Description: Switch to JUMP_BACK mode. 19 | 20 | Mode: JUMP_BACK 21 | Event: 22 | Description: Switch to JUMP_NEXT mode. 23 | 24 | Mode: JUMP_NEXT 25 | Event: 26 | Description: Switch to JUMP_BACK mode. 27 | 28 | Mode: JUMP_NEXT 29 | Event: 30 | Description: Switch to INSERT mode. 31 | 32 | Mode: JUMP_NEXT 33 | Event: 34 | Description: It adds/removes selection from initial cursor position to the insert position. 35 | 36 | Mode: JUMP_BACK 37 | Event: 38 | Description: Switch to INSERT mode. 39 | 40 | Mode: JUMP_BACK 41 | Event: 42 | Description: It adds/removes selection from initial cursor position to the insert position. 43 | """ 44 | 45 | from vyapp.app import root 46 | 47 | def get_char(num): 48 | try: 49 | char = chr(num) 50 | except ValueError: 51 | return '' 52 | else: 53 | return char 54 | 55 | class SeekSymbol: 56 | def __init__(self, area): 57 | area.add_mode('JUMP_BACK') 58 | area.add_mode('JUMP_NEXT') 59 | 60 | area.install('seek-symbol', 61 | ('NORMAL', '', self.next_mode), 62 | ('NORMAL', '', self.back_mode), 63 | ('JUMP_BACK', '', self.sel_data), 64 | ('JUMP_BACK', '', self.jump_back), 65 | ('JUMP_BACK', '', self.switch_insert), 66 | ('JUMP_NEXT', '', self.switch_insert), 67 | ('JUMP_NEXT', '', self.back_mode), 68 | ('JUMP_BACK', '', self.next_mode), 69 | ('JUMP_NEXT', '', self.sel_data), 70 | ('JUMP_NEXT', '', self.jump_next)) 71 | 72 | self.area = area 73 | 74 | def next_mode(self, event): 75 | self.area.mark_set('(RANGE_SEL_MARK)', 'insert') 76 | self.area.chmode('JUMP_NEXT') 77 | 78 | root.status.set_msg('Switched to JUMP_NEXT mode.') 79 | 80 | def back_mode(self, event): 81 | self.area.mark_set('(RANGE_SEL_MARK)', 'insert') 82 | self.area.chmode('JUMP_BACK') 83 | 84 | root.status.set_msg('Switched to JUMP_BACK mode.') 85 | 86 | def switch_insert(self, event): 87 | self.area.chmode('INSERT') 88 | root.status.set_msg('Switched to INSERT mode.') 89 | 90 | def jump_next(self, event): 91 | char = get_char(event.keysym_num) 92 | _, index0, index1 = self.area.isearch(char, index='insert', 93 | stopindex='end', regexp=False) 94 | 95 | self.area.mark_set('insert', index1) 96 | self.area.see('insert') 97 | 98 | def jump_back(self, event): 99 | char = get_char(event.keysym_num) 100 | _, index0, index1 = self.area.isearch(char, index='insert', 101 | stopindex='1.0', regexp=False, backwards=True) 102 | 103 | self.area.mark_set('insert', index0) 104 | self.area.see('insert') 105 | 106 | def sel_data(self, event): 107 | self.area.addsel('insert', '(RANGE_SEL_MARK)') 108 | self.area.chmode('NORMAL') 109 | 110 | install = SeekSymbol 111 | 112 | 113 | -------------------------------------------------------------------------------- /vyapp/plugins/shift.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements Key-Commands to shift blocks of text. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: shift 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Shift to the right. 15 | 16 | Mode: NORMAL 17 | Event: 18 | Description: Shift to the left. 19 | 20 | """ 21 | 22 | class Shift: 23 | def __init__(self, area): 24 | area.install('shift', 25 | ('NORMAL', '', self.sel_right), 26 | ('NORMAL', '', self.sel_left)) 27 | self.area = area 28 | 29 | def sel_right(self, event): 30 | """ 31 | Shift ranges of selected text to the right. 32 | """ 33 | srow, scol = self.area.indexref('sel.first') 34 | erow, ecol = self.area.indexref('sel.last') 35 | self.area.shift_right(srow, erow, 1, self.area.tabchar) 36 | 37 | def sel_left(self, event): 38 | """ 39 | Shift ranges of selected text to the left. 40 | """ 41 | 42 | srow, scol = self.area.indexref('sel.first') 43 | erow, ecol = self.area.indexref('sel.last') 44 | self.area.shift_left(srow, erow, 1) 45 | 46 | install = Shift 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /vyapp/plugins/snakerr.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Offers syntax checking for python with pyflakes. 6 | 7 | Extern dependencies: 8 | pyflakes 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Namespace: snakerr 14 | 15 | Mode: PYTHON 16 | Event: 17 | Description: Show previous Pyflakes reports. 18 | 19 | Mode: PYTHON 20 | Event: 21 | Description: Run Pyflakes on the current file. 22 | with syntax errors. 23 | 24 | Mode: PYTHON 25 | Event: 26 | Description: Run Pyflakes on the whole current file project. 27 | 28 | Commands 29 | ======== 30 | 31 | Command: py_errors() 32 | Description: Run pyflakes over the current file and highlighs 33 | all lines with errors. It is possible to jump upwards/downwards 34 | using the same keys as defined in text_spots plugin. 35 | 36 | """ 37 | 38 | from subprocess import Popen, STDOUT, PIPE 39 | from os.path import relpath 40 | from vyapp.widgets import LinePicker 41 | from vyapp.plugins import Command 42 | from vyapp.tools import get_project_root 43 | from vyapp.app import root 44 | from vyapp.stderr import printd 45 | from re import findall 46 | import sys 47 | 48 | class PythonChecker: 49 | options = LinePicker() 50 | path = 'pyflakes' 51 | 52 | def __init__(self, area): 53 | self.area = area 54 | area.install('snakerr', ('PYTHON', '', self.check_module), 55 | ('PYTHON', '', self.display_errors), 56 | ('PYTHON', '', self.check_all)) 57 | 58 | @classmethod 59 | def c_path(cls, path): 60 | printd('Snakerr - Setting Pyflakes path = ', cls.path) 61 | cls.path = path 62 | 63 | def display_errors(self, event=None): 64 | root.status.set_msg('Pyflakes previous errors!') 65 | self.options.display() 66 | self.area.chmode('NORMAL') 67 | 68 | def check_all(self, event=None): 69 | path = get_project_root(self.area.filename) 70 | child = Popen([self.path, path], 71 | stdout=PIPE, stderr=STDOUT, encoding=self.area.charset) 72 | output = child.communicate()[0] 73 | 74 | # Pyflakes omit the column attribute when there are 75 | # syntax errors thus the (.+?) in the beggining of the 76 | # regex is necessary. 77 | regex = '(.+?):([0-9]+):?[0-9]*:(.+)' 78 | ranges = findall(regex, output) 79 | 80 | sys.stdout.write('Pyflakes found global errors:\n%s\n' % output) 81 | self.area.chmode('NORMAL') 82 | root.status.set_msg('Pyflakes errors: %s' % len(ranges)) 83 | 84 | if ranges: 85 | self.options(ranges) 86 | 87 | def check_module(self, event=None): 88 | path = get_project_root(self.area.filename) 89 | child = Popen([self.path, path], 90 | stdout=PIPE, stderr=STDOUT, encoding=self.area.charset) 91 | output = child.communicate()[0] 92 | 93 | regex = '(%s):([0-9]+):?[0-9]*:(.+)' % relpath(self.area.filename) 94 | ranges = findall(regex, output) 95 | sys.stdout.write('Errors:\n%s\n' % output) 96 | self.area.chmode('NORMAL') 97 | root.status.set_msg('Pyflakes errors: %s' % len(ranges)) 98 | 99 | if ranges: 100 | self.options(ranges) 101 | 102 | @Command() 103 | def py_errors(area): 104 | python_checker = PythonChecker(area) 105 | python_checker.check_all() 106 | 107 | install = PythonChecker 108 | 109 | -------------------------------------------------------------------------------- /vyapp/plugins/spacing.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Used to insert tabs based on the type of file being edited. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: spacing 11 | 12 | Mode: INSERT 13 | Event: 14 | Description: Insert a tab/space based on the programming file type. 15 | 16 | """ 17 | 18 | from vyapp.plugins import Command 19 | from os.path import splitext 20 | from vyapp.app import root 21 | 22 | class Tab: 23 | scheme = {} 24 | 25 | def __init__(self, area): 26 | self.area = area 27 | area.install('spacing', (-1, '<>', self.set_scm), 28 | (-1, '<>', self.set_scm), 29 | ('INSERT', '', self.insert_tabchar)) 30 | 31 | def set_scm(self, event): 32 | ph, ext = splitext(self.area.filename.lower()) 33 | # When no '' default is specified it uses size = 4 and char = ' '. 34 | size, char = self.scheme.get(ext, self.scheme.get('', (4, ' '))) 35 | 36 | self.area.settab(size, char) 37 | 38 | def insert_tabchar(self, event): 39 | self.area.indent() 40 | return 'break' 41 | 42 | @classmethod 43 | def set_scheme(cls, scheme={}): 44 | cls.scheme.update(scheme) 45 | 46 | @Command() 47 | def tabset(area, size, char): 48 | """ 49 | Change tab default size/char globally based 50 | on the actual areavi filename extension. 51 | """ 52 | 53 | ph, ext = splitext(area.filename.lower()) 54 | Tab.scheme[ext] = size, char 55 | area.tabsize = size 56 | area.tabchar = char 57 | root.status.set_msg('Tab size:char:%s:%s' % (size, repr(char))) 58 | 59 | install = Tab 60 | -------------------------------------------------------------------------------- /vyapp/plugins/spawn/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /vyapp/plugins/spawn/base_spawn.py: -------------------------------------------------------------------------------- 1 | from vyapp.app import root 2 | import signal 3 | 4 | class BaseSpawn: 5 | def __init__(self, cmd, input, output): 6 | self.cmd = cmd 7 | self.input = input 8 | self.output = output 9 | self.install_events() 10 | 11 | def install_events(self): 12 | """ 13 | 14 | """ 15 | self.input.hook('spawn', 'NORMAL', '', 16 | lambda event: self.dump_signal(signal.SIGINT), add=False) 17 | 18 | self.input.hook('spawn', 'NORMAL', '', 19 | lambda event: self.dump_signal(signal.SIGQUIT), add=False) 20 | 21 | # When one of the AreaVi instances are destroyed then 22 | # the process is killed. 23 | 24 | self.output.hook('spawn', -1, '', 25 | lambda event: self.terminate_process()) 26 | 27 | self.input.hook('spawn', -1, '', 28 | lambda event: self.terminate_process()) 29 | 30 | self.input.hook('spawn', 'NORMAL', '', 31 | lambda event: self.dump_line(), add=False) 32 | 33 | self.input.hook('spawn', 'INSERT', '', 34 | lambda event: self.dump_line(), add=False) 35 | 36 | root.status.set_msg('%s -> %s' % (self.input.filename, 37 | self.output.filename)) 38 | 39 | def dump_signal(self, num): 40 | pass 41 | 42 | def terminate_process(self): 43 | pass 44 | 45 | def dump_line(self): 46 | pass 47 | 48 | def handle_close(self, expect): 49 | pass 50 | -------------------------------------------------------------------------------- /vyapp/plugins/spawn/cross_platform.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Used to spawn processes and send/receive data. It is useful to talk with extern processes like interpreters. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: shell 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Send the cursor line to the process. 15 | 16 | Mode: INSERT 17 | Event: 18 | Description: Send the cursor line to the process and insert a line down. 19 | """ 20 | 21 | from untwisted.expect import Expect, LOAD, CLOSE 22 | from vyapp.plugins.spawn.base_spawn import BaseSpawn 23 | from vyapp.plugins import Command 24 | from vyapp.plugins import ENV 25 | from vyapp.app import root 26 | from os import environ 27 | 28 | import shlex 29 | 30 | class Spawn(BaseSpawn): 31 | def __init__(self, cmd): 32 | self.expect = Expect(*shlex.split(cmd), env=environ) 33 | 34 | def install_events(self): 35 | super(Spawn, self).install_events() 36 | 37 | # When call.terminnate is called it may happen of having still data to be 38 | # processed. It would attempt to write on an AreaVi instance that no more exist. 39 | # So, it executes quietly the AreaVi.append method. 40 | self.expect.add_map(LOAD, lambda expect, data: self.output.append(data)) 41 | self.expect.add_map(CLOSE, self.handle_close) 42 | 43 | def dump_signal(self, num): 44 | self.expect.child.send_signal(num) 45 | 46 | def terminate_process(self): 47 | # Exceptions should be written to sys.stdout for default. 48 | self.expect.terminate() 49 | root.status.set_msg('Killed process!') 50 | 51 | def dump_line(self): 52 | data = self.input.get('insert linestart', 'insert +1l linestart') 53 | data = data.encode(self.input.charset) 54 | self.expect.send(data) 55 | self.input.down() 56 | 57 | def handle_close(self, expect): 58 | root.status.set_msg('Killed process!') 59 | expect.destroy() 60 | 61 | class HSpawn(Spawn): 62 | def __init__(self, cmd): 63 | Spawn.__init__(self, cmd) 64 | BaseSpawn.__init__(self, cmd, Command.area, 65 | Command.area.master.master.create()) 66 | 67 | class VSpawn(Spawn): 68 | def __init__(self, cmd): 69 | Spawn.__init__(self, cmd) 70 | BaseSpawn.__init__(self, cmd, Command.area, 71 | Command.area.master.master.master.create()) 72 | 73 | ENV['hspawn'] = HSpawn 74 | ENV['vspawn'] = VSpawn 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /vyapp/plugins/spawn/unix_platform.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Used to spawn new processes, this module works only on unix-like 6 | platforms. 7 | 8 | Commands 9 | ======== 10 | 11 | Command: hbash 12 | Description: Start a new bash process whose output 13 | is directed to a horizontal pane. 14 | 15 | Command: vbash 16 | Description: Start a new bash process whose output 17 | is directed to a vertical pane. 18 | 19 | Command: vpy 20 | Description: Start a python interpreter process 21 | in a vertical pane. 22 | 23 | Command: hpy 24 | Description: Start a python interpreter process 25 | in a horizontal pane. 26 | 27 | Command: vrb 28 | Description: Start a ruby interpreter process 29 | in a vertical pane. 30 | 31 | Command: hrb 32 | Description: Start a ruby interpreter process 33 | in a horizontal pane. 34 | 35 | Notes 36 | ===== 37 | 38 | **Run python from your bash process** 39 | 40 | tee -i >(stdbuf -o 0 python -i -u) 41 | 42 | **Run ruby from your bash process** 43 | 44 | stdbuf -o 0 irb --inf-ruby-mode 45 | 46 | The above commands could be slightly modified 47 | to work with other interpreters. 48 | 49 | """ 50 | 51 | from untwisted.file_writer import FileWriter 52 | from untwisted.file_reader import FileReader 53 | from untwisted.event import LOAD, CLOSE 54 | from vyapp.plugins import Command 55 | from untwisted.network import Device 56 | from vyapp.plugins import ENV 57 | from subprocess import Popen, PIPE, STDOUT 58 | from os import environ, setsid, killpg 59 | from vyapp.plugins.spawn.base_spawn import BaseSpawn 60 | from vyapp.app import root 61 | 62 | class Spawn(BaseSpawn): 63 | def __init__(self, cmd): 64 | self.child = Popen(cmd, 65 | shell=True, stdout=PIPE, stdin=PIPE, preexec_fn=setsid, 66 | stderr=STDOUT, env=environ) 67 | 68 | 69 | self.stdout = Device(self.child.stdout) 70 | self.stdin = Device(self.child.stdin) 71 | 72 | def install_events(self): 73 | super(Spawn, self).install_events() 74 | 75 | FileReader(self.stdout) 76 | FileWriter(self.stdin) 77 | 78 | self.stdout.add_map(LOAD, lambda con, data: \ 79 | self.output.append(data)) 80 | 81 | self.stdin.add_map(CLOSE, self.handle_close) 82 | self.stdout.add_map(CLOSE, self.handle_close) 83 | 84 | def dump_signal(self, num): 85 | killpg(self.child.pid, num) 86 | 87 | def terminate_process(self): 88 | self.child.kill() 89 | root.status.set_msg('Killed process!') 90 | 91 | def dump_line(self): 92 | data = self.input.get('insert linestart', 'insert +1l linestart') 93 | data = data.encode(self.input.charset) 94 | self.stdin.dump(data) 95 | self.input.down() 96 | 97 | def handle_close(self, dev, err): 98 | root.status.set_msg('Killed process!') 99 | self.stdout.destroy() 100 | self.stdin.destroy() 101 | 102 | class HSpawn(Spawn): 103 | def __init__(self, cmd): 104 | Spawn.__init__(self, cmd) 105 | BaseSpawn.__init__(self, cmd, Command.area, 106 | Command.area.master.master.create()) 107 | 108 | class VSpawn(Spawn): 109 | def __init__(self, cmd): 110 | Spawn.__init__(self, cmd) 111 | BaseSpawn.__init__(self, cmd, Command.area, 112 | Command.area.master.master.master.create()) 113 | 114 | ENV['hspawn'] = HSpawn 115 | ENV['vspawn'] = VSpawn 116 | ENV['vbash'] = lambda : VSpawn('bash -i') 117 | ENV['hbash'] = lambda : HSpawn('bash -i') 118 | ENV['hpy'] = lambda : HSpawn('bash -c "tee -i >(stdbuf -o 0 python -i -u)"') 119 | ENV['vpy'] = lambda : VSpawn('bash -c "tee -i >(stdbuf -o 0 python -i -u)"') 120 | 121 | ENV['hrb'] = lambda : HSpawn('bash -c "stdbuf -o 0 irb --inf-ruby-mode"') 122 | ENV['vrb'] = lambda : VSpawn('bash -c "stdbuf -o 0 irb --inf-ruby-mode"') 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /vyapp/plugins/splits.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Panels are a cool way to perform some tasks. This plugin implements Key-Commands to create horizontal/vertical 6 | panes. 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: splits 12 | 13 | Mode: Global 14 | Event: 15 | Description: Add a vertical pane. 16 | 17 | Mode: Global 18 | Event: 19 | Description: Add a horizontal pane. 20 | 21 | Mode: Global 22 | Event: 23 | Description: Remove a pane. 24 | 25 | Mode: Global 26 | Event: 27 | Description: Change focus one pane left. 28 | 29 | Mode: Global 30 | Event: 31 | Description: Change focus one pane right. 32 | 33 | Mode: Global 34 | Event: 35 | Description: Change focus one pane up. 36 | 37 | Mode: Global 38 | Event: 39 | Description: Change focus one pane down. 40 | 41 | """ 42 | 43 | from vyapp.app import root 44 | 45 | def add_vertical_area(area): 46 | """ 47 | It opens a vertical area. 48 | """ 49 | 50 | vpane = area.master.master.master 51 | vpane.create() 52 | 53 | wids = vpane.panes() 54 | height = root.winfo_height()//(len(wids) + 1) 55 | root.update() 56 | 57 | for ind in range(0, len(wids) - 1): 58 | vpane.sash_place(ind, 0, (ind + 1) * height) 59 | return 'break' 60 | 61 | def add_horizontal_area(area): 62 | """ 63 | It creates a new horizontal area. 64 | """ 65 | 66 | hpane = area.master.master 67 | hpane.create() 68 | 69 | wids = hpane.panes() 70 | width = root.winfo_width()//(len(wids) + 1) 71 | root.update() 72 | 73 | for ind in range(0, len(wids) - 1): 74 | hpane.sash_place(ind, (ind + 1) * width, 0) 75 | 76 | return 'break' 77 | 78 | def remove_area(area): 79 | """ 80 | It removes the focused area. 81 | """ 82 | 83 | vpanes = len(area.master.master.master.panes()) 84 | hpanes = len(area.master.master.panes()) 85 | 86 | if vpanes == 1 and hpanes == 1: return 87 | area.master.destroy() 88 | 89 | if not area.master.master.panes(): 90 | area.master.master.destroy() 91 | 92 | root.note.restore_area_focus() 93 | return 'break' 94 | 95 | 96 | def install(area): 97 | area.install('splits', 98 | (-1, '', lambda event: add_horizontal_area(event.widget)), 99 | (-1, '', lambda event: add_vertical_area(event.widget)), 100 | (-1, '', lambda event: remove_area(event.widget))) 101 | 102 | -------------------------------------------------------------------------------- /vyapp/plugins/symbol_jumps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements two Key-Commands to do quick jumps with the cursor to match 6 | the symbols. 7 | 8 | ( ) { } [ ] 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Namespace: symbol-jumps 14 | 15 | Mode: NORMAL 16 | Event: 17 | Description: Place the cursor at the next occurrence of ( ) { } [ ] 18 | 19 | 20 | Mode: NORMAL 21 | Event: 22 | Description: Place the cursor at the next occurrence of ( ) { } [ ] 23 | 24 | """ 25 | from re import escape 26 | 27 | class SymbolJumps: 28 | def __init__(self, area, chars): 29 | area.install('symbol-jumps', 30 | ('NORMAL', '', self.next_sym), 31 | ('NORMAL', '', self.prev_sym)) 32 | 33 | self.chars = chars 34 | self.area = area 35 | 36 | def next_sym(self, event): 37 | """ 38 | Place the cursor at the next occurrence of one of the chars. 39 | """ 40 | 41 | chars = [escape(ind) for ind in self.chars] 42 | REG = '|'.join(chars) 43 | 44 | _, index0, index1 = self.area.isearch(REG, index='insert', 45 | stopindex='end', regexp=True) 46 | 47 | self.area.mark_set('insert', index1) 48 | self.area.see('insert') 49 | 50 | def prev_sym(self, event): 51 | """ 52 | Place the cursor at the previous occurrence of one of the chars. 53 | """ 54 | 55 | chars = [escape(ind) for ind in self.chars] 56 | REG = '|'.join(chars) 57 | 58 | _, index0, index1 = self.area.isearch(REG, index='insert', 59 | backwards=True, stopindex='1.0', regexp=True) 60 | 61 | self.area.mark_set('insert', index0) 62 | self.area.see('insert') 63 | 64 | install = SymbolJumps 65 | 66 | 67 | -------------------------------------------------------------------------------- /vyapp/plugins/syntax/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyapp/vy/4ba0d379e21744fd79a740e8aeaba3a0a779973c/vyapp/plugins/syntax/__init__.py -------------------------------------------------------------------------------- /vyapp/plugins/syntax/keys.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | """ 4 | 5 | DEFAULT = ('Token.Comment.Multiline', 6 | 'Token.Literal.String.Doc', 7 | 'Token.Literal.String.Heredoc', 8 | 'Token.Literal.String.Regex', 9 | 'Token.Literal.String.Symbol', 10 | 'Token.Literal.String', 11 | 'Token.Literal.String.Other', 12 | 'Token.Literal.String.Single', 13 | 'Token.Comment.Preproc', 14 | 'Token.Comment', 15 | 'Token.Comment.Single', 16 | 'Token.Comment.Special', 17 | 'Token.Literal.String.Double', 18 | 'Token.Literal.String.Backtick', 19 | 'Token.Literal.String.Char', 20 | 'Token.Literal.Number.Float', 21 | 'Token.Punctuation', 22 | 'Token.Operator', 23 | 'Token.Name', 24 | 'Token.Name.Tag', 25 | 'Token.Text', 26 | 'Token.Keyword', 27 | 'Token.Generic', 28 | 'Token.Generic.Deleted', 29 | 'Token.Generic.Emph', 30 | 'Token.Generic.Error', 31 | 'Token.Generic.Heading', 32 | 'Token.Generic.Inserted', 33 | 'Token.Generic.Output', 34 | 'Token.Generic.Prompt', 35 | 'Token.Generic.Strong', 36 | 'Token.Generic.Subheading', 37 | 'Token.Generic.Traceback') 38 | 39 | 40 | HTML = ('Token.Text', 41 | 'Token.Comment.Multiline', 42 | 'Token.Literal.String.Doc', 43 | 'Token.Literal.String.Heredoc', 44 | 'Token.Literal.String.Regex', 45 | 'Token.Literal.String.Symbol', 46 | 'Token.Literal.String', 47 | 'Token.Literal.String.Other', 48 | 'Token.Literal.String.Single', 49 | 'Token.Comment.Preproc', 50 | 'Token.Comment', 51 | 'Token.Comment.Single', 52 | 'Token.Comment.Special', 53 | 'Token.Literal.String.Double', 54 | 'Token.Literal.String.Backtick', 55 | 'Token.Literal.String.Char', 56 | 'Token.Literal.Number.Float', 57 | 'Token.Punctuation', 58 | 'Token.Operator', 59 | 'Token.Name', 60 | 'Token.Name.Tag', 61 | 'Token.Keyword', 62 | 'Token.Generic', 63 | 'Token.Generic.Deleted', 64 | 'Token.Generic.Emph', 65 | 'Token.Generic.Error', 66 | 'Token.Generic.Heading', 67 | 'Token.Generic.Inserted', 68 | 'Token.Generic.Output', 69 | 'Token.Generic.Prompt', 70 | 'Token.Generic.Strong', 71 | 'Token.Generic.Subheading', 72 | 'Token.Generic.Traceback') 73 | 74 | PRECEDENCE_TABLE = { 75 | ('html', ) :HTML 76 | 77 | } 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /vyapp/plugins/syntax/spider.py: -------------------------------------------------------------------------------- 1 | """ 2 | :) 3 | """ 4 | from vyapp.plugins.syntax.tools import get_tokens_unprocessed_matrix 5 | from vyapp.plugins.syntax.keys import PRECEDENCE_TABLE, DEFAULT 6 | from pygments.lexers import get_lexer_for_filename, guess_lexer 7 | 8 | 9 | class Spider: 10 | def __init__(self, area, theme, max=10): 11 | self.area = area 12 | self.max = max 13 | self.theme = theme 14 | self.styles = theme.styles 15 | self.default_style = getattr(theme, 'default_style', '#957C8B') 16 | self.default_style = self.default_style if self.default_style else '#957C8B' 17 | 18 | self.default_background = theme.background_color \ 19 | if theme.background_color else 'black' 20 | self.lexer = None 21 | area.configure(background = self.default_background) 22 | area.configure(foreground = self.default_style) 23 | 24 | for ind in self.styles.keys(): 25 | self.set_token_style(ind) 26 | 27 | area.install('syntax', (-1, '<>', 28 | lambda event: self.update_all()), 29 | (-1, '<>', lambda event: self.update_all()), 30 | (-1, '', lambda event: self.update())) 31 | 32 | def set_lexer(self): 33 | """ 34 | Try to detect the lexer by filename if it fails 35 | then try to guess the lex by shebang statement. 36 | 37 | The shebang statement should be placed in the first 38 | 20 lines of the file. 39 | """ 40 | 41 | try: 42 | self.lexer = get_lexer_for_filename(self.area.filename, '') 43 | except Exception as e: 44 | self.lexer = guess_lexer(self.area.get('1.0', '20.0')) 45 | 46 | def update_all(self): 47 | """ 48 | Colorize all text in the widget. 49 | """ 50 | 51 | # When it need to update all the text 52 | # just save the lexer for later usage. 53 | self.set_lexer() 54 | self.tag_tokens('1.0', 'end') 55 | 56 | def update(self): 57 | """ 58 | Update a small range of the text. It is mostly called 59 | when Escape is pressed. 60 | """ 61 | 62 | TAG_KEYS_PRECEDENCE = PRECEDENCE_TABLE.get( 63 | tuple(self.lexer.aliases), DEFAULT) 64 | 65 | index0 = self.area.index('@0,0') 66 | index0 = self.area.index('%s -%sl' % (index0, self.max)) 67 | index0 = self.area.tag_next_occur(TAG_KEYS_PRECEDENCE, 68 | index0, 'insert', '1.0') 69 | 70 | index1 = '@%s,%s' % (self.area.winfo_height(), 71 | self.area.winfo_width()) 72 | 73 | index2 = self.area.index(index1) 74 | index2 = self.area.index('%s +%sl' % (index1, self.max)) 75 | 76 | index2 = self.area.tag_prev_occur(TAG_KEYS_PRECEDENCE, 77 | index2, 'insert', 'end') 78 | 79 | for ind in self.styles.keys(): 80 | self.area.tag_remove(str(ind), index0, index2) 81 | self.tag_tokens(index0, index2) 82 | 83 | def tag_tokens(self, index, stopindex): 84 | """ 85 | Add the token'tag to each range of text. 86 | """ 87 | 88 | count, offset = self.area.indexref(index) 89 | tokens = get_tokens_unprocessed_matrix(count, offset, 90 | self.area.get(index, stopindex), self.lexer) 91 | 92 | for ((srow, scol), (erow, ecol)), token, value in tokens: 93 | self.area.tag_add(str(token), '%s.%s' % (srow, 94 | scol), '%s.%s' % (erow, ecol)) 95 | 96 | def set_token_style(self, token): 97 | """ 98 | Configure the tag which maps to the token in the 99 | AreaVi. 100 | 101 | If there is no such a definition of token 102 | in styles dict then it defaults to self.background 103 | and self.default_style. 104 | """ 105 | 106 | tag = str(token) 107 | conf = self.theme.style_for_token(token) 108 | 109 | self.area.tag_configure(tag, 110 | foreground='#%s' % conf['color'] if conf['color'] \ 111 | else self.default_style, 112 | background='#%s' % conf['bgcolor'] if conf['bgcolor'] else \ 113 | self.default_background, 114 | underline=conf['underline']) 115 | 116 | # Note: It may be interesting to redefine 117 | # tag_configure in AreaVi and implement it there. 118 | self.area.tag_lower(tag, 'sel') 119 | 120 | install = Spider 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /vyapp/plugins/syntax/styles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyapp/vy/4ba0d379e21744fd79a740e8aeaba3a0a779973c/vyapp/plugins/syntax/styles/__init__.py -------------------------------------------------------------------------------- /vyapp/plugins/syntax/styles/vy.py: -------------------------------------------------------------------------------- 1 | from pygments.style import Style 2 | from pygments.token import Keyword, Name, Comment, String, Error, \ 3 | Number, Operator, Generic, Whitespace, Token, Punctuation, Text 4 | 5 | 6 | class VyStyle(Style): 7 | """ 8 | """ 9 | 10 | background_color = "#000000" 11 | default_style = "#957C8B" 12 | 13 | styles = { 14 | Token: "#cccccc", 15 | # Whitespace: "#957C8B", 16 | # Note: The Text tokens are set to default_style. So, when inserting chars. 17 | # it gets highlighed afterwards. 18 | Text: '#957C8B', 19 | Comment: "#ffbf00", 20 | Comment.Hashbang: "#006680", 21 | Comment.Multiline: "#807100", 22 | Comment.Preproc: "#ff8000", 23 | Comment.Single: "#f55600", 24 | Comment.Special: "#cd0000", 25 | 26 | Keyword: "#3E9D40", 27 | # Keyword.Constant: "", 28 | # Keyword.Declaration: "#3E9D40", 29 | # Keyword.Namespace: "#3E9D40", 30 | # Keyword.Pseudo: "#3E9D40", 31 | # Keyword.Reserved: "#3E9D40", 32 | # Keyword.Type: "#3E9D40", 33 | 34 | Operator: "#957C8B", 35 | Operator.Word: "#3E9D40", 36 | 37 | Punctuation: "#957C8B", 38 | Name: "#957C8B", 39 | Name.Attribute: "#957C8B", 40 | Name.Builtin: "#999999", 41 | Name.Class: "#00cdcd", 42 | # Name.Constant: "", 43 | Name.Decorator: "#cd00cd", 44 | # Name.Entity: "", 45 | Name.Function: "#D244D2", 46 | # Name.Label: "", 47 | # Name.Namespace: "", 48 | # Name.Other: "", 49 | # Name.Tag: "", 50 | Name.Exception: "#666699", 51 | Name.Variable: "#00cdcd", 52 | 53 | String: "#3E6F9D", 54 | # String.Single: "#805E00", 55 | 56 | String.Backtick: "#FF470A", 57 | String.Char: "#FF470A", 58 | String.Doc: "#f0d400", 59 | String.Regex: "#82ad00", 60 | String.Symbol: "#657439", 61 | Number: "#cd00cd", 62 | 63 | Generic.Heading: "#000080", 64 | Generic.Subheading: "#800080", 65 | Generic.Deleted: "#cd0000", 66 | Generic.Inserted: "#00cd00", 67 | Generic.Error: "#FF0000", 68 | # Generic.Emph: "", 69 | # Generic.Strong: "", 70 | Generic.Prompt: "#000080", 71 | Generic.Output: "#888", 72 | Generic.Traceback: "#04D", 73 | 74 | Error: "#FF0000" 75 | } 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /vyapp/plugins/syntax/tools.py: -------------------------------------------------------------------------------- 1 | def thread_colorize(area, lexer, theme, index, stopindex): 2 | for pos, token, value in lexer.get_tokens_unprocessed(area.get(index, stopindex)): 3 | area.tag_add(str(token), '%s +%sc' % (index, pos), 4 | '%s +%sc' % (index, pos + len(value))) 5 | 6 | yield 7 | 8 | def matrix_step(map): 9 | count, offset = 0, 0 10 | for pos, token, value in map: 11 | srow = count 12 | scol = pos - offset 13 | n = value.count('\n') 14 | erow = srow + n 15 | count = count + n 16 | m = value.rfind('\n') 17 | offset = pos + m + 1 if m >= 0 else offset 18 | ecol = len(value) - (m + 1) if m >= 0 else scol + len(value) 19 | yield(((srow, scol), (erow, ecol)), token, value) 20 | 21 | 22 | def get_tokens_unprocessed_matrix(count, offset, data, lexer): 23 | map = matrix_step(lexer.get_tokens_unprocessed(data)) 24 | 25 | for ((srow, scol), (erow, ecol)), token, value in map: 26 | if '\n' in value: 27 | yield(((srow + count, scol + offset), 28 | (erow + count, ecol)), token, value) 29 | break 30 | else: 31 | yield(((srow + count, scol + offset), 32 | (erow + count, ecol + offset)), 33 | token, value) 34 | 35 | for ((srow, scol), (erow, ecol)), token, value in map: 36 | yield(((srow + count, scol), (erow + count, ecol)), 37 | token, value) 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /vyapp/plugins/syslog.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin displays a Text window containing all the output of the 6 | python interpreter underlying Vy. 7 | 8 | When code is run to change Vy state it may print out debugging 9 | information to sys.stdout. Such a data is captured 10 | and shown on the CmdOutput window. 11 | 12 | Key-Commands 13 | ============ 14 | 15 | Namespace: syslog 16 | Mode: Global 17 | Event: 18 | Description: Display sys.stdout log on window. 19 | """ 20 | from vyapp.widgets import TextWindow 21 | import sys 22 | 23 | class CmdOutput: 24 | """ 25 | """ 26 | 27 | def __init__(self, win): 28 | self.win = win 29 | 30 | def write(self, data): 31 | self.win.text.insert('end', data) 32 | self.win.text.see('end') 33 | 34 | def __eq__(self, other): 35 | return self.win.text == other 36 | 37 | class Syslog: 38 | win = TextWindow('', title='Cmd Output') 39 | win.withdraw() 40 | 41 | cmd_output = CmdOutput(win) 42 | sys.stdout.append(cmd_output) 43 | sys.stdout.add_default(cmd_output) 44 | 45 | def __init__(self, area): 46 | self.area = area 47 | area.install('syslog', (-1, '', self.view_log)) 48 | 49 | def view_log(self, event): 50 | self.win.display() 51 | return 'break' 52 | 53 | install = Syslog -------------------------------------------------------------------------------- /vyapp/plugins/tab_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Key-Commands 6 | ============ 7 | 8 | Namespace: tab-search 9 | 10 | """ 11 | 12 | from vyapp.ask import Get 13 | from vyapp.app import root 14 | 15 | class TabSearch: 16 | def __init__(self, area): 17 | self.area = area 18 | 19 | area.install('tab-search', 20 | (-1, '', self.on_next_mode), 21 | (-1, '', self.on_back_mode)) 22 | 23 | def on_next_mode(self, event): 24 | get = Get(events={'<>': self.switch_next, 25 | '': self.switch_next, 26 | '': self.switch_back, 27 | '': self.stop, 28 | '': self.stop}) 29 | 30 | return 'break' 31 | 32 | def on_back_mode(self, event): 33 | get = Get(events={ 34 | '<>': self.switch_back, 35 | '': self.switch_next, 36 | '': self.switch_back, 37 | '': self.stop, 38 | '': self.stop}) 39 | 40 | return 'break' 41 | 42 | def switch_next(self, wid): 43 | """ 44 | """ 45 | data = wid.get() 46 | seq = root.note.next(lambda text: data in text) 47 | elem = next(seq) 48 | root.note.on(elem) 49 | 50 | wid = root.note.nametowidget(root.note.select()) 51 | root.title('Vy %s' % wid.focused_area.filename) 52 | 53 | def switch_back(self, wid): 54 | """ 55 | """ 56 | 57 | data = wid.get() 58 | seq = root.note.back(lambda text: data in text) 59 | elem = next(seq) 60 | root.note.on(elem) 61 | 62 | wid = root.note.nametowidget(root.note.select()) 63 | root.title('Vy %s' % wid.focused_area.filename) 64 | 65 | def stop(self, wid): 66 | root.note.set_area_focus() 67 | return True 68 | 69 | install = TabSearch 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /vyapp/plugins/tabs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Tabs are a great feature when manipulating several files. This plugin implements Key-Commands to create, 6 | open files, change the focus between opened tabs. 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: tabs 12 | 13 | Mode: Global 14 | Event: 15 | Description: It pops a file selection window to load the contents of a file in a new tab. 16 | 17 | Mode: Global 18 | Event: 19 | Description: It creates a new blank tab. 20 | 21 | Mode: Global 22 | Event: 23 | Description: It removes the focused tab. 24 | 25 | Mode: Global 26 | Event: 27 | Description: It changes the focus left from a tab. 28 | 29 | Mode: Global 30 | Event: 31 | Description: It changes the focus right from a tab. 32 | 33 | """ 34 | 35 | from vyapp.app import root 36 | # from tkinter.messagebox import * 37 | from tkinter.filedialog import askopenfilename 38 | 39 | def load_tab(): 40 | """ 41 | It pops a askopenfilename window to drop 42 | the contents of a file into another tab's text area. 43 | """ 44 | 45 | filename = askopenfilename() 46 | 47 | # If i don't check it ends up cleaning up 48 | # the text area when one presses cancel. 49 | 50 | if not filename: 51 | return 'break' 52 | 53 | try: 54 | root.note.load([ [filename] ]) 55 | except Exception: 56 | root.status.set_msg('It failed to load.') 57 | else: 58 | root.status.set_msg('File loaded.') 59 | return 'break' 60 | 61 | def create_tab(): 62 | root.note.create('none') 63 | return 'break' 64 | 65 | def remove_tab(): 66 | """ 67 | It removes the selected tab. 68 | """ 69 | 70 | if len(root.note.tabs()) <= 1: return 71 | name = root.note.select() 72 | wid = root.note.nametowidget(name) 73 | wid.destroy() 74 | root.note.select(0) 75 | root.note.set_area_focus() 76 | 77 | # We don't need to call forget after destroy. 78 | # It seems the method forget from note doesnt destroy 79 | # the widget at all consequently the event isn't 80 | # spreaded. 81 | # root.note.forget(wid) 82 | 83 | return 'break' 84 | 85 | def select_left(): 86 | """ 87 | """ 88 | 89 | root.note.select(root.note.index(root.note.select()) - 1) 90 | root.note.set_area_focus() 91 | return 'break' 92 | 93 | def select_right(): 94 | """ 95 | """ 96 | 97 | root.note.select(root.note.index(root.note.select()) + 1) 98 | root.note.set_area_focus() 99 | return 'break' 100 | 101 | def install(area): 102 | area.install('tabs', (-1, '', lambda event: load_tab()), 103 | (-1, '', lambda event: create_tab()), 104 | (-1, '', lambda event: remove_tab()), 105 | (-1, '', lambda event: select_left()), 106 | (-1, '', lambda event: select_right())) 107 | -------------------------------------------------------------------------------- /vyapp/plugins/ternjs/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /vyapp/plugins/ternjs/completer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This module implements javascript autocompletion using the tern javascript library. 6 | 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: ternjs 12 | 13 | Mode: INSERT 14 | Event: 15 | Description: Open the completion window with possible python words for 16 | completion. 17 | """ 18 | 19 | from vyapp.completion import CompletionWindow, Option 20 | from os.path import expanduser, join, exists, dirname 21 | from vyapp.plugins import Command 22 | from subprocess import Popen, PIPE 23 | from shutil import copyfile 24 | import json 25 | import requests 26 | import sys 27 | import atexit 28 | 29 | filename = join(expanduser('~'), '.tern-config') 30 | if not exists(filename): 31 | copyfile(join(dirname(__file__), 'tern-config'), filename) 32 | 33 | class JavascriptCompletionWindow(CompletionWindow): 34 | """ 35 | """ 36 | 37 | def __init__(self, area, *args, **kwargs): 38 | # If ~/.tern-port doesn't exist then run the server. 39 | source = area.get('1.0', 'end') 40 | line, col = area.indexref() 41 | 42 | if not exists(join(expanduser('~'), '.tern-port')): 43 | self.run_server() 44 | 45 | completions = self.completions(source, line - 1, col, area.filename) 46 | CompletionWindow.__init__(self, area, completions, *args, **kwargs) 47 | self.bind('', lambda event: 48 | sys.stdout.write('/*%s*/\n%s\n' % ('*' * 80, 49 | self.box.selection_docs()))) 50 | 51 | def run_server(self): 52 | self.child = Popen([JavascriptCompletion.PATH, 53 | '--port', str(JavascriptCompletion.PORT), '--persistent'], 54 | stdin=PIPE, stdout=PIPE, stderr=PIPE) 55 | 56 | self.child.stdout.readline() 57 | atexit.register(self.child.terminate) 58 | 59 | def completions(self, data, line, col, filename): 60 | """ 61 | """ 62 | 63 | payload = { 64 | 'query': { 65 | 'type': 'completions', 66 | 'file':filename, 67 | 'docs': 'true', 68 | 'types': 'true', 69 | 'end': {'line': line, 'ch':col}, 70 | }, 71 | 72 | 'files': [{"type": "full", 73 | "name": filename, 74 | "text": data}] 75 | } 76 | 77 | addr = 'http://localhost:%s' % JavascriptCompletion.PORT 78 | req = requests.post(addr, data=json.dumps(payload)) 79 | return self.build(req.text) 80 | 81 | def build(self, data): 82 | data = json.loads(data) 83 | return [Option(**ind) for ind in data['completions']] 84 | 85 | class JavascriptCompletion: 86 | PATH = 'tern' 87 | PORT = 1234 88 | 89 | def __init__(self, area): 90 | trigger = lambda event: area.hook('ternjs', 'INSERT', 91 | '', lambda event: JavascriptCompletionWindow( 92 | event.widget), add=False) 93 | 94 | remove_trigger = lambda event: area.unhook( 95 | 'INSERT', '') 96 | 97 | area.install('javascript-completion', 98 | (-1, '<>', trigger), 99 | (-1, '<>', trigger), 100 | (-1, '<>', trigger), 101 | (-1, '<>', trigger), 102 | (-1, '<>', remove_trigger), 103 | (-1, '<>', remove_trigger)) 104 | 105 | install = JavascriptCompletion 106 | @Command() 107 | def acj(area): 108 | area.hook('javascript-completion', 'INSERT', '', 109 | lambda event: JavascriptCompletionWindow(event.widget), add=False) 110 | 111 | 112 | -------------------------------------------------------------------------------- /vyapp/plugins/ternjs/tern-config: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [ 3 | "browser", 4 | "jquery" 5 | ], 6 | "loadEagerly": [ 7 | "importantfile.js" 8 | ], 9 | "plugins": { 10 | "requirejs": { 11 | "baseURL": "./", 12 | "paths": {} 13 | }, 14 | "node": {} 15 | } 16 | } 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /vyapp/plugins/text_anchors.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Key-Commands 4 | ============ 5 | 6 | Namespace: anchors 7 | 8 | """ 9 | from vyapp.app import root 10 | from tkinter import TclError 11 | 12 | class Anchors: 13 | def __init__(self, area): 14 | area.add_mode('ANCHORS-JUMP') 15 | area.add_mode('ANCHORS-DROP') 16 | self.area = area 17 | 18 | area.install('anchors', 19 | ('NORMAL', '', self.switch_jump), 20 | ('NORMAL', '', self.switch_drop), 21 | ('ANCHORS-DROP', '', self.drop), 22 | ('ANCHORS-JUMP', '', self.jump)) 23 | 24 | def switch_jump(self, event): 25 | self.area.chmode('ANCHORS-JUMP') 26 | root.status.set_msg('Jump to:') 27 | 28 | def switch_drop(self, event): 29 | self.area.chmode('ANCHORS-DROP') 30 | root.status.set_msg('Drop on:') 31 | 32 | def drop(self, event): 33 | self.area.mark_set('(ANCHORS-%s)' % event.keysym, 'insert') 34 | index = self.area.index('insert') 35 | root.status.set_msg('Droped: (%s) at %s' % (event.keysym, index)) 36 | self.area.chmode('NORMAL') 37 | 38 | def jump(self, event): 39 | index = '(ANCHORS-%s)' % event.keysym 40 | 41 | try: 42 | self.area.seecur(index) 43 | except TclError as error: 44 | root.status.set_msg('Bad index: (%s)' % event.keysym) 45 | else: 46 | root.status.set_msg('Jumped: (%s)' % event.keysym) 47 | self.area.chmode('NORMAL') 48 | 49 | install = Anchors -------------------------------------------------------------------------------- /vyapp/plugins/text_jumps.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Overview 4 | ======== 5 | 6 | This plugin implements two Key-Commands to make the cursor jump to specific 7 | region of texts. 8 | 9 | Key-Commands 10 | ============ 11 | 12 | Namespace: text-jumps 13 | 14 | Mode: NORMAL 15 | Event: 16 | Description: Place the cursor at the beginning of the file. 17 | 18 | 19 | Mode: NORMAL 20 | Event: 21 | Description: Place the cursor at the end of the file. 22 | 23 | Mode: NORMAL 24 | Event: 25 | Description: Place the cursor at the beginning of the line. 26 | 27 | 28 | Mode: NORMAL 29 | Event: 30 | Description: Place the cursor at the end of the line. 31 | 32 | Mode: NORMAL 33 | Event: 34 | Description: Place the cursor at the beginning of the next word. 35 | 36 | 37 | Mode: NORMAL 38 | Event: 39 | Description: Place the cursor at the beginning of the previous word. 40 | """ 41 | 42 | class TextJumps: 43 | def __init__(self, area): 44 | area.install('text-jumps', 45 | ('NORMAL', '', self.text_start), 46 | ('NORMAL', '', self.text_end), 47 | ('NORMAL', '', self.line_start), 48 | ('NORMAL', '', self.line_end), 49 | ('NORMAL', '', self.next_word), 50 | ('NORMAL', '', self.prev_word)) 51 | self.area = area 52 | 53 | def text_start(self, event): 54 | """ 55 | Place the cursor at the beginning of the file. 56 | """ 57 | 58 | self.area.mark_set('insert', '1.0') 59 | self.area.see('insert') 60 | 61 | def text_end(self, event): 62 | """ 63 | Place the cursor at the end of the file. 64 | """ 65 | 66 | self.area.mark_set('insert', 'end linestart') 67 | self.area.see('insert') 68 | 69 | def line_start(self, event): 70 | """ 71 | Place the cursor at the beginning of the line. 72 | """ 73 | 74 | self.area.mark_set('insert', 'insert linestart') 75 | 76 | 77 | def line_end(self, event): 78 | """ 79 | Place the cursor at the end of the line. 80 | """ 81 | 82 | self.area.mark_set('insert', 'insert lineend') 83 | 84 | def next_word(self, event): 85 | """ 86 | Place the cursor at the next word. 87 | """ 88 | 89 | _, index0, index1 = self.area.isearch('\M', index='insert', 90 | regexp=True, stopindex='end') 91 | 92 | self.area.mark_set('insert', index0) 93 | self.area.see('insert') 94 | 95 | def prev_word(self, event): 96 | """ 97 | Place the cursor at the previous word. 98 | """ 99 | 100 | _, index0, index1 = self.area.isearch('\M', backwards=True, 101 | regexp=True, index='insert', stopindex='1.0') 102 | 103 | self.area.mark_set('insert', index1) 104 | self.area.see('insert') 105 | 106 | install = TextJumps 107 | 108 | -------------------------------------------------------------------------------- /vyapp/plugins/text_spots.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements a handy functionality that is shading lines and being 6 | capable of making the cursor jump back/next to these shaded lines. It is basically a 7 | mark system. 8 | 9 | Key-Commands 10 | ============ 11 | 12 | Namespace: text-spots 13 | 14 | Mode: NORMAL 15 | Event 16 | Description: Shade/unshade a line. 17 | 18 | Mode: NORMAL 19 | Event: 20 | Description: Make the cursor jump to the next shaded line from the cursor position. 21 | 22 | Mode: NORMAL 23 | Event: 24 | Description: Make the cursor jump to the previous shaded line from the cursor position. 25 | 26 | Mode: NORMAL 27 | Event: 28 | Description: Remove all (SPOT) tags from the text. 29 | 30 | """ 31 | 32 | def install(area, setup={'background':'green', 'foreground':'black'}): 33 | area.tag_configure('(SPOT)', **setup) 34 | area.install('text-spots', ('NORMAL', '', lambda event: 35 | area.toggle_range('(SPOT)', 'insert linestart', 'insert lineend')), 36 | ('NORMAL', '', lambda event: 37 | area.seecur(area.tag_prevrange('(SPOT)', 'insert linestart')[0])), 38 | ('NORMAL', '', lambda event: 39 | area.tag_remove('(SPOT)', '1.0', 'end')), 40 | ('NORMAL', '', lambda event: 41 | area.seecur(area.tag_nextrange('(SPOT)', 'insert lineend')[0]))) 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /vyapp/plugins/tidy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Show errors in html files by running Tidy. 6 | 7 | Extern dependencies: 8 | Html Tidy 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Namespace: tidy 14 | 15 | Mode: HTML 16 | Event: 17 | Description: Run Tidy on the current html file and display 18 | a dialog window with all encountered errors. When the dialog 19 | window is shown with the errors it is possible to jump to the 20 | error line by pressing . 21 | 22 | Commands 23 | ======== 24 | 25 | Command: html_errors() 26 | Description: Same as the keycommand . 27 | 28 | """ 29 | 30 | from subprocess import Popen, STDOUT, PIPE 31 | from vyapp.widgets import LinePicker 32 | from vyapp.plugins import Command 33 | from vyapp.app import root 34 | from re import findall 35 | import sys 36 | 37 | class HtmlChecker: 38 | PATH = 'tidy' 39 | 40 | def __init__(self, area): 41 | self.area = area 42 | 43 | def check(self): 44 | child = Popen([self.PATH, '--show-body-only', '1', '-e', '-quiet', 45 | self.area.filename], stdout=PIPE, stderr=STDOUT, 46 | encoding=self.area.charset) 47 | 48 | output = child.communicate()[0] 49 | regex = 'line ([0-9]+) column ([0-9]+) - (.+)' 50 | ranges = findall(regex, output) 51 | ranges = map(lambda ind: (self.area.filename, ind[0], ind[2]), ranges) 52 | 53 | sys.stdout.write('Errors:\n%s\n' % output) 54 | self.area.chmode('NORMAL') 55 | 56 | if child.returncode: 57 | self.display(ranges) 58 | else: 59 | root.status.set_msg('No errors!') 60 | 61 | def display(self, ranges): 62 | root.status.set_msg('Errors were found!' ) 63 | options = LinePicker() 64 | options(ranges) 65 | 66 | def install(area): 67 | html_checker = HtmlChecker(area) 68 | picker = lambda event: html_checker.check() 69 | area.install('tidy', ('HTML', '', picker)) 70 | 71 | @Command() 72 | def html_errors(area): 73 | html_checker = HtmlChecker(area) 74 | html_checker.check() 75 | 76 | 77 | -------------------------------------------------------------------------------- /vyapp/plugins/undo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements undo/redo operations. 6 | 7 | Key-Commands 8 | ============ 9 | 10 | Namespace: undo 11 | 12 | Mode: NORMAL 13 | Event: 14 | Description: Do undo. 15 | 16 | Mode: NORMAL 17 | Event: 18 | Description: Do redo. 19 | """ 20 | 21 | class Undo: 22 | def __init__(self, area): 23 | area.install('undo', 24 | ('NORMAL', '', lambda event: event.widget.edit_undo()), 25 | ('NORMAL', '', lambda event: event.widget.edit_redo())) 26 | 27 | install = Undo 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /vyapp/plugins/untwisted.py: -------------------------------------------------------------------------------- 1 | from untwisted.tkinter import extern 2 | from vyapp.app import root 3 | 4 | # Install untwisted reactor. 5 | extern(root) 6 | -------------------------------------------------------------------------------- /vyapp/plugins/urlsrc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | Implement keycommands to download url source and view it in the 6 | given AreaVi instance. It also can load HTML content in the browser. 7 | 8 | Key-Commands 9 | =========== 10 | 11 | Namespace: urlsrc 12 | 13 | Mode: HTML 14 | Event: 15 | Description: Load the focused AreaVi instance file in the browser. 16 | 17 | Mode: HTML 18 | Event: 19 | Description: Download the source code of the URL that is in the clipboard 20 | and writes it to sys.stdout. 21 | """ 22 | 23 | import webbrowser 24 | import urllib.request, urllib.error, urllib.parse 25 | 26 | class UrlSrc: 27 | def __init__(self, area): 28 | self.area = area 29 | area.install('urlsrc', 30 | ('HTML', '', self.bload_data), 31 | ('HTML', '', self.url_download)) 32 | 33 | def bload_data(self, event): 34 | webbrowser.open_new_tab(event.widget.filename) 35 | event.widget.chmode('NORMAL') 36 | 37 | def url_download(self, event): 38 | opener = urllib.request.build_opener() 39 | opener.addheaders = [('User-agent', 'Mozilla/5.0')] 40 | req = opener.open(event.widget.clipboard_get()) 41 | event.widget.delete('1.0', 'end') 42 | event.widget.insert('1.0', req.read()) 43 | event.widget.chmode('NORMAL') 44 | 45 | install = UrlSrc 46 | -------------------------------------------------------------------------------- /vyapp/plugins/word_completion.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin does word completion. 6 | 7 | 8 | Key-Commands 9 | ============ 10 | 11 | Namespace: word-completion 12 | 13 | Mode: INSERT 14 | Event: 15 | Description: Open the completion window with possible words for 16 | completion. 17 | 18 | """ 19 | 20 | from vyapp.completion import CompletionWindow, Option 21 | from vyapp.app import root 22 | 23 | class WordCompletionWindow(CompletionWindow): 24 | """ 25 | """ 26 | 27 | def __init__(self, area, *args, **kwargs): 28 | pattern = area.get(*area.get_seq_range()) 29 | completions = [ind[1][0] for ind in area.find_all(root, '[^ ]*%s[^ ]*' % pattern 30 | if pattern else '[^ ]+', nocase=True)] 31 | 32 | completions = set(completions) 33 | completions = [Option(ind) for ind in completions] 34 | 35 | CompletionWindow.__init__(self, area, 36 | completions, *args, **kwargs) 37 | 38 | def install(area): 39 | area.install('word-completion', ('INSERT', '', 40 | lambda event: WordCompletionWindow(event.widget))) 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /vyapp/plugins/word_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Overview 3 | ======== 4 | 5 | This plugin implements a mechanism of search based on an initial pattern. It takes the 6 | words of the data input then calculates the permutations. The search processes happens 7 | regardless of the punctuation between the words. 8 | 9 | Key-Commands 10 | ============ 11 | 12 | Namespace: word-search 13 | 14 | Mode: NORMAL 15 | Event: 16 | Description: Ask for a pattern to search. 17 | 18 | Event: 19 | Mode: NORMAL 20 | Description: Display previous matches. 21 | 22 | """ 23 | 24 | from vyapp.widgets import LinePicker 25 | from vyapp.ask import Ask 26 | from itertools import groupby 27 | from re import escape 28 | from vyapp.app import root 29 | 30 | class WordSearch: 31 | options = LinePicker() 32 | 33 | def __init__(self, area): 34 | self.area = area 35 | 36 | area.install('word-search', 37 | ('NORMAL', '', self.match), 38 | ('NORMAL', '', self.display_matches)) 39 | 40 | def display_matches(self, event): 41 | self.options.display() 42 | root.status.set_msg('Word Search matches!') 43 | 44 | def match(self, event): 45 | """ 46 | 47 | """ 48 | 49 | ask = Ask() 50 | data = ask.data.split(' ') 51 | find = lambda ind: self.area.find( 52 | escape(ind).lower(), '1.0', step='+1l linestart') 53 | 54 | seq = self.match_regions(find, data) 55 | matches = ((self.area.filename, line, 56 | self.area.get_line('%s.0' % line)) 57 | for count, line in seq) 58 | 59 | if not seq: 60 | root.status.set_msg('No pattern found!') 61 | else: 62 | self.options(matches) 63 | 64 | def match_regions(self, find, data): 65 | regions = [] 66 | 67 | for ind in data: 68 | for ch, index0, index1 in find(ind): 69 | regions.append((int(index0.split('.')[0]), ch)) 70 | 71 | regions.sort() 72 | seq = groupby(regions, lambda ind: ind[0]) 73 | matches = self.sort_matches(seq, data) 74 | return matches 75 | 76 | def sort_matches(self, seq, data): 77 | matches = [] 78 | 79 | for line, group in seq: 80 | count = 0 81 | for line, word in group: 82 | if word in data: 83 | count = count + 1 84 | matches.append((count, line)) 85 | matches.sort(reverse=True) 86 | return matches 87 | 88 | install = WordSearch 89 | 90 | -------------------------------------------------------------------------------- /vyapp/plugins/xdg_open.py: -------------------------------------------------------------------------------- 1 | """" 2 | Overview 3 | ======== 4 | 5 | Many times a file path appears in some contexts of a daily programmer. 6 | This plugin uses xdg-open to open files using proper applications. It is enough 7 | to place the cursor over the file path then press a key to get the file 8 | opened with the proper application. 9 | 10 | Key-Commands 11 | ============ 12 | 13 | Mode: NORMAL 14 | Event: 15 | Description: Open a file using xdg-open whose path is under the cursor. The path 16 | has to be mapped to an entire line otherwise it fails. 17 | """ 18 | from subprocess import Popen 19 | 20 | class XdgOpen: 21 | def __init__(self, area): 22 | self.area = area 23 | 24 | area.install('xdg_open', ('NORMAL', '', lambda e: self.openfile())) 25 | 26 | def openfile(self): 27 | """ 28 | Open a file using the appropriate program for. 29 | """ 30 | 31 | filename = self.area.get_line() 32 | # No need for "" because it is passing the entire filename 33 | # as parameter. 34 | Popen(['xdg-open', '%s' % filename]) 35 | 36 | install = XdgOpen -------------------------------------------------------------------------------- /vyapp/plugins/ycmd/__init__.py: -------------------------------------------------------------------------------- 1 | HANDLE = [] 2 | 3 | # ENV is a dict holding plugins objects, like functions, classes etc. 4 | # Plugins should install their handles in ENV. 5 | ENV = {} 6 | 7 | def autoload(plugin, *args, **kwargs): 8 | HANDLE.append((plugin.install, args, kwargs)) 9 | 10 | def autocall(handle, *args, **kwargs): 11 | HANDLE.append((handle, args, kwargs)) 12 | 13 | def mapset(namespace, map): 14 | HANDLE.append((lambda area: 15 | area.update_map(namespace, map), (), {})) 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /vyapp/plugins/ycmd/default_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "filepath_completion_use_working_dir": 0, 3 | "auto_trigger": 1, 4 | "min_num_of_chars_for_completion": 2, 5 | "min_num_identifier_candidate_chars": 0, 6 | "semantic_triggers": {}, 7 | "filetype_specific_completion_to_disable": { 8 | "gitcommit": 1 9 | }, 10 | "collect_identifiers_from_comments_and_strings": 0, 11 | "max_num_identifier_candidates": 10, 12 | "max_num_candidates": 50, 13 | "extra_conf_globlist": [], 14 | "global_ycm_extra_conf": "", 15 | "confirm_extra_conf": 1, 16 | "max_diagnostics_to_display": 30, 17 | "filepath_blacklist": { 18 | "html": 1, 19 | "jsx": 1, 20 | "xml": 1 21 | }, 22 | "auto_start_csharp_server": 1, 23 | "auto_stop_csharp_server": 1, 24 | "use_ultisnips_completer": 1, 25 | "csharp_server_port": 0, 26 | "hmac_secret": "", 27 | "server_keep_logfiles": 0, 28 | "python_binary_path": "", 29 | "language_server": [], 30 | "java_jdtls_use_clean_workspace": 1, 31 | "java_jdtls_workspace_root_path": "", 32 | "java_jdtls_extension_path": [], 33 | "use_clangd": 1, 34 | "clangd_binary_path": "", 35 | "clangd_args": [], 36 | "clangd_uses_ycmd_caching": 1 37 | } 38 | -------------------------------------------------------------------------------- /vyapp/regutils.py: -------------------------------------------------------------------------------- 1 | from untwisted.splits import Terminator 2 | from re import search 3 | from re import split, escape 4 | 5 | class RegexEvent: 6 | def __init__(self, ssock, regstr, event, encoding='utf8'): 7 | self.encoding = encoding 8 | self.regstr = regstr 9 | self.event = event 10 | ssock.add_map(Terminator.FOUND, self.handle_found) 11 | 12 | def handle_found(self, ssock, data): 13 | data = data.decode(self.encoding) 14 | regex = search(self.regstr, data) 15 | 16 | if regex is not None: 17 | ssock.drive(self.event, *regex.groups()) 18 | 19 | def build_regex(data, delim='.+'): 20 | """ 21 | 22 | """ 23 | 24 | data = split(' +', data) 25 | pattern = '' 26 | for ind in range(0, len(data)-1): 27 | pattern = pattern + escape(data[ind]) + delim 28 | pattern = pattern + escape(data[-1]) 29 | return pattern 30 | 31 | def match_sub_pattern(pattern, lst): 32 | # pattern = buffer(pattern) 33 | for indi in lst: 34 | for indj in range(0, len(pattern)): 35 | if indi.startswith(pattern[indj:]): 36 | yield indi, indj 37 | 38 | -------------------------------------------------------------------------------- /vyapp/statusbar.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module implements the vy statusbar widget. 3 | """ 4 | 5 | from tkinter import SUNKEN, W, Frame, X, Label 6 | 7 | class StatusBar(Frame): 8 | """ 9 | This class implements a statusbar. 10 | """ 11 | 12 | def __init__(self, master): 13 | Frame.__init__(self, master) 14 | self.config(border=1) 15 | 16 | self.msg = Label(self, bd=1, relief=SUNKEN, anchor=W) 17 | self.msg.pack(side='left', expand=True, fill=X) 18 | 19 | self.column = Label(self, bd=1, relief=SUNKEN, anchor=W) 20 | self.column.config(text='Col: 0') 21 | self.column.pack(side='right', fill=X) 22 | 23 | self.line = Label(self, bd=1, relief=SUNKEN, anchor=W) 24 | self.line.config(text='Line: 1') 25 | self.line.pack(side='right', fill=X) 26 | 27 | 28 | self.mode = Label(self, bd=1, relief=SUNKEN, anchor=W) 29 | self.mode.config(text='Mode: 1') 30 | self.mode.pack(side='right', fill=X) 31 | 32 | 33 | def set_msg(self, data): 34 | """ 35 | Set statusbar msg. 36 | """ 37 | 38 | self.msg.config(text=data) 39 | self.msg.update_idletasks() 40 | 41 | def clear_msg(self): 42 | """ 43 | Clear statusbar msg. 44 | """ 45 | 46 | self.msg.config(text="") 47 | self.msg.update_idletasks() 48 | 49 | 50 | def set_column(self, col): 51 | """ 52 | Set the column field with col. 53 | """ 54 | 55 | self.column.config(text='Col: %s' % col) 56 | self.column.update_idletasks() 57 | 58 | def set_line(self, line): 59 | """ 60 | Set the line field. 61 | """ 62 | 63 | self.line.config(text='Line: %s' % line) 64 | self.line.update_idletasks() 65 | 66 | def set_mode(self, mode): 67 | """ 68 | Set the mode field. 69 | """ 70 | 71 | self.mode.config(text='Mode: %s' % mode) 72 | self.mode.update_idletasks() 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /vyapp/stderr.py: -------------------------------------------------------------------------------- 1 | from traceback import format_exc 2 | import logging 3 | import sys 4 | 5 | QUIET = 100 6 | logger = logging.getLogger() 7 | 8 | def xhook(exctype, value, tb): 9 | logger.exception('', exc_info=(exctype, value, tb)) 10 | 11 | def printd(*args): 12 | logger.debug(' '.join(map(str, args))) 13 | 14 | sys.excepthook = xhook 15 | c_handler = logging.StreamHandler() 16 | logger.setLevel(logging.DEBUG) 17 | logger.addHandler(c_handler) 18 | -------------------------------------------------------------------------------- /vyapp/stdout.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyapp/vy/4ba0d379e21744fd79a740e8aeaba3a0a779973c/vyapp/stdout.py -------------------------------------------------------------------------------- /vyapp/tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module implements a set of functions that are commonly used by plugins. 3 | """ 4 | 5 | from traceback import print_exc as debug 6 | from os.path import exists, dirname, join 7 | from vyapp.app import root 8 | from vyapp.areavi import AreaVi 9 | from os.path import abspath 10 | import sys 11 | 12 | 13 | def findline(filename, line, col=0): 14 | files = AreaVi.get_opened_files(root) 15 | filename = abspath(filename) 16 | 17 | try: 18 | area = files[filename] 19 | except KeyError: 20 | area = root.note.open(filename) 21 | else: 22 | pass 23 | finally: 24 | root.note.set_line(area, line) 25 | 26 | def error(handle): 27 | def shell(*args, **kwargs): 28 | try: 29 | return handle(*args, **kwargs) 30 | except Exception as e: 31 | root.status.set_msg('Error :%s' % e) 32 | raise 33 | return shell 34 | 35 | def get_project_root(path): 36 | """ 37 | Return the project root or the file path. 38 | """ 39 | 40 | # In case it receives '/file' 41 | # and there is '/__init__.py' file. 42 | if path == dirname(path): 43 | return path 44 | 45 | while True: 46 | tmp = dirname(path) 47 | if not exists(join(tmp, '__init__.py')): 48 | return path 49 | path = tmp 50 | 51 | def exec_pipe(data, env): 52 | """ 53 | This function is used to execute python code and it sets 54 | the sys.stderr to sys.stdout so exceptions would be printed on sys.stdout. 55 | After the code being executed then sys.stderr is restored to its 56 | default value. 57 | 58 | The data argument is python code to be executed and env is a dictionary where 59 | the code will be executed. 60 | 61 | Note: It is mostly used to execute python code from vy. 62 | """ 63 | 64 | # It has to be set before because 65 | # if some data code catches an exception 66 | # then prints use print_exc it will go to 67 | # sys.__stderr__. 68 | 69 | tmp = sys.stderr 70 | sys.stderr = sys.stdout 71 | 72 | try: 73 | 74 | exec(data, env) 75 | except Exception as e: 76 | debug() 77 | root.status.set_msg('Error: %s' % e) 78 | finally: 79 | sys.stderr = tmp 80 | 81 | def e_stop(handle): 82 | """ 83 | This decorator is used to execute an event handle 84 | and stop propagation of the event through event classes. 85 | 86 | Let us say there is a handle on: 87 | 88 | 89 | 90 | Such an event is on mode -1, if there is a handle on the same 91 | event on mode NORMAL then both handles would be executed in case 92 | an exception occurs in the -1 handle. 93 | 94 | It happens because the 'break' value is not propagated to the tkinter event loop. 95 | """ 96 | 97 | def wrapper(*args, **kwargs): 98 | try: 99 | handle(*args, **kwargs) 100 | except Exception: 101 | debug() 102 | return 'break' 103 | return wrapper 104 | 105 | def consume_iter(iterator, time=1): 106 | """ 107 | This function receives an iterator that is consumed from tkinter update 108 | function. It is a way to have python code executed asynchronously. Some 109 | plugins would perform heavy operations that could block tkinter mainloop, 110 | these plugins should write code that can be executed asynchronously using 111 | iterators. 112 | 113 | Note: Some plugins like syntax highlighting would use this technique 114 | to highlight code. 115 | """ 116 | 117 | def cave(): 118 | from vyapp.app import root 119 | try: 120 | next(iterator) 121 | except Exception: 122 | pass 123 | else: 124 | root.after(time, cave) 125 | 126 | cave() 127 | 128 | 129 | --------------------------------------------------------------------------------