Freeplane-Jumper icon indicating copy to clipboard operation
Freeplane-Jumper copied to clipboard

Execute operations directly from the Jumper window. For example: Connect.

Open lilive opened this issue 2 years ago • 15 comments

@euu2021 wrote:

Execute operations directly from the Jumper window. For example: Connect. So, the operation is executed on the node selected in the search list. This would be useful, for example, to make it easier to use connectors as backlinks.

I understand the idea. How to provide something like that while keeping Jumper as simple as possible ?

Ideas :

1 - If the operation to execute on a node in the search result require only this node to be selected, we may say that it's enough convenient that the user select the node (and so close the Jumper window), execute the operation he wants to, then jump back to the previous location with Freeplane > Navigate > Go backward if he needs to. The problem that I just discover is that Jumper change the locations history as the user browse the results, and that a single "Go backward" do not always return to the place where Jumper was invoked. I think this should be fixed.

2 - If the operation to execute require two nodes to be selected (like Connect), and that the user want them to be the node selected at Jumper invocation and one node in the search result, there would be this solution: Ctrl+Enter or Ctrl+Click on a node in the search results may add it to the selected nodes, instead of simply select it, and close the window. Then the user would execute the operation he wants to, for example "Connect".

What do you think ?

lilive avatar Feb 16 '23 18:02 lilive

I just realized that it's possible to call Jumper within another script :smile: . So this simple 2 lines script wait for the user to select a node with Jumper, then create a connector between the node it came from and the new selected node.

lilive.jumper.Jumper.start()
// The variable 'node' is the node selected when the user call the script.
// 'node' do not change after the user jump to another one,
// but we can get the new one with c.getSelected().
// (see ControllerRO in Freeplane scripting API)
node.addConnectorTo( c.getSelected() )

If we want to return to the first node, simply add a third line:

lilive.jumper.Jumper.start()
node.addConnectorTo( c.getSelected() )
c.select(node)

So it is possible to use Jumper to execute various operations on the selected node.

lilive avatar Feb 16 '23 18:02 lilive

I think I will modify the Jumper.start() method to make it return the select node, and null if Jumper is closed without selection. So it will be possible to write:

node.addConnectorTo( lilive.jumper.Jumper.start() )

And perhaps something like:

selection = c.getSelecteds().collect()
while( n = lilive.jumper.Jumper.start() ){
    selection << n
}
c.select( selection )

that will let the user to select multiple nodes with jumper to whatever operation he wants.

lilive avatar Feb 16 '23 20:02 lilive

I think I will modify the Jumper.start() method to make it return the select node, and null if Jumper is closed without selection.

Nice! If I understood correctly, the companion script will then be able to work like this:

  • The user chooses the result in Jumper
  • Jumper closes
  • a GUI opens with all the possible actions, like:
Connect
In/Out

Move as child
In (move node B into node A)/Out (move node A into node B)

Copy as child
In/Out

Clone
In/Out

Copy Style
In/Out

This GUI can also have the checkbox for the user to choose if, after choosing the action, Jumper should open again for another node to be chosen.

euu2021 avatar Feb 16 '23 21:02 euu2021

Yes, this will be a possibility. Just to be sure: have you understood that I think that the companion script seems to me outside of Jumper goals ? But I can modify Jumper to make it more easier to use within others scripts. I have to think more, but perhaps something like that:

selected = Jumper.jump() // Use Jumper to select a node, and return it
selected = Jumper.pick() // Use Jumper to choose a node, but don't select it in the map
selection = Jumper.addToSelection() // Use Jumper to choose a node, and add it to the current selection. Return this new selection.

In the meantime you can start to script yourself Example:

Selection script:

// @ExecutionModes({on_single_node})

// Start Jumper and add the selected node to Freeplane selection

lilive.jumper.Jumper.start()
// Node selected after Jumper
selected = c.getSelected()
// If n is the same node than the node selected before Jumper, do nothing
if( selected == node ) return
// Create a list with the two nodes
selection = [ node, selected ]
// Select them
c.select( selection )

Create connector Out

// @ExecutionModes({on_single_node})

// Assuming two nodes are selected,
// create a connector from the first one to the second one,
// clear the selection and select only the first node.

// Get the list of the selected nodes
selection = c.getSelecteds()
// Do nothing if this list do not contain exactly 2 nodes
if( selection.size() != 2 ) return
// Create the connector between the first node (selection[0])
// and the second (selection[1])
selection[0].addConnectorTo( selection[1] )
// Clear the selection and select only the first node
c.select( selection[0] )
// Display this node in the center of the view
c.centerOnNode( node )

Usage:

  • Call the first script. After this you should have two nodes selected
  • Call the second script, it will connect the two nodes

Variation: Put everything in a same script

// @ExecutionModes({on_single_node})
lilive.jumper.Jumper.start()
selected = c.getSelected()
if( selected == node ) return
node.addConnectorTo( selected )
c.select( node )
c.centerOnNode( node )

Ideas to go further:

  • Create other scripts like the second one. To make 'Create connector In' just swap the numbers 0 and 1.
  • Create a script for each item in your list. At this point you will have something functional.
  • Put together these scripts in a menu with https://github.com/EdoFro/Freeplane_Menu-o-Matic ? Or write a GUI in a script (harder).

lilive avatar Feb 16 '23 23:02 lilive

Just to be sure: have you understood that I think that the companion script seems to me outside of Jumper goals ? But I can modify Jumper to make it more easier to use within others scripts.

Sure. Each user will have specific needs for the actions, so it's better to leave it for each user to create his own companion script.

Put together these scripts in a menu with https://github.com/EdoFro/Freeplane_Menu-o-Matic ? Or write a GUI in a script (harder).

I think I will try to create a setup like this:

  • Ctrl + Shift + Alt + J = Opens the complete GUI with the pairs of actions (In/Out). Here, there will be radio buttons, so the user can pick one pair to be assigned for the Quick Action
  • Ctrl + J = Quick Action (Out)
  • Ctrl + Shift + J = Reverse Quick Action (In)
  • Ctrl + Alt + J = Quick Action on multiple nodes

euu2021 avatar Feb 17 '23 00:02 euu2021

Now, what if I want to move multiple nodes into the search result? In your example, you use node, but how to work with multiple nodes?

This, for example, doesn't work (gives me "not allowed"):

y = c.getSelecteds()
lilive.jumper.Jumper.start()
y.each{it.moveTo( c.getSelected() )}

This will be useful when I'm working on my Inbox and distributing the tasks over the map. No more drag and drop for me 😄

euu2021 avatar Feb 17 '23 01:02 euu2021

This works for a single node (using c.selected):

def nodeList = [ ]
nodeList = c.selected
lilive.jumper.Jumper.start()
nodeList.each{it.moveTo( c.getSelected() )}

But, this doesn't work 🤔:

def nodeList = [ ]
nodeList = c.selecteds
lilive.jumper.Jumper.start()
nodeList.each{it.moveTo( c.getSelected() )}

euu2021 avatar Feb 17 '23 01:02 euu2021

Hi, ControllerRO.getSelecteds() return "A read-only list of selected nodes". The read-only seems to be the problem. Try:

nodeList = c.selecteds.collect()
lilive.jumper.Jumper.start()
nodeList.each{it.moveTo(c.selected)}

lilive avatar Feb 17 '23 08:02 lilive

Brilliant!! I will share in the FP forum.

euu2021 avatar Feb 17 '23 12:02 euu2021

This is a first time I consider to expose some Jumper methods for scripting. If the ability to call Jumper within Groovy scripts become a Jumper feature, I have to take care about future compatibility: all the methods Jumper expose now should work in the future. I wrote you about that to bring your attention to the fact that I will probably change the name of the actual Jumper.start() method in some incoming Jumper release, and that you will have to adapt your scripts at that time.

lilive avatar Feb 17 '23 13:02 lilive

Ok, I will keep an eye on that.

euu2021 avatar Feb 17 '23 13:02 euu2021

For the new scripting methods, please consider creating

  • one that has a parameter that is inserted in the Jumper search
  • one that returns the searched words

This will allow, for example, a companion script that works as a tag manager, as requested here. BTW, a tag helper was the most upvoted feature request in the old forum, and was frequently mentioned.

I'm thinking of a node that has all the tags as unique children, and then Jumper opens with a search result showing exactly this list. If the user searches for a word that still has no tag for it, then it will be inserted in the text and also create a new node in the tags node.

I'm also thinking about the now popular feature of note-taking programs: the notation {{}}:

  • The user types {{ inside the text
  • a search box appears
  • the user has the option to either
    • pick a search result to create a link to the current spot,
    • or use the searched text to create a new object

euu2021 avatar Feb 17 '23 14:02 euu2021

This discussion and the latest @euu2021 proposals are very exciting 👍🏼 I think that's what a lot of people look for in logseq or obsidian

bepolymathe avatar Feb 17 '23 15:02 bepolymathe

For the new scripting methods, please consider creating

  • one that has a parameter that is inserted in the Jumper search
  • one that returns the searched words

Here's a proposal. Can you comment or propose modifications please ? You may have a clearer vision if you read #15 first.

The idea is to have 2 methods, jump() and pick(). jump() is like the current Jumper behavior, with the ability to define some search options. pick() is almost the same, except that no node is selected in the map. Both of these methods will return a report of what the user did.

package lilive.jumper
class Jumper {
    /**
     * Open Jumper dialog, let the user find a node and select it in the map.
     *
     * @param useLastPattern Fill the text field with the last used search terms.
     * @return A report about what the user did. @see lilive.jumper.Report
     *
     * This is equivalent to start Jumper with the menu Edit > Find > Jumper.
     */
    Report jump( boolean useLastPattern = true ){ ... }
    /**
     * Same as jump(boolean), but allows to define the search options.
     *
     * @see lilive.jumper.search.SearchOptions
     */
    Report jump( String pattern, SearchOptions options = null ){ ... }
    /**
     * Same as jump(boolean,SearchOptions), but search in the given node branch.
     */
    Report jump( String pattern, Node node, SearchOptions options = null ){ ... }
    /**
     * Same as jump(boolean), but do not select any node in the map.
     *
     * @return A report about what the user did.
     *         The field 'jumped' of the report is always null.
     */
    Report pick( boolean useLastPattern = true ){ ... }
    /**
     * Same as jump(boolean,SearchOptions), but do not select any node in the map.
     *
     * @return A report about what the user did.
     *         The field 'jumped' of the report is always null.
     */
    Report pick( String pattern, SearchOptions options = null ){ ... }
    /**
     * Same as jump(boolean,Node,SearchOptions), but do not select any node in the map.
     *
     * @return A report about what the user did.
     *         The field 'jumped' of the report is always null.
     */
    Report pick( String pattern, Node node, SearchOptions options = null ){ ... }
}

And now the two classes to define the search options and to handle the report:

package lilive.jumper.search
/**
 * The settings that drive the search.
 * (This class already exists in Jumper code)
 */
class SearchOptions {
    boolean useRegex = false          // Use regular expressions
    boolean caseSensitive = false     // Case (in)sensitive search
    boolean fromStart = false         // Search the pattern at the beginning of the text
    boolean splitPattern = true       // Split the pattern into words (or smaller regular expressions).
                                      // A node is considering to match if it contains all of them, in any order.
                                      // Ignored if transversal is true.
    boolean transversal = true        // Find nodes that don't match the entire pattern if their
                                      // ancestors match the rest of the pattern
    boolean useDetails = true         // Search into the nodes details
    boolean useNote = true            // Search into the nodes note
    boolean useAttributesName = true  // Search into the attributes name
    boolean useAttributesValue = true // Search into the attributes value
}

package lilive.jumper
/**
 * Report what the user did with Jumper. 
 */
class Report {
    String pattern        // The search pattern used
    SearchOptions options // The search option used
    Node selected         // The last node the user has selected in the search results.
                          // null if Jumper was closed without user selection.
    List<Node> selecteds  // The nodes the user has selected in the search results.
                          // Empty list if Jumper was closed without user selection.
    Node jumped           // The node where the user has jumped. null if it was no jump
    List<Node> results    // The nodes that match the search pattern and the search options.
                          // May be empty, or incomplete if the user close Jumper before the search was done.
}

lilive avatar Feb 18 '23 00:02 lilive

Sounds good! This Report that is returned can be used for a lot of things.

euu2021 avatar Mar 15 '23 16:03 euu2021