Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to get started with Python plugins? #2382

Closed
faerot opened this issue Sep 10, 2024 · 33 comments
Closed

How to get started with Python plugins? #2382

faerot opened this issue Sep 10, 2024 · 33 comments

Comments

@faerot
Copy link
Contributor

faerot commented Sep 10, 2024

I am trying to write a Python plugin, but I have faced a number of problems right off the bat:

  1. Not just zero documentatation, but also zero comments in the python sources. Is there any info that can be read to at least get things started? I've browsed the included plugin samples to get at least some kind of idea, but I have faced second problem:
  2. How to debug? The plugin is absolutely silent about errors or crashes. I have seen logging.ini has logger setup which should write logs to /tmp/far2l-py but there is nothing there. I have tried to copy it to ~/.config/far2l/plugins/python/ just like I did with plugins but it didn't change anything.

It is a shame, because Python is such and easy and powerful language that would allow to quickly re-implement most of the plugins for windows FAR, if only it had proper documentation and debug tools. Right now the barrier to entry for most of the new users looks impossible.

@unxed
Copy link
Contributor

unxed commented Sep 10, 2024

@m32 ?

@atolismesh
Copy link
Contributor

atolismesh commented Sep 10, 2024

Did you read python/configs/plug/plugins/read-udebug-en.txt (contains info about VS Code python plugins debugging) and read-en.txt?

  • Basics:

B. Logging and logger configuration
There is a logger.ini file in the Plugins/python/plug/far2l directory, you can set your own log information preferences there.
By default, the log is saved to the file /tmp/far2l-py.

C. Fundamentals
*.py files from this directory should be copied to the directory:
~/.config/far2l/plugins/python - The default directory for python plugins from which they can be easily activated.

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

Did you read python/configs/plug/plugins/read-udebug-en.txt and read-en.txt?

Yes I did. Did you read my issue? There is nothing in /tmp/. Does it mean that plugin is meant to be impossible to debug unless you use VS Code? It should report crashes somewhere no matter what.

@atolismesh
Copy link
Contributor

atolismesh commented Sep 10, 2024

https://github.com/elfmz/far2l/wiki/Running-far2l-with-debug-logging-enabled

https://github.com/akruphi/far2l/wiki

Missing /tmp/far2l-py file means, I suggest, the python plugin "subsystem" is not loaded. Check the far2l debug log.

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

https://github.com/elfmz/far2l/wiki/Running-far2l-with-debug-logging-enabled

Not sure if this environment variable helped, but now there is a /tmp/far2l-py file, thanks.

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

@faerot
In most cases you are right - lack of documentation should not be a problem, the API is like far2l, types and number of parameters.
The problem may be cffi - it has its requirements, and really only one - holding references so that gc does not unexpectedly remove variables passed to far2l, and this can be seen even in the constructor of each py file.

from pluginmanager.py:

USERHOME = os.path.expanduser("~/.config/far2l/plugins/python")

logging.basicConfig(level=logging.INFO)


def setup():
    if os.path.isdir(USERHOME):
        sys.path.insert(3, USERHOME)
        fname = os.path.join(USERHOME, "logger.ini")
        if os.path.isfile(fname):
            with open(fname, "rt") as fp:
                ini = configparser.ConfigParser()
                ini.read_file(fp)
                logging.config.fileConfig(ini)


setup()

mkdir ~/.config/far2l/plugins/python
cp logger.ini ~/.config/far2l/plugins/python

after restarting far2l the login should be visible in the file

debugging is a bit more complicated, I use vscode with the "attach to python" configuration
howto:
run far2l, run the udebug plugin, you can configure it (F11)
i.e. in the command line you type 'py:debug' and far2l freezes - you are waiting for the debugger from vscode to be connected
when vscode is connected, far2l will respond to the keyboard again

if the interpreter hits:
import debugpy
debugpy.debugger()

then in vs code you will go to debug this place

in other words you use the debugger as if you were debugging remote code, the same with docker containers, flask, fastapi, remote rpi, ...

you can also load plugins automatically and that's what plugins.ini is for

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

documentation - I don't know what to document since the plugin adds by itself:

  • 2 functions that make life easier:
def s2f(self, s) - return a string acceptable by far2l from python string
def f2s(self, s) - return a string acceptable by python from far2l pointer

  • 2 variables self.ffi and self.ffic

self.ffi is a wrapper to low-level functions from cffi like new,cast,string, ... documented by cffi
self.ffic is a holder to defined constants from far2l i.e. ffic.OPEN_DISKMENU, exposed is every #define and enum from far2lcffi.py

  • messagebox is almost the same as in c++ code:
self.info.Message(
    self.info.ModuleNumber, # GUID
    self.ffic.FMSG_WARNING | self.ffic.FMSG_LEFTALIGN, # Flags
    self.s2f("Contents"), # HelpTopic
    MsgItems, # Items
    len(MsgItems), # ItemsNumber
    1, #ButtonsNumber
)

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

The only place that can be mysterious is the dialogbuilder.

I don't like calculating the size of controls and their position, and just like wxPython does with sizers, this is how dialogbuilder works. HSIZER arranges controls horizontally, and VSIZER vertically.

Several controls are supported, and the principle is always the same:
BUTTON("vshow", "Show Me", flags=self.ffic.DIF_CENTERGROUP) - shows button inside dialog
ctrontrol(id, parameters_to_thar_control) - the parameters are identical to those used when creating dialogues by far2l

dialogbuilder then materializes own variable ID_vshow, which can be used in the function handling the dialog, i.e.:
if Param1 == dlg.ID_vshow:

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

@m32
Yeah, I have figured out most of it by digging through the source code. It's probably my expectations were a bit high, I was expecting more "pythononic" interface, not just bare bones C API which makes partially negates benefits of using python.

Better than nothing, so still appreciated the hard work, but in order to make the plugin writing process enjoyable, I will have to add an abstraction layer around those cffi calls.

documentation - I don't know what to document since the plugin adds by itself:

You say that as if there is a documentation of C++ plugins API. Or is there? So I will not have to figure our the hard way why FCTL_GETPANELINFO returns 0/1 and FCTL_GETCURRENTPANELITEM returns items size.

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

I meant the cffi documentation, not the far plugin creation documentation.
How to do, what parameters, ...? I also gained this knowledge when I encountered a problem in python.

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

I don't really see any possibility of writing some general classes that would free me even more from the C API.
I.E. two plugins: adb and docker, practically do the same thing, they show files on a remote device and I couldn't find any common abstract methods to simplify them.

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

I meant the cffi documentation, not the far plugin creation documentation. How to do, what parameters, ...? I also gained this knowledge when I encountered a problem in python.

cffi for me is not a problem, becuase it is common and well documented library. The problem is Far plugin API. Currently I am using an old help file from Far 1.70, but it is out of date in many places.

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

I don't really see any possibility of writing some general classes that would free me even more from the C API. I.E. two plugins: adb and docker, practically do the same thing, they show files on a remote device and I couldn't find any common abstract methods to simplify them.

I am currently finishing a simple plugin, once I will do, I will post it's source code to give you an idea of what I meant.

@unxed
Copy link
Contributor

unxed commented Sep 10, 2024

Probably we need a wiki article like "developing a python plugin: getting started" :)

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

Ok, I have written a small plugin. Back in the days of Far 1.70 there was a plugin which allowed to jump between selected files on the panel. This was very helpful if you have thousands or even tens of thousands files in the directory, select some of them by mask and wants to figure out which files were actually selected. I have bound those jumps to Alt+Up and Alt+Down.

Here is the implementation of this plugin, when all of the Far details are abstracted away in the pythonic library:

from yfar import FarPlugin

class Plugin(FarPlugin):
    label = "Jump Between Selected Files"
    openFrom = ["PLUGINSMENU", 'FILEPANEL']

    def OpenPlugin(self, _):
        panel = self.get_panel()
        option = self.menu(('Jump to &Previous Selected File',
                            'Jump to &Next Selected File'), self.label)
        if option == 0:  # move up
            for f in reversed(panel.selected):
                if f.index < panel.cursor:
                    panel.cursor = f.index
                    break
        elif option == 1:  # move down
            for f in panel.selected:
                if f.index > panel.cursor:
                    panel.cursor = f.index
                    break

This not pseudo-code, but an actual working plugin code.

This is what I meant when said I was hoping for a pythonic interface to Far. This kind of interface (especially if properly documented) would make writing plugins for far a breeze, even for not very technically savvy people.

Library attached. yfar.zip

Also question - how to make some of the python plugins auto-load on Far start without the need to py:load pluginname ?

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

copy Plugins/python/plug/plugins/plugins.ini to ~/.config/far2l/plugins/python, in [autoload] section add next line with file name without suffix (.py) under what name this plugin is available, like below for ucharmap.py:
[autoload]
ucharmap=

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

added to far2l :)

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

    log.error('Control({}, {}, {}, ...) failed, expecting {}'.format(

log is undefined ;)

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

added to far2l :)

LOL. It was only a proof of concept and implements only limited number of methods necessary to implement said plugin posted above, I don't think it is worth adding there in it's current stage.

It could turn out into proper library eventually though, if I will be writing more plugins and adding more interfaces.

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

log is undefined ;)

Yeah, was a bit too hard on cleaning things up before uploading. yfar.py needs

import logging

log = logging.getLogger(__name__)

@m32
Copy link
Contributor

m32 commented Sep 10, 2024

release often, release early maybe someone else will find it useful

api is not stable either, but it's always better to have something than nothing

@faerot
Copy link
Contributor Author

faerot commented Sep 10, 2024

Alright, I plan to implement at least couple of plugins that I miss from windows far, so will extend that library as well.

@faerot
Copy link
Contributor Author

faerot commented Sep 11, 2024

@m32 how to make Python plugin menu appear only in panels and not in editor?
If I write openFrom = ['PLUGINSMENU', 'FILEPANEL'] it shows both in panels plugins menu and in editor plugins menu,
if I write openFrom = ['FILEPANEL'] it does not show at all.

@m32
Copy link
Contributor

m32 commented Sep 11, 2024

python is queried for settings, and it queried all loaded plugins for settings, hence the sharing, see #2337

@faerot
Copy link
Contributor Author

faerot commented Sep 11, 2024

I don't understand that issue, it's about hotkeys shifting. I am saying that when you press F11 in panels and F11 in editor, how to make plugin to be displayed in panel plugin list, but not in editor plugin list.

@m32
Copy link
Contributor

m32 commented Sep 11, 2024

GetPluginInfo should return information about available options in DiskMenu, PluginsMenu and PluginConfig - this is taken from each python plugin.
It also has to set flags in Info.Flags - and here is the problem, they are set rigidly in the python plugin to PF_EDITOR|PF_VIEWER|PF_DIALOG - hence the python plugin is always after F11 in the editor, viewer and dialogs - so all python plugins are there too

@faerot
Copy link
Contributor Author

faerot commented Sep 11, 2024

So in other words - it is impossible, if I understand it correctly.
What openFrom = ['PLUGINSMENU', 'FILEPANEL'] affects then?

@m32
Copy link
Contributor

m32 commented Sep 11, 2024

far2l when opening a plugin (OpenPlugin), tells where to get it from (OpenFrom parameter) and additionally gives a number from the list obtained from the GetPluginInfo call - it looks very confusing to me and I couldn't handle it.

At the moment I haven't come up with anything better, and there was a time when the plugins I selected from the menu wouldn't open :)

@faerot
Copy link
Contributor Author

faerot commented Sep 17, 2024

I have created another plugin I was missing from windows version - Batch Rename.
In this plugin you select files and then invoke editor to rename all selected files at once:
зображення
I have also updated the pythonic far library as well a little bit.
How shoud I share my progress on library and plugins? Should I push the commits?

@shmuz
Copy link
Contributor

shmuz commented Sep 17, 2024

How should I share my progress on library and plugins? Should I push the commits?

It would be great to see your library and plugins on GitHub as a repository.

@m32
Copy link
Contributor

m32 commented Sep 17, 2024

The python plugin is part of far2l, so it's best to clone the repository and add fixes (PR's) in the normal way.

By the way, maybe it's time to add some plugin to manage add-ons?

@faerot
Copy link
Contributor Author

faerot commented Sep 20, 2024

Ok, I have created a pull request with 2 new plugins and a (work-in-progress) library.

@faerot
Copy link
Contributor Author

faerot commented Sep 23, 2024

Pull request was merged, I assume the issue is sort of closed, at least for me.

@faerot faerot closed this as completed Sep 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants