External Editor Choice helper application

Editor Choice Program: joplin_edit_choice.py

note: Hi There, I am a newbee on the forum, so if I have put this posting in the wrong section, let me know.

Rationale

This is a little python3 helper program that provides a graphical menu to select optional editors. It come out of necessity for me, sometimes working on a markdown file in Sublime Edit 3 if code intensive, or alternatively in Typora if predominantly textual. The Joplin editor is good, but sometimes, if my page/file has lots of links, images, or other 'markdown stuff', it can get a bit messy to see the text clearly. Hence, I use Typora. For code intensive stuff, I sometimes like to use Sublime Editor as it has macro recording and playback. Enough rationale...

Description

The Joplin Edit Choice program provides a simple graphical menu in tkinter that displays a list of buttons that when clicked, execute an editor program, accepting the filename to edit as the only command-line parameter.

This is not a 'plug -in' but an external program. It is set as Joplin's external Text Editor, and when <CTRL>+<E> is pressed, instead of launching an editor, it launches this program in a small window. The program displays a vertical row of buttons, each corresponding to an editor choice. When you click a button, that editor is launched and the name and path to the Joplin markdown file is passed as the only parameter. You can edit with your editor of choice, hitting save as you edit exactly like you normally would with the external editor launched directly from Joplin. When you have finished with your external editor and close it, this little helper app closes, and as per normal, you are back in Joplin. Do not forget to again hit <CTRL><E> to exit External Editing Mode in Joplin.

Of course, the little menu has an Exit option, so you can hit <CTRL><E>, Exit, and <CTRL><E>, over and over to have hours of procrastinatory fun. Not really, it is just to back out of the menu if you made a mistake.

This program when launched looks like this:

image
As you can tell, it's not very complex.

Let me know if you have any suggestions or changes that I might like. Otherwise, enjoy and write your little hearts out. :slight_smile:

License and Warranty

None of either. Do with it what you will. I hope it helps you in some way.

Parameters

Joplin only passes a single argument to the external editor. For that reason, there is no point of executing full command-line argument processing. Only argv[1] is of interest.

Location & Setup

In my Linux system program is located at ~/bin/joplin_edit_choice.py on my computer, and is set in the Joplin application options as the external Text Editor Command.

Source Code

#!/bin/python3

import tkinter as tk
import os
import sys

author="Paul Thompson"
email="mrptwriter@gmail.com"
version="1.0.0.1"
lastUpdated="15 Sep 2021"

#
# Change the names of the editors throughout the program to
# suit your editors of choice, and also change the paths to suit your
# operating system application locations. Mine is Linux with standard
# apt installations of typora, code, sublime-editor, and kate, so the 
# applications are located in /usr/bin . I would have included examples 
# for flatpak and snap applications, but they are a pain in the xxxx to 
# set up as external editors, so I avoid them.
#
typora='/usr/bin/typora '
sublime='/usr/bin/subl '
vscode='/usr/bin/code '
kate='/usr/bin/kate '
editFilename=sys.argv[1]

root = tk.Tk()

v = tk.IntVar()
v.set(1)  # initializing the choice, i.e. Python

languages = [("Typora", 101),
            ("Sublime Edit", 102),
            ("Visual Studio Code", 103),
            ("Kate", 104),
            ("Exit", 999)]

def ShowChoice():
    myChoice = v.get()
    if myChoice == 101:
        os.system(typora + editFilename)
        exit()
    if myChoice == 102:
        os.system(sublime + editFilename)
        exit()
    if myChoice == 103:
        os.system(vscode + editFilename)
        exit()
    if myChoice == 104:
        os.system(kate + editFilename)
        exit()
    if myChoice == 999:
        exit()

tk.Label(root, 
         text="""Editing markdown with:""",
         justify = tk.LEFT,
         padx = 20).pack()

myChoice=0

for language, val in languages:
    myChoice = tk.Radiobutton(root, 
                  text=language,
                  indicatoron = 0,
                  width = 20,
                  padx = 20, 
                  variable=v, 
                  command=ShowChoice,
                  value=val).pack(anchor=tk.W)

root.mainloop()
5 Likes

Maybe you can make a plugin

Since it's a Linux system, you can actually use xdg-desktop-portals, like the snap/flatpak use. They look like this

If you're up for experimenting for an alternative implementation, you might fancy rewriting this with pydbus, I've even a pretty simple Go reference here, and you could rip out the writable support and the URL support in your case.

The only problem I notice is the version of Typora I have sets itself as the exclusive markdown editor, which ruins the selection results by only showing Typora. I had to edit the .desktop file to get it to be less aggressive. (The QT backend does better here IMO).

4 Likes

Thanks a bunch for providing this script. This category is the perfect place for 3rd party apps and scripts for Joplin. :+1:

1 Like

Thanks for this however you might want to consider a slightly more idiot-proof guide as this idiot has had/is having a few problems in getting this to work mostly due to not really having played with python before (hardly your fault).

Using Linux Mint 20.2

I created a file called "joplin_edit_choice.py" in `~/Documents
I edited the file a bit to pick up stuff from my own computer, namely:

atom='/usr/bin/atom '
vscode='/usr/bin/code '
#marktext='/home/uname/.local/share/applications/marktext.desktop '
marktext='/home/uname/appimages/marktext-x86_64.AppImage '
editFilename=sys.argv[1]

and

languages = [("Atom", 101),
            ("Visual Studio Code", 102),
            ("MarkText", 103),
            ("Exit", 999)]

def ShowChoice():
    myChoice = v.get()
    if myChoice == 101:
        os.system(atom + editFilename)
        exit()
    if myChoice == 102:
        os.system(vscode + editFilename)
        exit()
    if myChoice == 103:
        os.system(marktext + editFilename)
        exit()
    if myChoice == 999:
        exit()

When I attempted to run the script by opening a terminal in ~/Documents (where the script is located) and running

$ python3 joplin_edit_choice.py 
Traceback (most recent call last):
  File "joplin_edit_choice.py", line 3, in <module>
    import tkinter as tk
ModuleNotFoundError: No module named 'tkinter'

Had to do some google-fu having never really used python before and found the following to run

$ python3 -m tkinter
/usr/bin/python3: No module named tkinter
$ sudo apt-get install python3-tk
$ python3 -m tkinter

This at least got me a little closer as the tkinter demo box shows up, if I now run

$ python3 joplin_edit_choice.py testdoc.txt

I get some warnings but it otherwise runs and opens my file in the application I pick

(node:61003) DeprecationWarning: file property is deprecated and will be removed in v5.
(node:61003) DeprecationWarning: init() is deprecated and will be removed in v5.

However I'm now not sure how to get this working with Joplin. I've tried a couple of different things to put into Tools > Options > General > Text editor command > Path

  • /home/uname/Documents/joplin_edit_choice.py
  • ~/Documents/joplin_edit_choice.py
  • python3 /home/uname/Documents/joplin_edit_choice.py

Whenever I click the open in editor button nothing happens. It works normally for other text editors e.g. atom in that field.
Am I doing something stupid or do I have some kind of permissions based issue here?

Joplin is complaining about the following in the dev tools log on the button click:

internal/child_process.js:267 Uncaught (in promise) Error: spawn /home/uname/Documents/joplin_edit_choice.py ENOENT
Command was: "/home/uname/Documents/joplin_edit_choice.py" /home/uname/.config/joplin-desktop/edit-4b3f914a9ef141649da202b42398d603.md
    at Process.ChildProcess._handle.onexit (internal/child_process.js:267)
    at onErrorNT (internal/child_process.js:469)
    at processTicksAndRejections (internal/process/task_queues.js:84)

Thanks for the reply. Yes, if I had wanted it for 'just' Linux, I would have looked at other options. Feel free to bonsai it and build your.

Thanks for the reply. Sorry you've had so many problems. I will investigate that and reply properly tomorrow. It is 12:30 am here in Gawler, so I need to get to sleep. Chat tomorrow.

Note to all: The above python script is just a means to an end for me. Not wanting to spend a lot of time on tweaking and refining or rewriting into something better. I am happy for it to be rewritten by any of you. I will help out those having problems getting the script to run, where I can, but it meets my need, so I need to get on with other writing work. Like we used to say in the Air Force, "There comes a time when you need to lock up the engineers so you can get on with flying the plane." :man_pilot: So, feel free to mod it or rewrite it.

Daeraxa, I will reply to you tomorrow.

Cheers from Down Under. Paul

1 Like

Honestly not a problem in the slightest, I could just be a moron here. I don't know if I currently have a need for it or not, I was just experimenting to see if it is something I could use.
As for the issues, Iwas just documenting the problems I had and things I was able to resolve in case it helps anyone else if they stumble upon it, nothing worse than "dw I fixed it".

Nope, and do not even think that. Talk to you tomorrow. Cheers, Paul

1 Like