I would like to create a VIM insert-mode-completion popup menu, preferably directly via the python vim module but also by calling vim commands if necessary. Per my requirements, if this is possible, what do I need to know?
Requirements
Modify the menu based on input. Either recolor and replace select characters ala easymotion plugin, or completely replace content, possibly resulting in a change of size.
Customizable navigation bindings, example:
j/k for vertical navigation (partial redraw)
h/l for category navigation/refinement (full redraw)
others as category/relationship shortcuts (full)
others as source cycling (full)
easymotion-style bindings (partial)
others for related functionality (none)
optionally, in lieu of the above bindings, a fuzzy filtering mode (capture all input) (full)
Hook to run action when menu is destroyed
Ability to replace previous word if menu item selected (easily done in normal mode... but, is there a better approach than calling :norm?)
Once again, I need to know if any of this is possible. If so, please provide specific documentation regarding the required capabilities, APIs, or functions. Well, enough to get me started.
If important, I'm not sure of the interface specifics yet but the menu itself may need to be quite large to accommodate the content.
Edit:
I haven't tried anything; rather I'm asking what I should read or know to implement the vim-specific functionality I need. All I now have is python code that accepts a sentence and a word in that sentence, determines the POS, lemmatises the word, and provides synonyms.
It should be clear by now this is a vim plugin providing thesaurus-like functionality, augmenting vim's built-in CTRL_X-CTRL_T functionality. Unfortunately synonymy is a complicated hierarchy, for example see thesaurus.com or wordnet. Below is a tree of the synonym selection process. To provide useful thesaurus functionality I would need to navigate this tree within the insert-mode-completion popup menu. By automatically inferring the POS the first step can be skipped, and of course it makes sense to initially merge and display all sense synonyms irrespective of relationship. However, to determine the POS I need access to the entire sentence, and I still need to be able to navigate a sense selection menu. I also want to be able to provide scratch buffer detailing word definitions and example sentences for the currently highlighted popup menu entry. Hence the necessity for a hook; to destroy the buffer when the menu is destroyed. All the other keybindings I've described would be conveniences to, for example, filter by relationship, open the informational scratch buffer, or switch POS.
POS Sense Relationship Synonym
N -> Sense 1 -> Relationship 1 -> Synonym 1
Synonym 2
...
Synonym n
-> Relationship 2 -> Synonym 1
...
Synonym n
-> Relationship 3 -> Synonym 1
...
Synonym n
Sense 2 -> Relationship 1 -> ...
Sense 3 -> Relationship 2 -> ...
V -> Sense 1 -> ...
A -> ...
Insert completion popup menus are great because they preserve context: surrounding text doesn't change, remains (mostly) visible, and windows aren't resized. I could provide the functionality I need in a separate buffer (such as a unite plugin), however I'd rather not.
After reading :h complete-functions:
If refresh="alway", complete-functions will be re-invoked each time leading text is changed. The purpose is to refine the matches, however this has little meaning for synonym-matching, where the word is already complete and instead will just be replaced. Aside from this, insert-completion popup menus only provide minimal key mappings: <CTRL-H>, CTRL-L>, <CTRL-Y>, <CTRL-E>, etc. Additional mappings can be added using pumvisible(), however such mappings affect all popup menus, and so would not be applicable. Of course I could use <expr> to check a global variable such as g:popup-menu-is-thesaurus, but this approach is basically a heap of hacks. For example, to display a new category in the menu, I could do something like:
:call Thesaurus#BindKey("h","w:Thesaurus#CategoryUp")
:call Thesaurus#BindKey("i","w:Thesaurus#InsertMode")
with:
function! Thesaurus#BindKey(key, function)
inoremap <expr> a:key a:function
endfunction
function! Thesaurus#CategoryUp()
if !b:popup-menu-is-thesaurus
return v:char
let b:thesaurus-category-index -= 1
endfunction
function! Thesaurus#InsertMode()
if !b:popup-menu-is-thesaurus
return v:char
let b:thesaurus-mode = "insert"
endfunction
function! Thesaurus#CompleteFunc(findstart, base)
if a:findstart
...
else
if b:thesaurus-mode = "insert"
return Thesaurus#FuzzyMatch(base)
else
return Thesaurus#Redraw()
endif
endif
endfunction
function! Thesaurus#Redraw()
...
endfunction
* Obviously I'm not very good with VimL. Also, am I using v:char correctly?
Of course I have no idea how to replace the previous word once an entry is selected, since the only way to enable fuzzy matching is to have a:base be empty.
Edit2: And how in the world do I get my completion-function triggered, preferably via the standard thesaurus-style completion shortcut, i_CTRL-X_CTRL-T? I don't want to lose my custom completions (omni and user-defined completion), and since the popup displays in the current buffer, unlike FuzzyFinder I can't override omnifunc.
Perhaps the ideal approach would be to have full control of the menu-drawing functionality to build the popup from scratch, and fake a new insert-completion popup menu mode using plugins such as tinymode, tinykeymap or submode.
Please keep in mind that the insert-mode completion popup menu is for completing matches e.g. offering foobar and foomatic when you type foo and trigger completion. There is no generic insert-mode menu with arbitrary actions, and it's probably a bad idea to implement such. That said, some plugins (e.g. FuzzyFinder) mis-use insert-mode completion menus for a dynamic choice selector (in a scratch buffer, so what get's completed is discarded there).
You'll find the full specification and an example at :help complete-function. If that doesn't satisfy you / leaves open questions, that's a sure sign that you're trying to use it for something it wasn't meant for.
Modify the menu based on input.
With each typed key, your 'completefunc' will be re-invoked, and it can offer a (reduced) set of matches.
Customizable navigation bindings
Bad idea. The user should customize this for all completions (using :imap <expr> with pumvisible() conditionals).
Hook to run action when menu is destroyed
The CompleteDone event has been added in recent Vim versions, but usually the only action should be "insert the match", which gets done automatically.
Ability to replace previous word if menu item selected
On the first invocation of your 'completefunc', your function can specify the base column, i.e. where the completion matches will start.
Related
I am faced with the task of writing a simple debug helper for Qt Creator 4.13.1 / Qt 5.12.5 / MSVC 2017 compiler for the C++ JSON implementation nlohmann::basic_json (https://github.com/nlohmann/json).
An object of nlohmann::basic_json can contain the contents of a single JSON data type (null, boolean, number, string, array, object) at a time.
There's a dump() member function which can be used to output the current content formatted as a std::string regardless of the current data type. I always want to use this function.
What I've done so far:
I've looked at https://doc.qt.io/qtcreator/creator-debugging-helpers.html, as well as at the given example files (qttypes.py, stdtypes.py...).
I made a copy of the file personaltypes.py and told Qt Creator about its existence at
Tools / Options / Debugger / Locals & Expressions / Extra Debugging Helpers
The following code works and displays a "Hello World" in the debugger window for nlohmann::basic_json objects.
import dumper
def qdump__nlohmann__basic_json(d, value):
d.putNumChild(0)
d.putValue("Hello World")
Unfortunately, despite the documentation, I have no idea how to proceed from here on.
I still have absolutely no clue how to correctly call basic_json's dump() function with the dumper from Python (e.g. with d.putCallItem ?).
I also have no starting point how to format the returned std::string so that it is finally displayed in the debugger window.
I imagined something like this, but it doesn't work.
d.putValue("data")
d.putNumChild(1)
d.putCallItem('dump', '#std::string', value, 'dump')
I hope someone can give me a little clue so that I can continue thinking in the right direction.
For example, can I call qdump__std__string from stdtypes.py myself to interpret the std::string?
I'm trying to do some basic Qt file manager app in Python 3.6 with PySide2. Code is like this:
class MainWidget(QWidget):
startDir = "."
def __init__(self):
super().__init__()
isDirselectDone = self.selectContentDir()
if isDirselectDone:
self.model = QFileSystemModel()
self.model.setRootPath(self.startDir)
self.tree = QTreeView()
self.tree.setModel(self.model)
self.tree.setSortingEnabled(True)
self.tree.show()
def selectContentDir(self):
print("Selecing game content folder")
self.startDir = QFileDialog.getExistingDirectory()
if(len(self.startDir) == 0):
print("Game content folder loading cancelled")
return False
print("Trying to load ", self.startDir)
return True
My problem is that no matter what is the contents of the chosen directory, the view does not sort the files. I can click oh the headers, and the little arrows are changing, but everything remains in the order it was loaded.
I tried to look for solutions, but the answers either say that you just have to call the setSortingEnabled() and everything magically works (which is not the case), or they describe some voodoo magic rituals involving inheriting from QAbstractTreeSuperAncientGodItemViewDelegate, then re-implement half of the Qt library and finally creating a Tartarus-like maze by connecting slots and signals all over the place.
So what is the easiest / simplest way to make this sorting work according to latest Qt standards?
I found a solution for my case, based on the following assumptions:
These three lines:
model.setRootPath(directory)
tree.setRootIndex(self.model.index(directory))
tree.setSortingEnabled(True)
Seem they have to be in this specific order, and of course the model rootpath and the tree root index has to point to the same directory (during debugging I tried to set them to different ones, just to see what happens when the tree shows just a subset of the model data, but as expected it broke multiple things in the app).
Also, any custom lines (like handling your custom columns) has to happen between the root index setting, and the sorting enable. I don't know if this is really a general rule, it seems kind of arbitrary, but in my project it turned out to be crucial to call setSortingEnabled() as a last step, to make it work.
If your custom columns do not contain simple text (or anything not easily sortable, in my case a combobox) or they are hidden, you'd better exclude that column from the sorting. This is not mandatory, but for me it resulted in faster response time for the other columns.
In Pycharm, there's "code structurure" side bar which provides a tree to navigate through the code, but, it is only useful when the code has classes and methods and objects. If nothing of that is in code then it is useless.
My question is: is there any way in which I dictate that this is a block, and I want to be able to collapse it and expand it? Something similar to Jupyter where the code is inherently divided to cells.
Currently, I'm doing this:
# ---------------------------------- chunck x blah blah -----------------------
EDIT:
Most of comments say that I'm dumb and I don't know how to code efficiently and that I should use functions and classes. Guys, I know how to use those, that's not my question. Thanks.
Turns out that the answer is very simple:
Select the code, right click, do custom folding
PyCharm allows you to define 'code cells' when you have 'Scientific Mode' enabled. These code cells are collapsible and expandable. To quote from the PyCharm website:
A “code cell” is a block of lines to be executed all at once in the
integrated Python console. You can define cells simply by adding
inline comments #%% to your regular Python files. PyCharm detects
these comments and shows you a special run icon in the left gutter.
Clicking this icon triggers the execution of a cell:
The only catch is that Scientific Mode and its code cell functionality are only available in PyCharm Professional Edition.
You can select a region, and press ctr+alt+t, then select <editor-fold...>. This will surround the region with a comment that makes the region collapsible. You can also do this manually by adding the following around the region:
# <editor-fold desc="This text is shown when collapsed">
# </editor-fold>
I sometimes use True conditional statements to create collapsible blocks in PyCharm and other IDEs. This also helps me to visually relate all indented code, access it when needed, and collapse it, when I'm focusing on other parts of my code.
if True:
# block code goes here
A fancier way is to use a descriptive string in the condition. The description stays visible for a collapsed block. You can also disable these with negation anytime, if needed.
if 'Define similarities':
Dot = lambda x, y: x # y
CosSim = lambda x, y: x # y / (x # x)**0.5 / (y # y)**0.5
I noticed that when calling QtWidgets.setFocus, I get a warning in PyCharm saying that it expects a Qt.FocusReason rather than a Boolean. Indeed, the documentation for this method also states that it takes a Qt.FocusReason. But for the life of me, I can't find any information on this for PyQt5.
myPlainTextEdit.setFocus(True)
The method works perfectly fine by passing in a Boolean, but I'm wondering if this is perhaps some legacy from PyQt4, or just an anomaly? I noticed that on the PyQt Sourceforge Documentation, which seems to be for PyQt4, that it says to get FocusReason from QFocusEvent. In attempting to do this, I get another warning; Expected type 'FocusReason', got 'Type' instead. This raises a TypeError, as one might expect.
myPlainTextEdit.setFocus(QtGui.QFocusEvent.ActionAdded)
The PyQt5 documentation does not appear to contain a page for Qt.FocusReason. As this is extremely pedantic, I'm not overly concerned if there is no solution. I am interested to know what causes this. Is it possible to pass a Qt FocusReason to QtWidgets.setFocus in the first place?
There are 2 functions called setFocus() that every widget supports:
void QWidget::setFocus(Qt::FocusReason reason)
void QWidget::setFocus()
You are referring to the first. So let's analyze what you point out:
The method works perfectly fine by passing in a Boolean
It works but it does not imply that it is correct, first Qt::FocusReason is an enumeration, that is to say that each element that belongs is associated to a number:
Qt::MouseFocusReason 0 A mouse action occurred.
Qt::TabFocusReason 1 The Tab key was pressed.
Qt::BacktabFocusReason 2 A Backtab occurred. The input for this may include the Shift or Control keys; e.g. Shift+Tab.
Qt::ActiveWindowFocusReason 3 The window system made this window either active or inactive.
Qt::PopupFocusReason 4 The application opened/closed a pop-up that grabbed/released the keyboard focus.
Qt::ShortcutFocusReason 5 The user typed a labels buddy shortcut
Qt::MenuBarFocusReason 6 The menu bar took focus.
Qt::OtherFocusReason 7 Another reason, usually application-specific.
so when passing a Boolean it will convert it to an integer, False to 0 and True to 1, so setFocus(True) equals setFocus(QtCore.Qt.TabFocusReason).
In attempting to do this, I get another warning; Expected type 'FocusReason', got 'Type' instead. This raises a TypeError, as one might expect.
myPlainTextEdit.setFocus(QtGui.QFocusEvent.ActionAdded)
As you realize QtGui.QFocusEvent.ActionAdded does not belong to that list, so it throws the error. You have to use the values from the previous list by changing :: by . and prefixing it with QtCore since it belongs to that submodule, for example:
myPlainTextEdit.setFocus(QtCore.Qt.MouseFocusReason)
Plus:
If you just want to establish that the widget has the focus you should call the second function:
myPlainTextEdit.setFocus()
And for clean use clearFocus().
I was looking inside the idlelib module how the Python programmers have implemented IDLE, and at some point I found the following new syntax: <<cut>> or <<copy>> inside the file EditorWindow.py. Specifically, that syntax seems to be used as an event identifier (I think) for the bind function:
text.bind("<<cut>>", self.cut)
What I am not understanding is why there are some many of them, it seems they were create specifically for that class EditorWindow:
text.bind("<<cut>>", self.cut)
text.bind("<<copy>>", self.copy)
text.bind("<<paste>>", self.paste)
text.bind("<<center-insert>>", self.center_insert_event)
text.bind("<<help>>", self.help_dialog)
text.bind("<<python-docs>>", self.python_docs)
text.bind("<<about-idle>>", self.about_dialog)
text.bind("<<open-config-dialog>>", self.config_dialog)
text.bind("<<open-module>>", self.open_module)
text.bind("<<do-nothing>>", lambda event: "break")
text.bind("<<select-all>>", self.select_all)
text.bind("<<remove-selection>>", self.remove_selection)
text.bind("<<find>>", self.find_event)
text.bind("<<find-again>>", self.find_again_event)
text.bind("<<find-in-files>>", self.find_in_files_event)
text.bind("<<find-selection>>", self.find_selection_event)
text.bind("<<replace>>", self.replace_event)
text.bind("<<goto-line>>", self.goto_line_event)
text.bind("<<smart-backspace>>",self.smart_backspace_event)
text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
text.bind("<<smart-indent>>",self.smart_indent_event)
text.bind("<<indent-region>>",self.indent_region_event)
text.bind("<<dedent-region>>",self.dedent_region_event)
text.bind("<<comment-region>>",self.comment_region_event)
text.bind("<<uncomment-region>>",self.uncomment_region_event)
text.bind("<<tabify-region>>",self.tabify_region_event)
text.bind("<<untabify-region>>",self.untabify_region_event)
text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
text.bind("<Left>", self.move_at_edge_if_selection(0))
text.bind("<Right>", self.move_at_edge_if_selection(1))
text.bind("<<del-word-left>>", self.del_word_left)
text.bind("<<del-word-right>>", self.del_word_right)
text.bind("<<beginning-of-line>>", self.home_callback)
It seems we can also define in some way our events with this syntax <<EVENT_NAME>>.
I am using Python 3.4.
Virtual events, indicated with double rather than single brackets, are part of tcl/tk and are, of course, exposed in tkinter. For instance, a ttk.Notebook can generate a "<<NotebookTabChangede>>" event.
Create virtual events with widget.add_event(virtual, sequence, ...), where virtual is a double-bracketed name and each sequence is a normal single-bracketed physical event name. Delete such associations with Widget.event_delete(virtual, sequence, ...). Get info with w.event_info(virtual=None). Bind virual events with bind just like physical events. (I got this all from here.
Virtual events avoid hard-coding physical events to actions. In Idle, Options -> Idle preferences -> General -> Custom Key Setting is a table of actions and key bindings. Each action is both a virtual event and the event handler it is bound to. Those are fixed, but the key-binding to invoke the events are not. If you change the key bindings, event_delete and event_add are called as appropriate (and a custom set of key bindings is created or changed and saved to ~/.idlerc/config-keys.cfg).
<<..>> is not anything Python specific. It is specific to the Tk widget toolkit. Events with double chevrons are virtual events.
The editor window is a complex widget, and IDLE needs to handle many different events in that widget in order to implement code editing effectively.