Opening a theme file locks the initiating Leo session (#1425)

47 views
Skip to first unread message

Matt Wilkie

unread,
Dec 21, 2019, 1:26:31 AM12/21/19
to leo-e...@googlegroups.com

Issue #1425. Settings > Open A Theme File can look like it's not working. The Leo session that spawns the Leo-with-themefile locks and says "Not Responding" in the title bar until the Leo-with-themefile session is closed. Read the issue for more context. This is an ENB as I work towards resolution,

...

As currently written this menu command, defined in ../commands/commanderFileCommands.py-->Themes-->c_file.open_theme_file, will only work in Leo source code checkouts. That is, it's broken for anyone who installs from Pypi.org, from pip in a non-editable mode, or some other deployment mode that works similarly.

The problem part:

command = f'python launchLeo.py "{fn}"'
os
.system(command)

launchLeo.py doesn't exist outside a source code repo. In a pip deployment Leo is launched with PYTHONHOME/Scripts/leo. Others use their own launch wrapper somewhere else.

A few different fixes suggest themselves:

  1. search PATH for leo
  2. ask current Leo session what it's launch command is/was
  3. reproduce the contents of launchLeo.py as a python -c '...{py code}...' command line expression. (It's short enough that this is practical)

I don't like 1 & 3 much. I do like 2 but don't know yet how much work it will be. Well, time to start learning!

Along the way, we might as well replace os.command with a current recommended method as "The subprocess module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function. See the Replacing Older Functions with the subprocess Module section in the subprocess documentation for some helpful recipes." (ref)

-matt

Matt Wilkie

unread,
Dec 21, 2019, 3:07:45 AM12/21/19
to leo-editor
I couldn't find anyway of retrieving the command which Leo was launched with unless I added lines like the below to the launch scripts, and that's just getting in too deep.

# name of the script we're running in
import inspect
g
.es_print(inspect.getfile(inspect.currentframe()))



The problem part:

command = f'python launchLeo.py "{fn}"'
os
.system(command)

I did find a crude but workable fix:

#command = f'python launchLeo.py "{fn}"'
#os.system(command)
import sys
command
= f'{sys.executable} {g.app.loadDir}/runLeo.py "{fn}"'
#g.es_print(command)
import subprocess
subprocess
.Popen(command)

Both sys and subprocess are likely already available as Leo commands, so no need to import the modules again. I just need to figure out what they're called.

-matt

vitalije

unread,
Dec 21, 2019, 3:52:35 AM12/21/19
to leo-editor
Have you tried using sys.argv ? It usually contains the whole command line including the executable as sys.argv[0], but it seems that Leo startup code stripped that first argument from sys.argv. But it can replaced with sys.executable anyway. It seems that inside Leo sys.argv[0] is the python script that was used to start Leo.

Vitalije

Matt Wilkie

unread,
Dec 21, 2019, 11:37:22 PM12/21/19
to leo-editor
Oh! I hadn't thought of sys.argv, which is kind of weird since I've used it a lot in other places. Thanks!

[Later]: When Leo is started from the pip-created launcher sys.argv[0] is that exe. When started from python it's the name of the .py:

Started with `leo` sys.argv: ['c:\\apps\\miniconda3\\envs\\leo-dev\\Scripts\\leo']
Started with `python launchLeo.py` sys.argv: ['launchLeo.py']

We could test if sys.argv[0] ends with .py and insert sys.executable if yes, otherwise use as is.

-matt

Matt Wilkie

unread,
Dec 22, 2019, 12:13:30 AM12/22/19
to leo-editor

We could test if sys.argv[0] ends with .py and insert sys.executable if yes, otherwise use as is.

This is what that looks like (working code):

    # fix idea 2:
   
if g.sys.argv[0].endswith('.py'):
        command
= f'{g.sys.executable} {g.sys.argv[0]} "{fn}"'
   
else:
        command
= f'{g.sys.argv[0]} "{fn}"'
    g
.es_print(command)
    g
.subprocess.Popen(command)

And this is the first approach:

    # fix idea 1:
    command
= f'{g.sys.executable} {g.app.loadDir}/runLeo.py "{fn}"'
    g
.es_print(command)
    g
.subprocess.Popen(command)

At the moment I much prefer the simple and direct version.

Matt Wilkie

unread,
Dec 22, 2019, 12:45:56 AM12/22/19
to leo-editor
My fix pushed to devel with 016867f.

Things I learned exploring this issue:
  • Leo's g global module contains the python os, sys, and subprocess modules, so they don't need to be initialized (imported) in order to use them.
  • g.app.leoDir == leo-editor/leo, and g.app.loadDir == leo-editor/leo/core.
  • ./leo/core/runLeo.py is the real work horse for starting Leo. (I've often been confused about just how/where Leo is started.)
  • Leo needs to be restarted in order to pick up changes in this core file (probably true generally)
  • I really am able to identify, locate and fix a bug in Leo within an area I have no previous experience in! (At least once anyway. ;-)
What I still need to learn: how to write and run Leo unit tests.

-matt

Edward K. Ream

unread,
Dec 22, 2019, 5:55:07 AM12/22/19
to leo-editor
On Sun, Dec 22, 2019 at 12:13 AM Matt Wilkie <map...@gmail.com> wrote:
 
At the moment I much prefer the simple and direct version.

There are times when even a single "if" statement can cause a great deal of problems.  This may be one of those times.

Edward

Edward K. Ream

unread,
Dec 22, 2019, 6:10:36 AM12/22/19
to leo-editor
Excellent summary, and congratulations on exploring previously uncharted territory.

In general, it is manifestly impossible to use importlib.reload on Leo's core because reload does not update Leo's data structures.  Some unit tests have a "preamble" that calls importlib.reload, but those are rare exceptions.  Search for "preamble" in unitTest.leo.
 
What I still need to learn: how to write and run Leo unit tests.

It's easy.  Just open unitTest.leo.  To see the default bindings, do <alt-x>run-<tab>.

Don't bother running unit tests externally. As I write this I am wondering whether the run-*-externally commands could be retired...

Alt-4 runs all unit tests in the selected outline.  To run all the standard unit tests, select the node "Active Unit Tests" at the top level.

Alt-5 runs all the marked unit tests.  I typically use Alt-5 when developing unit tests, because then I don't have to select the desired node.

@test nodes create unit tests, without all the bother of creating an instance of unittest.TestCase. The @test startup code creates that instance automagically.

The simplest unit test:
@test pass
pass

Select the node, hit Alt-4, see the results in the console.

Within @test nodes, c, g and p are bound as usual. More importantly, "self" is bound to the created instance of unittest.TestCase.  So you can use self.assert*, and self.skipTest(reason) if the test depends on conditions that aren't met.

unitTest.leo contains hundreds of examples.  You can browse through them if you like looking for tricks :-) 

Edward

Matt Wilkie

unread,
Dec 22, 2019, 11:00:34 PM12/22/19
to leo-editor
Thank you for the feedback, and the especially the testing overview.

(I've built up an avoidance pattern around writing unit tests that it's time to dissolve. When I was taking a programming course I gave up in disguest when it took all afternoon to write a bug-free unit test for a function that took me half an hour to write, and the function was bug free. I know intellectually that over the long haul tests still save time, but I don't know it know it. Yet.)

-matt

Reply all
Reply to author
Forward
0 new messages