Difference between revisions of "FreeplaneJrubyInstaller - developers"

From Freeplane - free mind mapping and knowledge management software
 
(43 intermediate revisions by 2 users not shown)
Line 1: Line 1:
  
 +
This wiki page is intended for new programmers that want to '''try Ruby, and make Freeplane ruby scripts'''.
  
== Lets play first and then latter read the instructions ==
+
If you just want to '''''run a ruby script''''', then instead read [[FreeplaneJrubyInstaller_Addon|FreeplaneJrubyInstaller Addon]] <br />
 +
If you want to know '''''how the addon was made''''', then instead read [[FreeplaneJrubyInstaller_-_making_of|FreeplaneJrubyInstaller - making of]]
  
  
* In Freeplane, click '''Tools/Jruby.../Ruby Live Debugger.../Debug in paralell Thread (GUI responsive)''', and wait (2min) until a new black window appears
+
I estimate that only a small number of people will actually feel curious to learn ruby and make freeplane scripts with ruby, which makes us - the ''freeplane-ruby-scripts programmers'' - a '''really small and nice club''' :)
  
* Now follow one of these video examples:
+
So, if you anytime feel ''part-of-the-club'' (or just want to say hello), dont be shy and drop a comment in [https://sourceforge.net/p/freeplane/discussion/758437/thread/324b9cb0/ this forum thread: Announcing FreeplaneJrubyInstaller !!!!]
  
 +
Hope we all have a great time :)
  
TODO
 
  
Ideas:
 
1: nodnode
 
2: node
 
3: node.text
 
4: invokeAndWait { node.createChild("Hi im child")
 
5: }
 
6: invokeAndWait { node.createChild("Hi im another child") }
 
7: invokeAndWait { node.text
 
8: }
 
9: node.text
 
10: invokeAndWait { node.text += "--> Added into the end of text <--" }
 
11: 3.times.do { |i| invokeAndWait { node.createChild("Hi im grandson #{i}") } }
 
12: 3.times.each { |i| invokeAndWait { node.createChild("Hi im grandson #{i}") } }
 
13: node.fold
 
14: node.folded?
 
15: node.folded = !node.folded?
 
16: node.folded = !node.folded?
 
17: invokeAndWait { node.folded = !node.folded? }
 
18: hist
 
19: hist --replay 3..12
 
  
 +
So here we go: learn ruby, program freeplane, have fun :)
  
  
NOTES:
 
After editing an existing script, you dont need to restart freeplane. That is, you can just edit the ruby script, save it, and then in freelpane rerun the script, without restarting freeplane!
 
  
invokeAndWait {}
+
== Ruby scripts examples ==
* does not return a value from the block (always returns nil)
+
 
    [12] pry(main)> invokeAndWait { 1 }
+
Start programming by tinkering with existing code, and seeing what happens. When you get stuck or need to understand something better, then go read some introduction book/tutorial (just enough) and get back to tinkering again. Its the fun of tinkering that fuels to get better,  understand more, go more complex.
    => nil
+
 
    [13] pry(main)> invokeAndWait { 5 }
+
 
    => nil
+
=== Sort children by icon ===
    [14] pry(main)> invokeAndWait { true }
+
[https://github.com/zipizap/freeplaneRubyScripts/raw/master/sort_by_icons.rb sort_by_icons.rb]
    => nil
 
    [15] pry(main)> invokeAndWait { "invokeAndWait always returns nil, no matter what happens inside the block" }
 
    => nil
 
    [16] pry(main)> invokeAndWait { raise "Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file" }
 
    => nil
 
  
 +
[https://github.com/zipizap/freeplaneRubyScripts/blob/master/sort_by_icons.rb see it in github]
  
* ruby exceptions that happen inside the block are not shown in the debug window, but they are registered in freeplane log file
+
* Select a node, execute script, and the children will be ordered/sorted by their icons
    [5] pry(main)> invokeAndWait { node.thisMethodDoesNotExist }
+
* Related to [https://sourceforge.net/p/freeplane/discussion/758437/thread/3ada3b06/ this forum thread "Possible sort children by icon 'priority' or 'progress'?" from Cadux]
    => nil
 
   
 
    [16] pry(main)> invokeAndWait { raise "Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file" }
 
    => nil
 
  
   
 
    # exception is registered in freeplane logs: <freeplane_user_dir>/logs/log.0
 
    STDERR: Exception in thread "AWT-EventQueue-0"
 
    STDERR: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `thisMethodDoesNotExist' for #<Java::OrgFreeplanePluginScriptProxy::NodeProxy:0xdbe721>
 
    STDERR: at RUBY.block in evaluate_ruby((pry):6)
 
  
    STDERR: Exception in thread "AWT-EventQueue-0"
 
    STDERR: org.jruby.exceptions.RaiseException: (RuntimeError) Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file
 
    STDERR: at RUBY.block in evaluate_ruby((pry):18)
 
   
 
  
  
  
===  
+
== Freeplane ruby script = Ruby + Freeplane Scripting Api ==
  
  
TODO: continue here
+
A ruby script for freeplane, is a '''ruby''' program that will use '''Freeplane java-objects from the [http://www.freeplane.org/doc/api/ Freeplane Scripting API]'''
  
  
 +
To learn how to make ruby scripts for freeplane, the ideal learning-path is to:
 +
# [[FreeplaneJrubyInstaller_-_developers#Learn_the_basics_of_Ruby|Learn the basics of Ruby]]
 +
# Know how to call the Freeplane-java-objects in your ruby script. This is in fact 2 things:
 +
## [[FreeplaneJrubyInstaller_-_developers#Learn_what_freeplane-java-objects_are_available_for_you_-_the_Freeplane_Scripting_API|Learn what freeplane-java-objects are available for you - the Freeplane Scripting API]]
 +
## [[FreeplaneJrubyInstaller_-_developers#Learn_how_to_call_freeplane-java-objects_in_.28J.29ruby|Learn how to call freeplane-java-objects in (J)ruby]]
  
=== The making of the FreeplaneJrubyInstaller addon - a very long story for curious minds ===
 
  
NOTE: Reading this section is completely optional, the technical details are very resumed and a bit oriented for experienced programmers and curious-minds :) All interesting information about how to *use* the addon is resumed in other previous sections.
+
In the next sections, we'll go through this learning-path, which I trimmed down to the essentials (with links for further exploration, if/when you need)
  
  
For me this addon is a personal give-back to the open freeplane community, in return for creating and maintaining freeplane - I enjoy using it a lot :)
 
  
I first started making this addon as an experiment, but later realized that the addon could also be usefull to others users in the freeplane community. So I took it as a long-time-geek-hobby, which slowly evolved at the rythm of my scarce free time.
 
  
 +
=== Learn the basics of Ruby ===
  
With the addon I had in mind to accomplish:
+
Ruby is a simple language, easy to learn for new programmers, with lots of books, guides, videos and explanations in internet (almost too much)
* easy install of jruby-engine in freeplane (jruby is the added piece that makes freeplane understand Ruby scripts). Make this a no-fuss seamless-process to the normal freeplane user
 
* easy programming/creation of ruby scripts for freeplane, specially for newbie-programmers that want to try scripting in freeplane
 
* easy use/execution of ruby scripts in freeplane, for the casual-non-technical users that just want to use some freeplane-ruby-script.rb
 
  
 +
In internet there are lots of learning material about "Ruby" and about "Ruby on Rails", but stay away from "Ruby on Rails" because that is another umongous universe (using ruby to make webpages). For freeplane scripting, we just need to learn ''Ruby'' (without Rails!), and even then, just the ''basics'' of Ruby.
  
While making the addon, there where a lot of decisions taken, and small details choosen by me. I want to explain them, in case they are usefull for other programmers to understand why things were made this way, and hope that someday someone improves the addon by implementing other better decisions. I did not knew where to start or how to explain all this, so I just started writting the "making of" the addon.
 
  
 +
To start with Ruby, I recommend you read and follow these learning links:
  
If you are a programmer or a curious mind who wants to follow the history of how it's made and works internally, keep reading. If not, just skip it, its ok :)
+
* [http://tryruby.org/levels/1/challenges/0 Try ruby in 15 minutes, in the browser]
 +
** >> I think this should be your first contact with ruby: a guided interactive tour to do simple things in ruby
 +
** Its all online, no install needed, and seems like 15-30min
 +
** From here take a general idea of ruby - you'll see these things explained in more detail in other guides/books
  
----
+
* [https://pine.fm/LearnToProgram/ Learn to Program - A Place to Start for the Future Programmer]
 +
** >> I think this should be your second contact with ruby - understand a bit more of what is ruby. <br /> While reading the book, have in the side a browser open with the [https://repl.it/languages/ruby Online Interactive Ruby (online irb)] (see bellow) to copy/paste code from the book, and try for yourself
 +
** Small online book made to teach ruby for new programmers.
 +
** Covers the basics, and seems well written
  
 +
* [https://repl.it/languages/ruby Online Interactive Ruby (online irb)]
 +
** >> Keep this close to you everytime you are learning ruby, it allows you to try ruby code without installing anything. This is the equivalent of an ''online'' '''irb'''.
 +
** Instead of installing ruby in your pc and then use its "irb" to test ruby code, you can use this ''online'' irb without installing anything :)
 +
** Good to try out small code snippets, like examples from a book, or for you to explore ruby
  
  
Great, you decided to keep reading - hope you enjoy. Tried to lay it out as clear as possible :)
+
Also, the following links are optional '''support-material''' - they will not teach you ruby but if you have them somewhere close they will be helpfull many times.  
Grab a beer or a coffee/tea and bucle your seatbelt - its a long story and sometimes we'll move fast :)
 
  
 +
They contain more than info than you will ever need, but are good consulting references, to clearout doubts, see examples, etc. I still use them myself:
  
I love freeplane. Since like 5 years now I use it everyday to organize my work and personal notes.
+
* [http://www.digilife.be/quickreferences/qrc/ruby%20language%20quickref.pdf Ruby QuickRef Card (print it)]
 +
** >> Print these 2 pages in 1 sheet and keep it around in the desk - it will help you many times to remember ruby syntax. Take notes on it as you need :)
  
At first, the simple tree structure with some background colors was enough to be usefull, but with time I discovered that the multiple options of freeplane could fit my needs to better structure knowledge. No doubt a big part of freeplane-usefullness is '''how''' people use it.
+
* [http://www.zenspider.com/ruby/quickref.html Zenspider Ruby QuickRef (bookmark it)]
 +
** >> This is '''the best quickref of ruby'''. I always search here first. Its too big to be printed (what a pitty) but whenever you want, you can open it online, and "find" what you want
 +
** Its a great reference to be consulted when needed! I never got even close to know all of it, so don't sweat. Just a reference, use it as you need it :)
  
So, someday I gave some attention to this '''scripts''' thing that freeplane had - basically you could write some code that would use the '''[http://freeplane.sourceforge.net/doc/api/ Freeplane API]''' to programatically make freeplane do things.
+
* Search in google "TutorialsPoint Ruby Basics" - TutorialsPoint Ruby Basics (consult when needed)
The code was in the Groovy language which is a kind of java-that-looks-a-bit-like-ruby, and I did not know groovy nor java, but ruby was (is) my prefered language and I feel very comfortable programming in ruby.
+
** >> Use it like support-material: when you have a doubt about something specific, search for it in here
So although with no knowledge of Groovy, I could slightly understand the groovy scripts, and managed to make a couple of simple scripts, basically from copy/pasting/modifying from examples of the [http://www.freeplane.org/wiki/index.php/Scripts_collection scripts collection page] and got my homemade very-simple-groovy-scripts going on. No big groovy knowledge there, I just did the basic modify-try-and-error cycle which many times is enough for simple things.
+
** It packs good info in short resumes. Sometimes a bit canonical, but the examples can be of use for you and clarifying
 +
** Use it to consult, like a ''reference'' with examples and resumes.
 +
** This wiki filters the webpage link as spam (which is not), so use google to reach it
  
Time passed, and sometimes, very rarely, I would notice that my personal usage of freeplane, could be boostered with some little scripty-automation.
 
This lead me to get bolder in making Groovy scripts, and soon enough I got to the point that I had to learn/understand a little more about Groovy to make my scripts more complex - the copy/paste/modify magic was falling short to what I wanted to program.
 
  
As it turns out, for another project, I passed the previous 6 months of scarse freetime learning C++ from a "renowned" book, and out of boredoom and curiosity I casually took a Java book to my one-week-vacations (instead of C++), and in one week of late-nigh-readings I ended up feeling more capable with Java than I felt with 6 months of C++ reading.
+
For freeplane scripts, its enough to learn only the basics of ruby - its up to you to decide how deep you want to learn it. In any case, have an overall idea of Ruby, and comeback to the above links whenever you need.
And that was the tipping point to drop the study of C++ in favour of Java and use Jruby (Jruby = Java + Ruby) for that other project. The book that introduced java so well to me was Head First Java, and it made a very funny reading :) Take it to your vacations if you want to learn Java :)
 
  
So, luck took me to learning a bit of Java, and I knew that Groovy was java-that-looks-a-bit-like-ruby, so I started to improve my groovy scripts with the new java understanding. And it worked :) To a certain point.
+
<small> NOTE: If someday you want to become a ruby pro, you will want to read the "pickaxe" book: [https://pragprog.com/book/ruby4/programming-ruby-1-9-2-0 The Pragmatic Programmers' Guide, Programming Ruby] If you are a new programmer, dont start here. </small>
I managed to make the more complex scripts that I wanted with Java and Groovy. Still it costed me an enourmous efforth, as I could only do it in scarce free time, which meant sometimes severall weeks passed by between code-session of only a few hours, and that was my only practice of java and groovy. It was working, but carried along a steep beginner-overhead for me, far from efficient/productive, and only worth for high-priority-not-very-simple-but-not-too-complex scripts. Yes, a step forward, but not yet the promised land.
 
  
  
  
 +
=== Know how to call the Freeplane-java-objects in your (J)ruby script ===
  
 +
==== Learn what freeplane-java-objects are available for you - the Freeplane Scripting API ====
  
==== Jruby: Freeplane can also run ruby scripts with Jruby! ====
+
Now, we will skim over the [http://www.freeplane.org/doc/api/ Freeplane Scripting API], which are a group of Java classes and its methods.
  
  
On the other hand, I did read [http://www.freeplane.org/wiki/index.php/Scripting:_Other_languages in the wiki that it is possible to use other programming languages for scripting (scala, jython, etc) through the JSR 223]. With some try-and-error I got jruby to work with freeplane (very simple after all!), and got all excited - using jruby I could write scripts for freeplane in the '''ruby''' language (instead of groovy/java).
+
All the Freeplane Scripting API are '''Java''' classes and methods.
So, I started to think about making an addon to easily enable ruby scripts with freeplane. And that motivation slowly lead to the creation of the FreeplaneJrubyInstaller addon.
 
  
My first intention was for the addon to simply install Jruby in Freeplane to enable ruby scripts, and just that.
+
This will not make much sense to you now, after having learned the basics of ruby, because you don't know how to use ''Java'' classes/methods in ''ruby''. These java classes will not be ''used'' just now, I will just ''present'' them so that you have an idea that they exist, and where you can find them latter, when you need them.
  
I had to learn [http://www.freeplane.org/wiki/index.php/Add-ons_%28Develop%29 how to make a freeplane addon] (which after you read and try, you notice it's really easy!).
 
Then I noticed that to make freeplane aware of jruby it was sufficient to put the file jruby-xxx.jar in 'freeplane-user-dir/lib/jruby-xxx.jar'. However the jruby-xxx.jar file is +20MBytes in size, which is too much to be packaged inside an addon.mm  (that gave memory problems, both when building the addon myself, and later when installing the addon which would affect others who tried to install it - a no-go), and the alternative I came up was to make a java-downloader-window, that would be executed after the addon was installed in freeplane, and that would simply download the jruby-xxx.jar file from internet. That was not easy but got it done (the right way!) :) Also, I left the default download url pointing to jruby-9.0.4 (the jruby version I use and know it works!) but that can be easily changed in the future to upgrade to more recent versions of jruby (only if/when there is a convincing argument to upgrade! A prior jruby-9.0.something.jar did not work because of a jruby bug!). The ablity to change the download-url was done on purpose, as a simple method that allows future upgrades of jruby.jar
 
  
Just for future reference, to upgrade jruby.jar to a newer version, the procedure would be:
+
The freeplane-java-objects are documented in the [http://www.freeplane.org/doc/api/ Freeplane Scripting API], which is a Java API, where you can see the '''classes''' , the '''methods''' of each '''class''' and a short description of what each '''method''' does. The Freeplane Scripting Api should be open by your side while you'r making freeplane scripts - you will consult it all the time. Click the "FRAMES" link at the top, to show in the webpage a left-side-navigation-list with all the Classes available.
* close freeplane
 
* manually delete the existing jruby-xxx.jar file in 'freeplane-user-dir/lib/jruby-xxx.jar'
 
* start freeplane
 
* click the menu entry 'Tools -> Jruby... -> Check/Install Jruby version...'
 
* and automatically it will detected that there is no jruby-xxx.jar present, which will open the java-downloader-window, where you can manually write a new download-url pointing to the new jruby version that you want to install. (scrap download-urls from [http://jruby.org/download] )
 
* after the new jruby-xxx.jar is downloaded, restart freeplane, and you can then use the new jruby version
 
  
  
And so with the addon installed (and jruby post-downloaded), one could write a freeplane-ruby-script (ex: '<freeplane-user-dir>/scripts/my_ruby_script.rb') and then restart freeplane and click in the new menu entry ''Tools/Scripts/my_ruby_script'' to run it. In Preferences/Plugins some options had to be enabled, but that was also easy.
+
To learn freeplane scripting, the most interesting class of the [http://www.freeplane.org/doc/api/ Freeplane Scripting API], is [http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html Proxy.Node]
 +
* Represented in groovy and ruby scripts by the variable <code>node</code> 
 +
* Look to these java methods: '''getText()''' and '''setText("whatever")'''
  
  
So, at this point the addon could install jruby in freeplane. With jruby installed, freeplane could already run ruby scripts.  
+
In Ruby scripts (imitating what happens with Groovy scripts), there are some handy '''special variables predefined''' for us to use in scripts, that point to usefull java-classes:
 +
* <code>node, c</code> - as explained here http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/ScriptUtils.html
 +
* <code>ui, logger, htmlUtils, textUtils, menuUtils, config</code> - as explained here http://www.freeplane.org/doc/api/index.html?org/freeplane/plugin/script/FreeplaneScriptBaseClass.html
  
Also, I've also noticed that it was possible for other future ''addons to use ruby instead of groovy addon-scripts inside the addon'' (via the addon.mm#scripts node, just like its now possible with .groovy scripts)
 
  
 +
   
  
A good accomplishment, great!
 
  
 +
==== Learn how to call freeplane-java-objects in (J)ruby ====
  
  
 +
In last section I ''presented'' the Freeplane Scripting API, which are a group of '''Java''' classes/methods. In this section, I will focus on how to ''use'' '''Java''' classes/methods inside a '''(j)ruby''' script.
  
  
==== Adding to ruby scripts the variables: c, node, ui, logger, textUtils, ... ====
+
Read this section once, and take a pause. Then re-read it again. It's normal to don't understand-it-all in the first time (and second), at least that's what happened to me. <br /> So know in advance that this will not be all understood in one pass - so read it relaxedly and let it sink-in afterwards. <br /> Its a bunch of concepts, and you dont need to master all of them to start with scripts. The natural evolution is to get an idea, use examples, and if/when you need then comeback and dive deeper into understanding.  
  
  
However the ruby scripts did not had these handy variables defined - '''c, node, ui, logger, textUtils, ...''' - they had to be manually extracted from java classes... that's possible but it's annoying/awkward to do it in every single script. In groovy scripts these variables ''just work'' and that's great, so in ruby scripts the same would be desirable. That was possible to do using some ruby metaprogramming magic.
 
  
The code is in the file ''freeplane.rb'' (with descriptive comments!), you can find it in ''freeplane_user_dir/addons/freeplaneJrubyInstaller/FreeplaneJrubyCommonEnvironment/lib/freeplane.rb''. This file is automatically required by '''freeplane_jruby_common_environment.rb''', so in any freeplane-ruby-script its only necessary to '''require "freeplane_jruby_common_environment.rb"''' to have these variables availabe for use in *any* scope (like global variables).
+
<small>
 +
NOTE: Ignore the following if you are starting with scripting
  
 +
If/when you want to understand more about this topic, then:
 +
* have a look into the Jruby wiki page dedicated to [https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#calling-java-from-jruby Calling Java from JRuby]
 +
* get this book [https://www.google.es/search?q=using+jruby+inurl%3Apdf Using JRuby - The pragmatic programmers]
 +
** read only "Chapter2 Driving Java from Ruby" and "Appendix B: Ruby/Java Interoperability"
 +
** you just need to read those 2 sections of the book, its a 2-3h read, not much
 +
** dont stress out - just read it once to get an idea, and get back to it if/when you latter need it.
 +
** NOTE: if you are a java programmer getting into jruby, this book may be '''''it'''''
 +
</small>
  
{{FreeplaneJrubyInstallerNote|
 
* all freeplane-ruby-scripts need to '''require "freeplane_jruby_common_environment.rb"''' to use the variables '''c, node, ui, logger, textUtils, ...'''
 
* You need to have at least these freeplane preferences enabled, in Tools/Preferences/Plugins
 
[[File:Preferences_jruby_script.png|frame|400x200px|none|link={{filepath:Preferences_jruby_script.png}}]]
 
}}
 
  
  
  
  
==== Rubygems: All ruby scripts share one local gems-home folder ====
+
The '''ruby''' language cannot use java-objects by default, but the '''J'''ruby-complete.jar adds some special-jruby-magic that makes possible for the ruby programs executed by the '''j'''ruby-complete.jar to '''''use''''' java-objects.
  
 +
So '''Jruby''' = ruby + <some-special-magic-that-enables-us-to-call-java-objects-from-inside-jruby-scripts>.
  
After, I realized that ruby scripts may at some point in the future, need to use '''rubygems'''. Rubygems by default uses a default system-wide directory to store all the gems, for all ruby programs (freeplane and non-freeplane). I did not wanted to use that systemwide-default-rubygems-folder, I wanted to use an isolated and self-contained rubygems-folder, where only the gems necessary for the freeplane-ruby-scripts could be stored and read from.  
+
That jruby <special-magic> is refered as "Driving Java from Ruby", and I will resume here some basics of it, enough for freeplane-ruby-script. For  more info, see links at the end of section
  
I've investigated about how to use only a local-rubygems-folder... First I started with [http://bundler.io/ bundler] but it turned out to be an unexpected dead-end for this, and finally concluded that the best solution was to (avoid bundler and) define the environment variables GEM_HOME and GEM_PATH (no dependencies, clean-small code, easily understood) to indicate to rubygems that it should only load gems from a specific '''local gem-home folder'''. The result of using GEM_HOME and GEM_PATH is that the '''''freeplane-ruby-scripts only see and only use the gems present in this local gem-home folder'''''. Great, gems containment and isolation achieved :)
 
  
I've decided to place the local gem-home folder in '''freeplane_user_dir/addons/freeplaneJrubyInstaller/FreeplaneJrubyCommonEnvironment/gem_home'''.
 
The code that makes this local gem-home ready to use in scripts, is in the file 'only_gem_home.rb' file, you can find it in 'freeplane_user_dir/addons/freeplaneJrubyInstaller/FreeplaneJrubyCommonEnvironment/lib/only_gem_home.rb'. This file is automatically required by '''freeplane_jruby_common_environment.rb''', so in any freeplane-ruby-script its only necessary to '''require "freeplane_jruby_common_environment.rb"''' to only use gems from this local gem-home.
 
  
 +
In a normal '''R'''uby program (not '''J'''ruby, just '''R'''uby as you saw in [[FreeplaneJrubyInstaller_-_developers#Learn_the_basics_of_Ruby|Learn the basics of Ruby ]]), there is:
 +
* '''ruby code''' that ''drives/uses''
 +
**  '''Ruby-objects''' (Ruby-classes/methods)
  
{{FreeplaneJrubyInstallerNote|
 
* all freeplane-ruby-scripts need to '''require "freeplane_jruby_common_environment.rb"''' to use the local ''gem_home'' folder. After that line, the gems (installed in gem_home) can be require'd normally in your script (ex: require 'awesome_print')
 
* all freeplane-ruby-scripts will use one common rubygems folder, which is the local ''gem_home'' folder, located in ''freeplane_user_dir/addons/freeplaneJrubyInstaller/FreeplaneJrubyCommonEnvironment/gem_home''
 
}}
 
  
NOTE: If you want to ''manage (list/install/uninstall/...) gems in local gem_home'' then have a look and edit the files ''freeplane_user_dir/addons/freeplaneJrubyInstaller/FreeplaneJrubyCommonEnvironment/gem.bat or gem.sh''
+
In a '''<span style="background:GreenYellow">J</span>'''ruby program (not '''R'''uby, but '''J'''ruby now), there is:
 +
* '''ruby code''' that ''drives/uses''
 +
** '''Ruby-objects''' (Ruby-classes/methods)
 +
** <span style="background:GreenYellow">'''Java-objects''' (Java classes/methods) </span>
  
  
NOTE2: If you are making an '''''addon with ruby-scripts that needs more gems''''', then instead of adding gems into the common ''.../FreeplaneJrubyCommonEnvironment/gem_home'' which is shared by all freeplane-ruby-script, its better for you to package your own independent addon_gem_home folder, with a modified ''only_gem_home.rb'' pointing to the addon_gem_home folder. This is easier and garantees that your addon will be deployed and executed with exactly the same gems that you want (instead of relying on the common ''.../FreeplaneJrubyCommonEnvironment/gem_home'' which is intended to serve as a common base for the non-addon freeplane-ruby-scripts, and may even be tinkered by curious users out there :) ). However, (many) more gems could slow down yours scripts execution which may be annoying for users - have that tradeoff in mind.
 
  
 +
Jruby can access any Java-class, however we will focus mainly on the Java objects from the [http://www.freeplane.org/doc/api/ Freeplane Scripting API]
  
  
So, at this point the addon was capable of installing Jruby, and then freeplane-ruby-scripts could be run from menu ''Tools/Scripts/my_ruby_script''
 
The freeplane-ruby-script should have a '''require "freeplane_jruby_common_environment.rb"''' that defines the variables '''c, node, ui, ...''' and also enables the gems (installed in gem_home) that thereafter can be required normally by the script.
 
  
Great :)
+
===== Access Scripting API Java-classes: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config =====
  
 +
Jruby can access any Java-class, however we will focus mainly on the Java-classes from the Freeplane Scripting API, and not all of the API java-classes, but just in those that are most usefull.
  
  
 +
And we are in luck, that the Freeplane Scripting Api has some '''special variables predefined''' for us, to access the most-usefull [http://www.freeplane.org/doc/api/ Freeplane Scripting API] classes: '''node, c, ui, logger, htmlUtils, textUtils, menuUtils, config''' (see [[FreeplaneJrubyInstaller_-_developers#Learn_what_freeplane-java-objects_are_available_for_you_-_the_Freeplane_Scripting_API|Learn what freeplane-java-objects are available for you - the Freeplane Scripting API]]). <br />So you can simply use '''node''' to access the [http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html Proxy.Node] Java-class, and the other special-variables to access other most-usefull java-classes from the Scripting API.
  
 +
So, Jruby can access any of the Freeplane Scripting API Java-classes, but from aaalll the classes of the API, only a small number of them is actually usefull, and for those there are '''special variables predefined''' that give quick access to those java classes.
  
==== RubyLiveDebugger ====
+
I recommend that to start playing with Scripting API, you only use the '''[http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html node]''' (and maybe others '''special variables predefined'''). <br />
 +
The '''[http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html node]''' will allow you to read/write into a node text, details, attributes, ... so should give more than enough runway for a smooth start into scripting :)
  
===== Theory: Pry, Pry-remote, and why we open an external terminal (debug window) =====
 
  
Well, with all that ready, the next step would be to write some freeplane ruby scripts! And, as it happens to everyone who programs, at some point you'll find an error and debug your code for that error...
 
When an error is found during execution of your ruby script, a short error-message-window will show up in freeplane (which tipically will not show the line number of the error). On the other hand the error will also be logged into the freeplane log file (freeplane_user_dir/logs/log.0), where you may find the line number of the error among other usefull info such as the ruby exception raised. So keep in mind that the log file will have more info about the error than what is shown in the small error-message-window that pops up.
 
  
And there you go reading the log file for the line number of your error, then opening the ruby script and rechecking that line and trying to mentally recompute and fing the error. And thats ok, but there is a better way availabe in ruby - there is this gem called [http://pryrepl.org/ Pry] that is used normally to ''debug a program live while it is still executing'' (its a REPL launched inside a running program). Pry is very usefull, you just put a line of code in the middle of your program where you want, and then run your program and when the program execution gets to that line it waits for you to interact with it: read variables, change variables, call methods, etc, it's live coding inside your program in that point. Have a look a some youtube videos to understand it better, or wait 5 min to read this to the end and then it try yourself, its pretty easy to use, and we will use it with freeplane!
 
  
However Pry expects the program to be launched from a terminal and reuses the terminal's stdin/stdout to interact with the user, but we are running a ruby program with jruby which is called from java and the program stdin/stdout is who-knows-where... :) the stdout at least seems to be logged into the log files, but stdin is out-of-reach.
+
<small>
For these cases (and other similar that happen in the ruby universe, like daemon processes), there is another gem called  [https://github.com/Mon-Ouie/pry-remote Pry-remote] which uses tcp/ip to communicate between a pry-remote-server listening on 127.0.0.1:9876, to which its possible to connect with a pry-remote-client. The pry-remote-server is attached to the ruby program, and waits a connection from the pry-remote-client. The pry-remote-client is executed from another terminal, and will connect to the pry-remote-server via Tcp/Ip and use the client-terminal stdin/stdout to allow the user to interact with the ruby program (attached to pry-remote-server). So, using pry + pry-remote, we can debug a freeplane-ruby-script in the middle of its execution, from an external terminal that is running the pry-remote-client, and inspect the running freeplane-ruby-script to see its variables value and try new commands.
+
NOTE: Ignore the following if you are starting with scripting
  
 +
If/when you need to access any other Java-classes, you can do it like this:
 +
More info in https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby
  
Using pry + pry-remote in this way implies severall things:
+
    # Access Java-classes of Scripting API that are  -->> Proxy.Xyzzz <<-- with:
* 1) have the freeplane-ruby-script execute a pry-remote command (in the line where the debug will occur). This will start the pry-remote-server which will stay listening and waiting for a tcp connection in 127.0.0.1:9876
+
    Java::org.freeplane.plugin.script.proxy.XyzzzProxy    # Java: Proxy.Xyzzz  (notice that XyzzzProxy <---- Proxy.Xyzzz )
* 2) in a separate terminal window, launch the pry-remote-client (using java + jruby + the pry-remote gem)
+
    Java::org.freeplane.plugin.script.proxy.MapProxy      # Java: Proxy.Map
* 3) finally once pry-remote-client is openned and connected, the user can debug the freeplane-ruby-script using that terminal (typing in keyboard, reading in screen). Tab is very usefull (autocompletion!)
+
    Java::org.freeplane.plugin.script.proxy.NodeProxy    # Java: Proxy.Node
 +
        #
 +
        # NOTE: In technicall rigor, I believe that the Java-Interface 'Proxy.Node' has a Java-Class 'NodeProxy',
 +
        # and that Java-Class is used in Jruby as 'NodeProxy'
 +
        # I believe that is why all the Java-Interfaces (all the Proxy.Xyzzz) are accessed with 'XyzzzProxy'
 +
        #
 +
   
 +
    # Access Java-classes of Scripting API that are not -->> Proxy.Xxxxx <<-- with:
 +
    Java::org.freeplane.core.util.FreeplaneVersion        # Java: FreeplaneVersion
 +
    Java::org.freeplane.core.util.HtmlUtils              # Java: HtmlUtils
 +
    Java::org.freeplane.core.util.LogUtils                # Java: LogUtils
 +
</small>
  
  
  
  
As can be seen, manually launching up a pry-remote debug session is a bit daunting... and repeating it every time we want to debug something is not pleasant and a distraction...
 
So I automated it programatically so that '''a script-developer would not need to think about all this, but just use (write) a special command inside the freeplane-ruby-script where one wants to open a debug window:''' '''''RubyLiveDebugger.open_debug_here(binding)'''''
 
  
You can put the '''''RubyLiveDebugger.open_debug_here(binding)''''' in multiple lines - when the program is being executed, each time one of those lines is reached a debug window will open for you to debug the ruby script in that point of execution.
+
===== Call Java-methods: getXXX(), setXXX(yyy), isXXX(), ... =====
  
 +
Ok, so in last section we saw we can use the special-variables to access the most usefull java-scripting-classes
  
 +
Now we'll see how to '''call''' a ''java''-'''method''''' from those ''java-classes''.
  
The RubyLiveDebugger class is defined in file ''ruby_live_debugger.rb'' located in ''freeplane_user_dir/addons/freeplaneJrubyInstaller/FreeplaneJrubyCommonEnvironment/lib/ruby_live_debugger.rb'' This file is automatically required by '''freeplane_jruby_common_environment.rb''', so in any freeplane-ruby-script its only necessary to '''require "freeplane_jruby_common_environment.rb"''' to have available the '''''RubyLiveDebugger.open_debug_here(binding)''''' command
+
In Jruby, you can call Java-methods in multiple ways - they all do the same thing, just written differently:
  
The RubyLiveDebugger is most usefull for freeplane-ruby-scripts developers - it's a kind-of development tool (a debug mode!), and so its not usefull/necessary to all other casual-non-technical-freeplane-users that just want to run/use an already-made ruby-script that they copied.
+
    # Calling java-methods in "java-style"
 +
    the_node_text = node.getText()
 +
    node.setText("hello!")
 +
    bool_value = node.isRoot()
 +
    node.createChild("Im a newborne child node")
 +
   
 +
    # In ruby the parenthesis are optional
 +
    # So we could write it as:
 +
    the_node_text = node.getText
 +
    node.setText "hello!" 
 +
    bool_value = node.isRoot
 +
    node.createChild "Im a newborne child node"
 +
   
 +
    # Jruby also allows to call aJavaMethod() like this: a_java_method()
 +
    # So we could write it as:
 +
    the_node_text = node.get_text
 +
    node.set_text "hello!"
 +
    bool_value = node.is_Root
 +
    node.create_child("Im a newborne child node")
 +
   
 +
    # Jruby also allows to call the java-methods getXXX(), setXXX(yyy), isXXX() like this: 
 +
    #
 +
    #            a_value = obj.getXXX()        can be written as as        a_value = obj.XXX
 +
    #
 +
    #            obj.setXXX(yyy)                can be written as as        obj.XXX = yyy
 +
    #
 +
    #            a_bool = obj.isXXX()          can be written as as        a_bool = obj.XXX?
 +
    #
 +
    # (for more info see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#alternative-names-and-beans-convention)
 +
    # So we could write it as:
 +
    the_node_text = node.text
 +
    node.text = "hello!" 
 +
    bool_value = node.root?
 +
    node.createChild("Im a newborne child node")
 +
   
 +
   
 +
It's up to you to decide which to use.<br /> For an easy start, use the  <code>node.getText()</code>  (the "java-style") that is closer to the Scripting API syntax. <br /> With time, you may get more confortable with <code>node.text</code> and <code>node.root?</code>
  
  
  
===== Debug-session commands (!!@ and friends) =====
 
  
Inside the pry-remote debug-session, you can execute normal ruby code and also use the '''''[https://github.com/pry/pry/wiki/State-navigation#Exit_program pry-commands]'''''
 
  
The following pry-commands are more than enough to do basic debugging - you really only need the "!!@":
+
===== Call Java-methods: conversions between ruby-type <--> java-type (automatic and manual) =====
  * up/down keys  : to navigate in past commands (command history)
 
  * '''!!@'''            : to exit from debug-session
 
  * '''_''' (underscore) : the result of last evaluated expression (usually the result from previous line)
 
  * '''ls MyObject'''    : list the methods of MyObject (see also "ls -h")
 
  * '''help'''          : to list all the other pry-commands
 
  
If you are really curious about other pry-commands, knock yourself out:
+
So now that we know how to call a java-method from Jruby, lets see how we can give arguments and receive return-values from the java-methods in our Jruby programs.
* [http://www.linuxjournaldigital.com/linuxjournal/201207?pg=29#pg29 this linux-journal article] gives an overview of pry
 
* [http://railscasts.com/episodes/280-pry-with-rails?autoplay=true this demo video shows off pry in 8min] (from its creator)
 
* [https://github.com/pry/pry/wiki/Documentation-browsing the pry-wiki] is where there is lots of more info... (lots)
 
 
 
 
 
 
'''To exit from a debug session, its mandatory to send the command  !!@  ''' - only exit with '''''!!@''''' and never with 'exit' or 'quit' or closing the debug window directly.
 
This is necesary for the pry-remote-server process to cleanup and properly release/continue the execution of the freeplane-ruby-script past the debug line. (the worst it can happen is to freeze freeplane indefinitely untill you restart it manually)
 
  
 +
This section will be a long one. Just keep the idea that there are conversions going on with ruby/java and if/when you need to go into it, then this is here to help you.
  
 
{{FreeplaneJrubyInstallerNote|
 
* all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''RubyLiveDebugger.open_debug_here(binding)''' and when the script is executed it will open a debug window in that point of the script.
 
* to enable and use the debug command '''RubyLiveDebugger.open_debug_here(binding)''' the freeplane-ruby-script must have a '''require "freeplane_jruby_common_environment.rb"'''
 
* exit from a debug session with command '''''!!@''''' and never with 'exit' nor 'quit' or closing the debug window directly
 
* For RubyLiveDebugger to work, you need to have at least these freeplane preferences enabled, in Tools/Preferences/Plugins
 
[[File:Preferences_RubyLiveDebugger.png|frame|400x200px|none|link={{filepath:Preferences_RubyLiveDebugger.png}}]]
 
}} 
 
  
  
 
+
As we saw before, in a '''<span style="background:GreenYellow">J</span>'''ruby program (not '''R'''uby, but '''J'''ruby now), there is:
 +
* '''ruby code''' that ''drives/uses''
 +
** '''Ruby-objects''' (Ruby-classes/methods)
 +
** <span style="background:GreenYellow">'''Java-objects''' (Java classes/methods) </span>
  
  
  
 +
Each Java-class has '''methods'''.
  
And now, at this point the addon was capable of installing Jruby, and then freeplane-ruby-scripts could be run from menu ''Tools/Scripts/my_ruby_script''
+
Each Java method takes 0-or-more '''arguments''', and returns a '''return-value'''
The freeplane-ruby-script should have a '''require "freeplane_jruby_common_environment.rb"''' that defines the variables '''c, node, ui, ...''' and also enables the gems (installed in gem_home) that thereafter can be required normally by the script. That require (together with certain adjusted Preferences) also enables the debug command '''RubyLiveDebugger.open_debug_here(binding)''' that can be put in any place of a script to open a debug window when that script is executed.
 
  
Awesome :)
 
  
  
  
 +
* In the pure '''Java-world''' there are only '''java-objects''': in a java program, you have only java-objects, and java-methods expect to work with java-objects.
  
===== RubyLiveDebugger: EDT-Thread (Gui freezed) vs Parallel-Thread (Gui responsive) =====
+
* In the pure '''Ruby-world''' there are only '''ruby-objects''': in a ruby program, you only have ruby-objects, and ruby-methods expect to work with ruby-objects.
  
This next 2 parts will get a bit complex so better tell you now that these sections are completely optional - you can stick to debug using the '''RubyLiveDebugger: EDT-Thread (Gui freezed')'' with <code>RubyLiveDebugger.open_debug_here(binding)</code> as indicated in [http://freeplane.sourceforge.net/wiki/index.php?title=FreeplaneJrubyInstaller_Addon#RubyLiveDebugger].
+
* In the '''<span style="background:GreenYellow">J</span>ruby-world''', there are both '''java-objects''' and '''ruby-objects''' coexisting in the jruby program
 +
** Jruby has a magical-invisible bridge between the '''Ruby-world''' and the '''Java-world'''
 +
** In Jruby when you call a '''java-method''' with arguments that are '''ruby-objects''', then Jruby will automatically try to convert the arguments-''ruby''-objects into appropriate arguments-''java''-objects that can be used internally by the java-method
 +
** Also, in Jruby after a java-method is called, it internally delivers a return-value that is a '''java-object''', ''but'' Jruby automatically tries to convert it into an appropriate '''ruby-object''' that is returned into the jruby-program (if it cannot convert, it returns the unconverted java-object)
  
The explanation is a bit complex, and can be a turndown for first-starters, so I would leave the reading for a latter time.
+
These conversions are predefined for the most usefull ruby/java types (see bellow) and work so well normally that you dont even think about them.
What we will do next is to open a debug-session in ''paralell'' with freeplane-unfreezed - that is, we can debug freeplane and see the results instantly refreshed in the freeplane-gui !!!
 
If you want, skip the next explanation and simply read the resume in the end and test it yourself. Or watch the demo videos (end of the section) and go straight into wild-hacking without any theory (and sometime latter if you need to, come back to read the theory)
 
  
 +
But in some few cases, when there is no automatic-conversion possible, then you may need to manually convert between java-objects/ruby-objects:
 +
* a java-method can only work with arguments that are java-object internally: so if in the jruby-program you give it an argument that is a ruby-object, then jruby will internally try to convert that ruby-object into a java-object; but if jruby cannot do the conversion automatically (because it does not have an automatic conversion defined for that specific pair of ruby-object>>java-object types) then '''you need to make the conversion manually in you jruby-program''' <small>(because the java-method needs ''java-objects'' as arguments, it does not understand ruby-objects)</small> and craft yourself an already-converted-java-object (instead of the unconverteable-ruby-object) to use it as the argument for the java-method.
 +
* on the other hand, for return-values its much simpler: if Jruby cannot convert the internal-java-object-return-value to a suitable ruby-object, then it will simply deliver the java-object to the jruby-program (jruby-programs understand java-objects along with ruby-objects).
  
____
 
  
'''RubyLiveDebugger: EDT-Thread (Gui freezed)'''
 
  
  
As you will see when you start using the RubyLiveDebugger, while the debug-window is working, the freeplane gui (window) will be freezed and unresponsive. This is due to the fact that freeplane scripts (all of them: groovy or ruby) are executed inside the Java EDT-thread (the [https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html Java Swing EDT-Thread]) which makes the freeplane-gui freeze since a script starts executing untill the script finishes executing, when freeplane-gui will unfreeze again.
+
The conversions can be made in 2 directions: ruby-object -> java-object and java-object -> ruby-object
  
 +
For the most used ruby-types and java-types there are predefined "conversions" that are '''automatic'''.
 +
For other ruby/java types the conversion are not automatic and should be made '''manually''' by the programmer (you)
  
Basically, imagine that freeplane works in this (imaginary) sequence:
 
* Freeplane is running normally
 
* The user clicks a menu-entry to launch a freeplane-script, and
 
** the freeplane-gui will freeze and start executing the script in EDT-Thread
 
*** the script starts executing...
 
*** ....the script continues executing...
 
*** ...the script finishes executing
 
** the freeplane-gui will unfreeze (and visual changes will only now be updated in the gui)
 
  
  
Normally freeplane scripts execute so fast that we barely notice freeplane freezing. However when we put the '''RubyLiveDebugger.open_debug_here(binding)''' in a script, the script will open a debug session and will wait for the debug-session to terminate (!!@). The freeplane-gui will remain freezed, untill the debug session terminates (!!@) and untill the script finished completely, and only then freeplane-gui will unfreeze and update any visual changes.
+
====== Automatic jruby<->java conversions ======
  
 +
The '''automatic''' conversions (made seamlessly by Jruby) between most used ruby-types and java-types, are:
  
Basically, imagine that freeplane with '''RubyLiveDebugger.open_debug_here(binding)''' in a script works in this (imaginary) sequence:
+
[[Image:FreeplaneJrubyInstaller__160908_170934.png]]
* Freeplane is running normally
 
* The user clicks a menu-entry to launch a freeplane-script, and
 
** the freeplane-gui will freeze and start executing the script in EDT-Thread
 
*** the script starts executing...
 
*** ... the script continues executing...
 
**** ... '''RubyLiveDebugger.open_debug_here(binding)''' is executed and opens a debug-session with a debug-window
 
**** ... user debugs in debug-window - (this stage takes minutes while all other stages take miliseconds)....
 
**** ... in the end, user writes "!!@" which terminates the debug-session
 
*** ... the script continues executing...
 
*** ... the script finishes executing
 
** the freeplane-gui will unfreeze (and visual changes will only now be updated in the gui)
 
  
 +
For more indepth-info, see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#ruby-to-java and https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#java-to-ruby
  
While inside a debug-session (debug-window) you can read and '''change''' direclty the freeplane-java-objects values (ex: <code>puts(node.text); node.text="text changed" </code>) but because the freeplane-gui is freezed, you will not see any visual changes reflected in the freeplane-window immediately. When you finish the debug session with "!!@", then the script will continue executing, and when the script finishes completely, then the freeplane-gui will unfreeze and reflect all the visual changes at once.
 
  
 +
====== Manual jruby<->java conversions ======
  
Think of the EDT-Thread as a lonely CPU, that can only execute 1 line of code at a time. That EDT-Thread normally waits for mouse-input and keyboard-input, and refreshes the freeplane-gui. When we execute a script with a lot of code in that EDT-Thread, while the code of the script is being executed the CPU is busy and cannot do anything else, and so the freeplane-gui cannot be refreshed, nor the mouse-input processed nor the keyboard-input be attended. So while the code of the script is being executed in the EDT-Thread, the freeplane-gui is freezed. When finally the script code finishes executing, then the EDT-Thread will have oportunity to execute the freeplane-gui code and refresh the freeplane-gui with all visual changes, and attend mouse/keyboard-input, etc... You get the idea, right?
+
Here are some '''manual''' conversions that '''you''' as programmer might have to use, for some ruby-types >> java-types, and ruby-types << java-types:
 +
* '''java.util.List --> ruby-Array'''
 +
    # java.util.List --> ruby-Array: use .to_a
 +
    a_java_array_list = java.util.ArrayList.new([1,2,3])  # or Vector, LinkedList, Collection, ...
 +
    a_ruby_array = a_java_array_list.to_a
 +
    #=> [1, 2, 3]
  
 +
* '''java.util.Map --> ruby-Hash'''
 +
    # java.util.Map --> ruby-Hash: use .to_hash
 +
    a_java_hash_map = java.util.HashMap.new(:a => 123) # or Hashtable
 +
    a_ruby_hash = a_java_hash_map.to_hash
 +
    #=> {:a = 123}
  
Ok, re-read this a couple of times if necessary to get a more or less clear picture of what is happening in the EDT-Thread - in the next paragragh we will give it a twist...
+
* '''ruby-Time --> java.util.Date'''
 +
    # ruby-Time --> java.util.Date: use .to_java
 +
    a_ruby_time = Time.now
 +
    a_java_date = a_ruby_time.to_java
 +
    #=> #<Java::JavaUtil::Date:0x747541f8>
  
 +
* '''java.util.Date --> ruby-Time'''
 +
    # java.util.Date --> ruby-Time
 +
    a_java_date = java.util.Date.new
 +
    a_ruby_time = Time.at(a_java_date.getTime/1000)
 +
    #=> 2018-01-26 23:49:32 +0100
  
 +
====== Examples of jruby<->java conversions (automatic and manual) ======
  
____
+
Some examples of conversions:
  
'''RubyLiveDebugger: Parallel-Thread (Gui responsive)'''
+
    # NodeProxy.setNoteText() expects as argument a java.lang.String object
 +
    # but we can give as argument a ruby-String, and Jruby will automatically convert the ruby-String into a java.lang.String
 +
    # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html#setNoteText-java.lang.String-
 +
    node.setNoteText("Hi Im a ruby-string")
  
 +
    # NodeProxy.getNoteText() returns a java.lang.String object, which Jruby automatically converts
 +
    # into a ruby-String
 +
    # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getNoteText--
 +
    a_ruby_string = node.getNoteText()
  
Now, imagine you have 2 distictive CPUs, where each CPU can run a distinctive thread: one CPU can run the EDT-Thread and another-CPU can run another-thread.
+
    # NodeProxy.getChildren() returns a java.util.List<Proxy.Node>
Now you can execute 2 threads of code ''at the same time'', 1 thread in each CPU. So while CPU#1 runs the EDT-Thread code, the CPU#2 can run another-thread code - so EDT-Thread-code runs '''''in parallel''''' to the another-thread-code.
+
    # Jruby cannot automatically convert a java.util.List, but you can convert it manually into a ruby-Array
 +
    # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getChildren--
 +
    a_java_list = node.getChildren()
 +
    a_ruby_array = a_java_list.to_a
 +
   
  
So, imagine that freeplane with '''RubyLiveDebugger.open_debug_here(binding)''' in a script works in this (imaginary) sequence, using 2 threads (2 CPUs).
+
   
For simplicity, lets consider that
 
* [CPU#1] only executes the EDT-Thread (where freeplane-gui "lives") and certain parts of freeplane-script
 
* [CPU#2] only executes the another-thread, where  '''RubyLiveDebugger.open_debug_here(binding)''' executes
 
  
And with [CPU#1] for EDT-Thread and [CPU#2] available for another-thread, imagine this:
 
* [CPU#1] Freeplane is running normally
 
* [CPU#1] The user clicks a menu-entry to launch a freeplane-script, and
 
** [CPU#1] the freeplane-gui will freeze and start executing the script in EDT-Thread
 
*** [CPU#1] the script starts executing...
 
*** [CPU#1] ... the script continues executing...
 
**** [CPU#2]... the script executes '''Thread.new { RubyLiveDebugger.open_debug_here(binding) }''' and switches to '''''another-thread [CPU#2]''''' where it executes the debug-session with a debug-window  (executing in [CPU#2])
 
*** [CPU#1] ... the script continues executing...
 
*** [CPU#1] ... the script finishes executing
 
** [CPU#1] the freeplane-gui will unfreeze (and visual changes will only now be updated in the gui)
 
  
**** [CPU#2]... user debugs in debug-window....
+
=== Joining the pieces together ===
**** [CPU#2]... in the end, user writes "!!@" which terminates the debug-session
 
  
With imaginary [CPU#1]/[CPU#2] we can execute 2 threads in parallel at the same time.
 
So we take advantage of it by placing the execution of '''RubyLiveDebugger.open_debug_here(binding)''' in another-thread [CPU#2] so that [CPU#2] is now who has to execute-and-wait for the debug-session to terminate.
 
Meanwhile [CPU#1] continues to execute the script in paralell untill the script finishes executing, and then gets back to unfreeze the freeplane-gui much faster (uncluttered from the debug-session!)
 
  
So, with 2 threads, we can make freeplane-gui unfreeze much faster. While the debug-session executes ''in paralell'' on another-thread
+
We have come a long way !
This means you can have a debug-session open in a debug-window, and freeplane-gui unfreezed and executing normally as usual. At the same time! In paralell :) Using 2 threads!
 
  
There is a catch though: inside the debug session you '''can read''' freeplane-java-objects directly (ex: <code>puts(node.text)</code> ), but you '''cannot change''' freeplane-java-objects '''directly''' (ex: <code>node.text="nooooo! "</code>)
+
Dont stress if you didnt get all of it - the strange thing would be for someone to understand it all so fast! It takes its time. Just get an idea and re-read it when you need to.
  
However there is a solution for it: from the paralell debug-session you '''can change''' freeplane-java-objects '''''indirectly''''', with the use of '''invokeAndWait { ... }''' (ex: <code>invokeAndWait { node.text = "yes!!! use *invokeAndWait* when debugging in *paralell*!" }</code> ). If you dont use invokeAndWait{...} to make the changes, then sooner or later bizarre asynchronous error will creep up on you (with very non-indicative error messages), and freeplane behaviour will be inconsistent (ie, you will endup in a pit of suffering struggling with uninteligible asynchronous multithread errors disguised as other errors - yes, believe me, not even pleasant to show you, just take my word...)
+
The journey was:  
 +
* we [[#Learn_the_basics_of_Ruby|learned the basics of ruby]]
 +
* we [[#Learn_what_freeplane-java-objects_are_available_for_you_-_the_Freeplane_Scripting_API|saw what/where is the Freeplane Scripting Api]], and how to easily access it's most usefull java-classes via the special variables: <code>node, c, ui, logger, htmlUtils, textUtils, menuUtils, config</code>
 +
* then we saw [[#Call_Java-methods:_getXXX.28.29.2C_setXXX.28yyy.29.2C_isXXX.28.29.2C_...|how to call java-methods from Jruby]] (ex: getXXX(), setXXX(yyy), isXXX(), ...)
 +
* and then we saw how [[#Call_Java-methods:_conversions_between_ruby-type_.3C--.3E_java-type_.28automatic_and_manual.29|conversions between ruby/java types]] happen when you call a java-method
  
  
____
 
  
 +
== Create your own Freeplane-Ruby-Script ==
  
  
 +
Creating your own freeplane-ruby-scripts is (on purpose!) very easy:
  
I'll leave a resume of what is important to know about RubyLiveDebugger:
 
  
 
{{FreeplaneJrubyInstallerNote|
 
{{FreeplaneJrubyInstallerNote|
'''Requisites to use RubyLiveDebugger (in''' '''''EDT-Thread (Gui freezed)''''' '''or in''' '''''Parallel-Thread (Gui responsive)''''' ''')'''
+
A freeplane-ruby-script should:
* the freeplane-ruby-script must have a '''require "freeplane_jruby_common_environment.rb"'''
+
* be placed inside the directory '''freeplane_user_dir/scripts''' (ex: freeplane_user_dir/scripts/ruby_sample_script.rb )
* certain Preferences have to be enabled
+
* start the file with:
[[File:Preferences_RubyLiveDebugger.png|frame|400x200px|none|link={{filepath:Preferences_RubyLiveDebugger.png}}]]
+
  require "freeplane_jruby_common_environment.rb"
 +
  # The 'freeplane_jruby_environment.rb' file enables the following:
 +
  # # [1] From now on, you can require the gems installed in "gem_home" (and only those)
 +
  # # [2] Enables these omnipresent-global-methods: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config, invokeAndWait
 +
  # # [3] Enables RubyLiveDebugger.open_debug_here(binding)
 +
 
 +
* A new ruby script is detected after Freeplane restarts, and since then can be executed by clicking in menu '''Tools/Scripts/Ruby_Sample_Script'''.
 +
* Once the ruby script appears in freeplane, you can (re)edit the ruby-script and execute it without restarting freeplane (ie, no need to restart freeplane everytime you change an existing ruby script!).
 +
 
 +
}}
 +
 
 +
 
 +
 
  
 +
TODO: video example of how to use debugger to test code -> copy/paste into freeplane-script and leave or remove invokeAndWait/invokeLater 
  
* ''RubyLiveDebugger.open_debug_here(binding)'''
 
** all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''RubyLiveDebugger.open_debug_here(binding)''' and when the script is executed it will open a debug window in that point of the script. The debug-session will execute in the EDT-thread, with freeplane-freezed untill the debug-session and script finish-executing
 
** exit from a debug session with command '''''!!@'''''  - and never with 'exit' nor 'quit' nor closing the debug window directly
 
  
  
* '''RubyLiveDebugger: Parallel-Thread (Gui responsive)'''
 
** all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''Thread.new { RubyLiveDebugger.open_debug_here(binding) }''' and when the script is executed it will open a debug window in that point of the script. The *debug-session* will execute in a *paralell thread*, and the script will resume executing inmediately *at the same time* in the EDT-Thread untill it finishes and then make freeplane unfreeze instantaneously. The debug-session (debug-window) will continue executing in paralell to the unfreezed freeplane application.
 
** in the debug-session of the paralell-thread, be carefull:
 
*** freeplane-java-objects can be '''read directly''' <br /> CORRECT: <code> puts(node.text) </code>
 
*** freeplane-java-objects can '''not''' be '''changed directly''' <br /> INCORRECT: <code> node.text&#61;"NO, wrong. Cannot change objects in *paralell-debug* without *invokeAndWait*. Dont do this!" </code>
 
*** freeplane-java-objects '''can''' be '''changed indirectly''' by using '''invokeAndWait {}''' <br />  CORRECT: <code> invokeAndWait { node.text&#61;"Yes!!! *paralell-debug* can change objects (only with) *invokeAndWait*"} </code>
 
** exit from a debug session with command '''''!!@''''' and never with 'exit' nor 'quit' or closing the debug window directly
 
  
}}
+
Inside a freeplane-ruby-script you dont have to use invokeLater/invokeAndWait, but if you do there is no problem either. So when you copy/paste code from the RubyLiveDebugger into a ruby-script, you can either leave or remove the <code>invokeLater{} and invokeAndWait{}</code> wraps (because scripts are run inside the EDT-thread by default). What is this strange talk about invokeLater/invokeAndWait and RubyLiveDebugger? Read on to the next section
  
  
  
  
 +
== Playing with the RubyLiveDebugger ==
  
As part of the addon, there are 2 menu entries in ''' Tools -> Jruby... -> RubyLiveDebugger...''' to quick-launch a debug-session for you to play with freeplane at any time:
+
While learning Ruby did you saw something called ''irb'' that is like a debug-window where you write ruby code and see the result when you press ENTER? The ''irb'' is just great to quickly test little pieces of ruby code and see their result. Its so usefull that someone made an irb-on-steroids that can be called '''''inside''''' a running ruby program. And even better, the FreeplaneJrubyInstaller Addon comes with an improved irb-on-steroids-for-freeplane-ruby-scripts that is called '''RubyLiveDebugger''' - just for your supreme satisfaction :)
  
''' Tools -> Jruby... -> RubyLiveDebugger... -> Debug in EDT Thread (GUI freezed) '''
+
So what is this '''RubyLiveDebugger''' thing? Its simple: you can "pause" and "inspect" any freeplane-ruby-script, by just putting a line with <br /> <code>RubyLiveDebugger.open_debug_here(binding)</code> <br /> anywhere in the script; when the script is running and gets to that line, it will "pause" the execution and give you a window where you can see the values of the variables in that point, try ruby commands and see their results, and then finally write "!!@" to close the window and "unpause" the script that will continue executing normally.
* launches a ruby script that simply calls <code> RubyLiveDebugger.open_debug_here(binding) </code>
 
  
''' Tools -> Jruby... -> RubyLiveDebugger... -> Debug in paralell Thread (GUI responsive) '''
+
So, its like "pause"-and-"peek" into your script, ''in the middle of execution!!!''. Very usefull for debugging your freeplane-ruby-scripts :)
* launches a ruby script that simply calls <code> Thread.new { RubyLiveDebugger.open_debug_here(binding) } </code>
 
  
 +
To use the '''RubyLiveDebugger''' you need to make some preparations/requirements, and to close the window you must use '''!!@''' (and not ''exit'' nor ''quit'' nor closing the window directly!). While in "pause", the Freeplane window is "freezed"
  
  
In practice the '''Debug in EDT Thread (GUI freezed)'''  <code>RubyLiveDebugger.open_debug_here(binding) </code> is good for ''creating a ruby script'', as it ''suspends'' and debugs the script in a specific line
+
 
The '''RubyLiveDebugger: Parallel-Thread (Gui responsive)''' <code>Thread.new { RubyLiveDebugger.open_debug_here(binding) } </code> is not so good for debugging ruby scripts, but is ''good to interact with the freeplane-gui'' and test different situations quickly (like selecting different nodes with the mouse in freeplane-gui, and execute ruby commands in the parallel-debug-session - see the demo videos in the end of this section)
+
{{FreeplaneJrubyInstallerNote|
 +
'''Requisites to use RubyLiveDebugger'''
 +
* certain Preferences have to be enabled in Freeplane
 +
[[File:Preferences_RubyLiveDebugger.png|frame|400x200px|none|link={{filepath:Preferences_RubyLiveDebugger.png}}]]
 +
* the freeplane-ruby-script must have a '''require "freeplane_jruby_common_environment.rb"'''  
  
  
 +
* '''RubyLiveDebugger.open_debug_here(binding)'''
 +
** all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''<code>RubyLiveDebugger.open_debug_here(binding)</code>''' and when the script is executed it will open a debug window in that point of the script. The debug-session will execute <small>(in the EDT-thread)</small> with freeplane-freezed untill the debug-session and the script finish-executing
 +
** exit from a debug session with command '''''!!@'''''  - and never with 'exit' nor 'quit' nor closing the debug window directly
  
 +
}}
 +
 
  
===== And now lets play a bit with the RubyLiveDebugger =====
 
  
Demo: RubyLiveDebugger: EDT-Thread (Gui freezed)
+
 
* https://youtu.be/eGBi-EqNjyE
+
 
 +
 
 +
 
 +
 
  
 +
 
 +
 
 +
 
 +
 
  
Demo: RubyLiveDebugger: Parallel-Thread (Gui responsive)
+
TODO:
* https://youtu.be/dy6kDGApaKw
+
  * explain the debugger and how to use invokeLater/invokeAndWait
* Introduces the '''Scripting API mindmap (Help -> Api Generator)''', where all the freeplane Scripting API objects and methods are resumed (as well if they are (r), (w) or (rw) ). Use '''invokeAndWait {}''' to call (w) and (rw) methods.
+
 
 +
 
 +
 
 +
{{FreeplaneJrubyInstallerNote|
 +
'''Requisites to use RubyLiveDebugger (for both ''' '''''EDT-Thread (Gui freezed)''''' ''' and ''' '''''Parallel-Thread (Gui responsive)''''' ''')'''
 +
* certain Preferences have to be enabled
 +
[[File:Preferences_RubyLiveDebugger.png|frame|400x200px|none|link={{filepath:Preferences_RubyLiveDebugger.png}}]]
 +
* the freeplane-ruby-script must have a '''require "freeplane_jruby_common_environment.rb"'''
  
  
 +
* '''RubyLiveDebugger.open_debug_here(binding)'''
 +
** all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''RubyLiveDebugger.open_debug_here(binding)''' and when the script is executed it will open a debug window in that point of the script. The debug-session will execute in the EDT-thread, with freeplane-freezed untill the debug-session and script finish-executing
 +
** exit from a debug session with command '''''!!@'''''  - and never with 'exit' nor 'quit' nor closing the debug window directly
  
RubyLiveDebugger - Long demo of simple operations on nodes, debug-session with freeplane responsive (1h16m long)
 
* https://youtu.be/J2_DMFagXsw
 
* Made for newbie-programmers, showing step-by-step examples of how to do simple operations with a node, attributes, text/plainText, on 1st-level children nodes, on all-descendents-nodes
 
* Tried to show a "programming cycle": you have an idea,  search the freeplane-API-webpage (Proxy.Node) for available methods, try to code your idea using freeplane-API methods, test your code, fix errors, etc...
 
* The text notes used in the video are left below:
 
  
    # I'll try to show how to make simple things with the freeplane API
+
* '''RubyLiveDebugger: Parallel-Thread (Gui responsive)'''
    # I will do this for the first time, consulting the API-web and certainly making mistakes. The idea is to show to newbie programmers how its done. To demo the "programming-cycle" :)
+
** all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''Thread.new { RubyLiveDebugger.open_debug_here(binding) }''' and when the script is executed it will open a debug window in that point of the script. The *debug-session* will execute in a *paralell thread*, and the script will resume executing inmediately *at the same time* in the EDT-Thread untill it finishes and then make freeplane unfreeze instantaneously. The debug-session (debug-window) will continue executing in paralell to the unfreezed freeplane application.
    # Always keep the freeplane API web open :)
+
** in the debug-session of the paralell-thread, be carefull:
    # Proxy.Node is the most used freeplane object :)
+
*** freeplane-java-objects can be '''read directly''' <br /> CORRECT: <code> puts(node.text) </code>
    #
+
*** freeplane-java-objects can '''not''' be '''changed directly''' <br /> INCORRECT: <code> node.text&#61;"NO, wrong. Cannot change objects in *paralell-debug* without *invokeAndWait*. Dont do this!" </code><br /> CORRECT: <code> invokeAndWait { node.text&#61;"Yes!!! *paralell-debug* can change objects (only with) *invokeAndWait*"} </code><br />  freeplane-java-objects '''can''' be '''changed indirectly''' by using '''invokeAndWait {}''' or '''invokeLater {}'''
    # http://freeplane.sourceforge.net/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html
+
** exit from a debug session with command '''''!!@''''' and never with 'exit' nor 'quit' or closing the debug window directly
    # http://ruby-doc.org/core-2.3.1/Array.html
 
    #
 
   
 
   
 
    # node attributes
 
    # list all attributes names
 
    node.getAttributes.getNames.to_a
 
   
 
    # write attribute
 
    invokeAndWait { node.putAt("attribute1", "val_attr_1") }
 
   
 
    # read 1 attribute value (from the atribute name)
 
    attribute_name = "blue attribute"
 
    attribute_value = node.getAttributes.getAll(attribute_name)[0]
 
   
 
    # list all attributes by name
 
    rubyArray_with_names_of_attributes = node.getAttributes.getNames.to_a
 
   
 
    # node text
 
    # plain text (html format is removed)
 
    #read
 
    node.getPlainText
 
    #write
 
    invokeAndWait { node.setText("just plain text without any html code")}
 
   
 
    # text
 
    #read
 
    node.getText
 
   
 
    #write
 
    invokeAndWait {node.setText("new text")}
 
   
 
    # node's children (1st level only)
 
    # read text from them
 
    rubyArray_children_nodes = node.getChildren.to_a
 
    rubyArray_children_nodes.each {|child_node| puts child_node.text}
 
   
 
    # having the children in an array, we can operate in all of them more easily
 
    invokeAndWait {  rubyArray_children_nodes.each {|cn| cn.text = cn.text.downcase} }
 
   
 
    # node + all children (all levels + selected-node included)
 
    rubyArray_all_children_nodes = node.findAllDepthFirst.to_a
 
    rubyArray_all_children_nodes.each {|child_node| puts child_node.text}
 
   
 
    # only all children (without selected-node)
 
    rubyArray_all_child_nodes_without_current_node = node.findAllDepthFirst.to_a.delete_if {|a_node| a_node.nodeID == node.nodeID}
 
   
 
    #ex: lets add an new attribute to all children (not including selected-nodes)
 
    # the attribute will be the nodeID, for example
 
    # pseudo-code (or having an idea of what will will have to do in small steps)
 
    array_child_nodes_without_current_node = node.findAllDepthFirst.to_a.delete_if {|a_node| a_node.nodeID == node.nodeID}
 
    invokeAndWait do
 
    array_child_nodes_without_current_node.each do |n|
 
    #...add atribute to n, with name "theNodeId" and with value "nodeID"...
 
    n.putAt("theNodeId", n.getId)
 
    end
 
    end
 
 
  
 +
}}
  
Other notes about using the freeplane API with ruby:
 
  
 +
<small>
 +
When/if you need, there is more (much much more) info about the RubyLiveDebugger [[FreeplaneJrubyInstaller_-_making_of#RubyLiveDebugger|in the FreeplaneJrubyInstaller - making of]]
 +
</small>
  
'''Get array with all the (multiple) selected nodes'''
 
  
[[File:FreeplaneJrubyInstaller_example1.png|frame|400x200px|none|link={{filepath:FreeplaneJrubyInstaller_example1.png}}]]
 
  
    # I've manually selected multiple nodes by holding ctrl while clicking each node with the mouse. the first node selected was bbb1, then bb2, then bb3.
 
    # Although we have multiple nodes selected, the "node" variable points only to one node, the first-node selected
 
    node.text
 
    => "bb1"
 
   
 
    # To get an array with all the selected nodes (all 3 of them), use c.selecteds.to_a
 
    array_of_multiple_selected_nodes = c.selecteds.to_a
 
 
    # With the multiple selected nodes in an array, we can operate on them - lets see their nodeID and text
 
    array_of_multiple_selected_nodes.each {|n| puts "#{n.nodeID}    #{n.text}" }
 
    =>  ID_1890893980    bb1
 
        ID_1026956272    bb2
 
        ID_261556528    bb3
 
  
   
 
  
  
  
==== Dir structure after addon installed ====
 
  
 +
 
  
Well, having all those previous parts tested and working (jruby, script-variables c,node,ui,... , self-contained ruby-gems, RubyLiveDebugger ) I then focused on packaging the addon, so that after installing the addon it would be easy to use by ruby scripts and curious programmers.
 
  
When the addon is installed, it takes this directory structure in the freeplane-user-dir (other files and directories not relevant to the addon where omited from the diagram)
+
== Jruby coding samples and tips and whatever else ==
  
[[File:FreeplaneJrubyInstaller_dir_struct.png|frame|400x200px|none|link={{filepath:FreeplaneJrubyInstaller_dir_struct.png}}]]<br />
 
  
There are a couple of things represented in this diagram, and we'll go one by one, so just follow me
+
* After editing an existing script, you dont need to restart freeplane. That is, you can just edit the ruby script, save it, and then in freeplane rerun the script, without restarting freeplane!
* The freeplane-ruby-scripts should be placed in the same typical location as groovy scripts - '''freeplane_user_dir/scripts/''' - and they should start with <code>require "freeplane_jruby_common_environment.rb"</code> - see the example script ruby_sample_script.rb . That require will call a chain of requires: [a] -> [b] -> [c] -> [d1]+[d2]+[d3] that together will enable to use the "gem_home", the omnipresent-global-methods '''c, node, ui, ...''', and '''RubyLiveDebugger.open_debug_here(binding)'''. So a freeplane-ruby-script only needs to start with <code>require "freeplane_jruby_common_environment.rb"</code> to get all the benefits from that chain of requirements.
 
  
* All freeplane-ruby-scripts share one common rubygems local folder: "gem_home" [e0]. If you want/need to install more gems, have a look and edit [e1] or [e2] as needed. However, take care to dont uninstall existing gems because they may be missed by other freeplane-ruby-scripts that need them. If you screw up, just uninstall/reinstall the FreeplaneJrubyInstaller and "gem_home" will be back to normal
 
  
* When packaging an addon, you use *addon-scripts* made with '''groovy'''. However, I've tested and checked that ''*if you already have Jruby installed in Freeplane*'', then you can *also* make addon-scripts in '''ruby'''. So in the future you could install this FreeplaneJrubyInstaller-addon (to install jruby in freeplane) and ''after'' install ''another'' addon which uses addon-scripts in '''ruby'''. This can be usefull if you prefer to program addon-scripts in ruby instead of groovy (like I would :) ).
+
    # In Jruby to access a Java-class-field, use :: like this SomeJavaClass::SomeJavaField
 +
    # Access FreeplaneVersion::XML_VERSION (see http://www.freeplane.org/doc/api/org/freeplane/core/util/FreeplaneVersion.html)
 +
    the_java_class = Java::org.freeplane.core.util.FreeplaneVersion
 +
    the_value_of_field = the_java_class::XML_VERSION
  
 +
    # To call a java-class-constructor, just:
 +
    a_java_instance = JavaClass.new()
 +
    a_java_instance = JavaClass.new(x,y,z)
 +
   
  
 +
    # toggle folding of node branch
 +
    node.folded = ! node.folded?    # same as:    node.setFolded( ! node.isFolded() )
 +
   
  
Now let me tell you about an acrobatic thing I did in the FreeplaneJrubyInstaller to make it first execute an addon-script in groovy (before jruby was installed) and later (after jruby was installed) make it execute an addon-script in ruby: I made the addon-scripts in groovy [f1],[f2] which will allways be executed first, and that contain code to check if jruby is installed, and, if ''jruby is not installed'' it executes another ''groovy'' script; if ''jruby is installed'' it executes another ''ruby'' script. The code for this [f1],[f2] was difficult, made in a hurry, in rude state, but works. The interesting parts (that might be usefull to be reused in other future addons) is the code that detects the presence of jruby, and the code that calls the execution of another groovy/javacript/ruby scripts.
+
    # Show message in new window
 +
    ui.informationMessage("Hello World!")
 +
   
 +
    # Show message in status bar (bottom of freeplane)  
 +
    c.statusInfo = "Hello World again!"
  
 +
    # Description:
 +
    #    Lambda that receives a target_node
 +
    #    and returns an array of all ancestor nodes (including the root-node)
 +
    # Arguments:
 +
    #    target_node  - a Proxy.Node
 +
    # Returns:
 +
    #    Array of nodes, where array[0] is root-node, and array[-1] is the target_node
 +
    #    [root-node, ..., parent-of-parent-of-target_node, parent-of-target_node, target_node]
 +
    # Usage:
 +
    #    the_ancestry_of_node  = get_ancestry_array.call(node)
 +
    #    the_ancestry_of_node.map {|n| n.text }
 +
    #
 +
    #    the_ancestry_of_nodeX = get_ancestry_array.call(nodeX)
 +
    #    the_ancestry_of_nodeX.map {|n| n.text }
 +
    #
 +
    get_ancestry_array = lambda do |target_node|
 +
      ancestry = [target_node]
 +
      ancestry.unshift(ancestry[0].parentNode) while ( not ancestry[0].root? )
 +
      ancestry
 +
    end
 +
   
 +
    the_ancestry_of_node  = get_ancestry_array.call(node)
 +
    the_ancestry_of_node.map {|n| n.text }
 +
   
 +
    # Example:
 +
    [[Image:FreeplaneJrubyInstaller__161001_230705.png]]
  
  
 +
    # Show the Java-class of an object
 +
    node.java_class
 +
    #=> class org.freeplane.plugin.script.proxy.NodeProxy
  
 +
  
==== Finally the addon is made ====
 
  
Hurray, so lets stop talking about it and start using it !!!!!
+
* invokeAndWait{} or invokeLater{}:
 +
** does not returns a value from the block (always returns nil)
 +
** Exceptions occuring inside the block (which will execute in a paralell thread) will only kill their thread but not propagate to launching thread. Nonetheless the exceptions are always written to freeplane log file.
 +
    pry(main)> invokeAndWait { 1 }
 +
    => nil
 +
    pry(main)> invokeAndWait { 5 }
 +
    => nil
 +
    pry(main)> invokeAndWait { true }
 +
    => nil
 +
    pry(main)> invokeAndWait { "invokeAndWait always returns nil, no matter what happens inside the block" }
 +
    => nil
 +
    pry(main)> invokeAndWait { raise "Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file" }
 +
    => nil
 +
    pry(main)> invokeAndWait { node.thisMethodDoesNotExist }
 +
    => nil
 +
   
 +
    STDERR: Exception in thread "AWT-EventQueue-0"
 +
    STDERR: org.jruby.exceptions.RaiseException: (RuntimeError) Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file
 +
    STDERR:  at RUBY.block in evaluate_ruby((pry):18)
 +
   
 +
    # exception is registered in freeplane logs: <freeplane_user_dir>/logs/log.0
 +
    STDERR: Exception in thread "AWT-EventQueue-0"
 +
    STDERR: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `thisMethodDoesNotExist' for #<Java::OrgFreeplanePluginScriptProxy::NodeProxy:0xdbe721>
 +
    STDERR:  at RUBY.block in evaluate_ruby((pry):6)
  
If you do not have the addon already installed, then install it now - see [[FreeplaneJrubyInstaller_Addon#How_to_install_the_FreeplaneJrubyInstaller-Addon_in_Freeplane | How to install the FreeplaneJrubyInstaller-Addon in Freeplane ]]
+
   
  
Ok, if you got here, congratulations. I'm looking back at all I've written and start to doubt if anyone will ever read this far... Please write in the freeplane forum if you see this was actually usefull, cause I have serious doubts...
+
* Clipboard helpers
  
 +
    module RubyClipboard
 +
      # Returns: string or nil
 +
      def self.readClipboardContentsAsString
 +
        java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().getContents(nil).getTransferData(java.awt.datatransfer.DataFlavor.stringFlavor) || ""
 +
      end
 +
   
 +
      # Arguments:
 +
      #    html_for_clipboard - a string
 +
      def self.writeClipboardContentAsHtml(html_for_clipboard="")
 +
        org.freeplane.core.util.TextUtils.copyHtmlToClipboard(html_for_clipboard) 
 +
      end
 +
    end
  
  
 +
   
  
  
 +
== FreeplaneJrubyInstaller Addon - The making of ==
  
  
{{FreeplaneJrubyInstallerNote|
+
If you want to go even more deep into the rabbit hole, into how this addon was made, understanding the details, mechanism and design-decisions about the addon plugin and how this all came together into freeplane, then read the [[FreeplaneJrubyInstaller_-_making_of|FreeplaneJrubyInstaller - making of]].
A freeplane-ruby-script should:
 
* be placed inside the directory '''freeplane_user_dir/1.5.x/scripts''' (ex: freeplane_user_dir/1.5.x/scripts/ruby_sample_script.rb )
 
* start the file with:
 
  require "freeplane_jruby_common_environment.rb"
 
  # The 'freeplane_jruby_environment.rb' file enables the following:
 
  # # [1] From now on, you can require the gems installed in "gem_home" (and only those)
 
  # # [2] Enables these omnipresent-global-methods: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config, invokeAndWait
 
  # # [3] Enables RubyLiveDebugger.open_debug_here(binding)
 
  
And this sums up what you need to know to go ahead with freeplane-ruby-scripts.
+
Its a completely optional and big reading - if your starting with scripting, leave it for a latter day :)
}}
 

Latest revision as of 12:03, 9 January 2019

This wiki page is intended for new programmers that want to try Ruby, and make Freeplane ruby scripts.

If you just want to run a ruby script, then instead read FreeplaneJrubyInstaller Addon
If you want to know how the addon was made, then instead read FreeplaneJrubyInstaller - making of


I estimate that only a small number of people will actually feel curious to learn ruby and make freeplane scripts with ruby, which makes us - the freeplane-ruby-scripts programmers - a really small and nice club :)

So, if you anytime feel part-of-the-club (or just want to say hello), dont be shy and drop a comment in this forum thread: Announcing FreeplaneJrubyInstaller !!!!

Hope we all have a great time :)


So here we go: learn ruby, program freeplane, have fun :)


Ruby scripts examples

Start programming by tinkering with existing code, and seeing what happens. When you get stuck or need to understand something better, then go read some introduction book/tutorial (just enough) and get back to tinkering again. Its the fun of tinkering that fuels to get better, understand more, go more complex.


Sort children by icon

sort_by_icons.rb

see it in github



Freeplane ruby script = Ruby + Freeplane Scripting Api

A ruby script for freeplane, is a ruby program that will use Freeplane java-objects from the Freeplane Scripting API


To learn how to make ruby scripts for freeplane, the ideal learning-path is to:

  1. Learn the basics of Ruby
  2. Know how to call the Freeplane-java-objects in your ruby script. This is in fact 2 things:
    1. Learn what freeplane-java-objects are available for you - the Freeplane Scripting API
    2. Learn how to call freeplane-java-objects in (J)ruby


In the next sections, we'll go through this learning-path, which I trimmed down to the essentials (with links for further exploration, if/when you need)



Learn the basics of Ruby

Ruby is a simple language, easy to learn for new programmers, with lots of books, guides, videos and explanations in internet (almost too much)

In internet there are lots of learning material about "Ruby" and about "Ruby on Rails", but stay away from "Ruby on Rails" because that is another umongous universe (using ruby to make webpages). For freeplane scripting, we just need to learn Ruby (without Rails!), and even then, just the basics of Ruby.


To start with Ruby, I recommend you read and follow these learning links:

  • Try ruby in 15 minutes, in the browser
    • >> I think this should be your first contact with ruby: a guided interactive tour to do simple things in ruby
    • Its all online, no install needed, and seems like 15-30min
    • From here take a general idea of ruby - you'll see these things explained in more detail in other guides/books
  • Online Interactive Ruby (online irb)
    • >> Keep this close to you everytime you are learning ruby, it allows you to try ruby code without installing anything. This is the equivalent of an online irb.
    • Instead of installing ruby in your pc and then use its "irb" to test ruby code, you can use this online irb without installing anything :)
    • Good to try out small code snippets, like examples from a book, or for you to explore ruby


Also, the following links are optional support-material - they will not teach you ruby but if you have them somewhere close they will be helpfull many times.

They contain more than info than you will ever need, but are good consulting references, to clearout doubts, see examples, etc. I still use them myself:

  • Ruby QuickRef Card (print it)
    • >> Print these 2 pages in 1 sheet and keep it around in the desk - it will help you many times to remember ruby syntax. Take notes on it as you need :)
  • Zenspider Ruby QuickRef (bookmark it)
    • >> This is the best quickref of ruby. I always search here first. Its too big to be printed (what a pitty) but whenever you want, you can open it online, and "find" what you want
    • Its a great reference to be consulted when needed! I never got even close to know all of it, so don't sweat. Just a reference, use it as you need it :)
  • Search in google "TutorialsPoint Ruby Basics" - TutorialsPoint Ruby Basics (consult when needed)
    • >> Use it like support-material: when you have a doubt about something specific, search for it in here
    • It packs good info in short resumes. Sometimes a bit canonical, but the examples can be of use for you and clarifying
    • Use it to consult, like a reference with examples and resumes.
    • This wiki filters the webpage link as spam (which is not), so use google to reach it


For freeplane scripts, its enough to learn only the basics of ruby - its up to you to decide how deep you want to learn it. In any case, have an overall idea of Ruby, and comeback to the above links whenever you need.

NOTE: If someday you want to become a ruby pro, you will want to read the "pickaxe" book: The Pragmatic Programmers' Guide, Programming Ruby If you are a new programmer, dont start here.


Know how to call the Freeplane-java-objects in your (J)ruby script

Learn what freeplane-java-objects are available for you - the Freeplane Scripting API

Now, we will skim over the Freeplane Scripting API, which are a group of Java classes and its methods.


All the Freeplane Scripting API are Java classes and methods.

This will not make much sense to you now, after having learned the basics of ruby, because you don't know how to use Java classes/methods in ruby. These java classes will not be used just now, I will just present them so that you have an idea that they exist, and where you can find them latter, when you need them.


The freeplane-java-objects are documented in the Freeplane Scripting API, which is a Java API, where you can see the classes , the methods of each class and a short description of what each method does. The Freeplane Scripting Api should be open by your side while you'r making freeplane scripts - you will consult it all the time. Click the "FRAMES" link at the top, to show in the webpage a left-side-navigation-list with all the Classes available.


To learn freeplane scripting, the most interesting class of the Freeplane Scripting API, is Proxy.Node

  • Represented in groovy and ruby scripts by the variable node
  • Look to these java methods: getText() and setText("whatever")


In Ruby scripts (imitating what happens with Groovy scripts), there are some handy special variables predefined for us to use in scripts, that point to usefull java-classes:



Learn how to call freeplane-java-objects in (J)ruby

In last section I presented the Freeplane Scripting API, which are a group of Java classes/methods. In this section, I will focus on how to use Java classes/methods inside a (j)ruby script.


Read this section once, and take a pause. Then re-read it again. It's normal to don't understand-it-all in the first time (and second), at least that's what happened to me.
So know in advance that this will not be all understood in one pass - so read it relaxedly and let it sink-in afterwards.
Its a bunch of concepts, and you dont need to master all of them to start with scripts. The natural evolution is to get an idea, use examples, and if/when you need then comeback and dive deeper into understanding.


NOTE: Ignore the following if you are starting with scripting

If/when you want to understand more about this topic, then:

  • have a look into the Jruby wiki page dedicated to Calling Java from JRuby
  • get this book Using JRuby - The pragmatic programmers
    • read only "Chapter2 Driving Java from Ruby" and "Appendix B: Ruby/Java Interoperability"
    • you just need to read those 2 sections of the book, its a 2-3h read, not much
    • dont stress out - just read it once to get an idea, and get back to it if/when you latter need it.
    • NOTE: if you are a java programmer getting into jruby, this book may be it



The ruby language cannot use java-objects by default, but the Jruby-complete.jar adds some special-jruby-magic that makes possible for the ruby programs executed by the jruby-complete.jar to use java-objects.

So Jruby = ruby + <some-special-magic-that-enables-us-to-call-java-objects-from-inside-jruby-scripts>.

That jruby <special-magic> is refered as "Driving Java from Ruby", and I will resume here some basics of it, enough for freeplane-ruby-script. For more info, see links at the end of section


In a normal Ruby program (not Jruby, just Ruby as you saw in Learn the basics of Ruby ), there is:

  • ruby code that drives/uses
    • Ruby-objects (Ruby-classes/methods)


In a Jruby program (not Ruby, but Jruby now), there is:

  • ruby code that drives/uses
    • Ruby-objects (Ruby-classes/methods)
    • Java-objects (Java classes/methods)


Jruby can access any Java-class, however we will focus mainly on the Java objects from the Freeplane Scripting API


Access Scripting API Java-classes: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config

Jruby can access any Java-class, however we will focus mainly on the Java-classes from the Freeplane Scripting API, and not all of the API java-classes, but just in those that are most usefull.


And we are in luck, that the Freeplane Scripting Api has some special variables predefined for us, to access the most-usefull Freeplane Scripting API classes: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config (see Learn what freeplane-java-objects are available for you - the Freeplane Scripting API).
So you can simply use node to access the Proxy.Node Java-class, and the other special-variables to access other most-usefull java-classes from the Scripting API.

So, Jruby can access any of the Freeplane Scripting API Java-classes, but from aaalll the classes of the API, only a small number of them is actually usefull, and for those there are special variables predefined that give quick access to those java classes.

I recommend that to start playing with Scripting API, you only use the node (and maybe others special variables predefined).
The node will allow you to read/write into a node text, details, attributes, ... so should give more than enough runway for a smooth start into scripting :)



NOTE: Ignore the following if you are starting with scripting

If/when you need to access any other Java-classes, you can do it like this: More info in https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby

   # Access Java-classes of Scripting API that are  -->> Proxy.Xyzzz <<-- with:
   Java::org.freeplane.plugin.script.proxy.XyzzzProxy    # Java: Proxy.Xyzzz  (notice that XyzzzProxy <---- Proxy.Xyzzz )
   Java::org.freeplane.plugin.script.proxy.MapProxy      # Java: Proxy.Map
   Java::org.freeplane.plugin.script.proxy.NodeProxy     # Java: Proxy.Node
       #
       # NOTE: In technicall rigor, I believe that the Java-Interface 'Proxy.Node' has a Java-Class 'NodeProxy',
       # and that Java-Class is used in Jruby as 'NodeProxy'
       # I believe that is why all the Java-Interfaces (all the Proxy.Xyzzz) are accessed with 'XyzzzProxy'
       #
   
   # Access Java-classes of Scripting API that are not -->> Proxy.Xxxxx <<-- with:
   Java::org.freeplane.core.util.FreeplaneVersion        # Java: FreeplaneVersion
   Java::org.freeplane.core.util.HtmlUtils               # Java: HtmlUtils
   Java::org.freeplane.core.util.LogUtils                # Java: LogUtils



Call Java-methods: getXXX(), setXXX(yyy), isXXX(), ...

Ok, so in last section we saw we can use the special-variables to access the most usefull java-scripting-classes

Now we'll see how to call a java-method from those java-classes.

In Jruby, you can call Java-methods in multiple ways - they all do the same thing, just written differently:

   # Calling java-methods in "java-style"
   the_node_text = node.getText()
   node.setText("hello!")
   bool_value = node.isRoot()
   node.createChild("Im a newborne child node")
   
   # In ruby the parenthesis are optional
   # So we could write it as:
   the_node_text = node.getText
   node.setText "hello!"  
   bool_value = node.isRoot
   node.createChild "Im a newborne child node" 
   
   # Jruby also allows to call aJavaMethod() like this: a_java_method()
   # So we could write it as:
   the_node_text = node.get_text
   node.set_text "hello!"
   bool_value = node.is_Root
   node.create_child("Im a newborne child node")
   
   # Jruby also allows to call the java-methods getXXX(), setXXX(yyy), isXXX() like this:  
   #
   #            a_value = obj.getXXX()         can be written as as        a_value = obj.XXX 
   #
   #            obj.setXXX(yyy)                can be written as as        obj.XXX = yyy
   #
   #            a_bool = obj.isXXX()           can be written as as         a_bool = obj.XXX?
   #
   # (for more info see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#alternative-names-and-beans-convention)
   # So we could write it as:
   the_node_text = node.text
   node.text = "hello!"   
   bool_value = node.root?
   node.createChild("Im a newborne child node")
   
   

It's up to you to decide which to use.
For an easy start, use the node.getText() (the "java-style") that is closer to the Scripting API syntax.
With time, you may get more confortable with node.text and node.root?



Call Java-methods: conversions between ruby-type <--> java-type (automatic and manual)

So now that we know how to call a java-method from Jruby, lets see how we can give arguments and receive return-values from the java-methods in our Jruby programs.

This section will be a long one. Just keep the idea that there are conversions going on with ruby/java and if/when you need to go into it, then this is here to help you.


As we saw before, in a Jruby program (not Ruby, but Jruby now), there is:

  • ruby code that drives/uses
    • Ruby-objects (Ruby-classes/methods)
    • Java-objects (Java classes/methods)


Each Java-class has methods.

Each Java method takes 0-or-more arguments, and returns a return-value



  • In the pure Java-world there are only java-objects: in a java program, you have only java-objects, and java-methods expect to work with java-objects.
  • In the pure Ruby-world there are only ruby-objects: in a ruby program, you only have ruby-objects, and ruby-methods expect to work with ruby-objects.
  • In the Jruby-world, there are both java-objects and ruby-objects coexisting in the jruby program
    • Jruby has a magical-invisible bridge between the Ruby-world and the Java-world
    • In Jruby when you call a java-method with arguments that are ruby-objects, then Jruby will automatically try to convert the arguments-ruby-objects into appropriate arguments-java-objects that can be used internally by the java-method
    • Also, in Jruby after a java-method is called, it internally delivers a return-value that is a java-object, but Jruby automatically tries to convert it into an appropriate ruby-object that is returned into the jruby-program (if it cannot convert, it returns the unconverted java-object)

These conversions are predefined for the most usefull ruby/java types (see bellow) and work so well normally that you dont even think about them.

But in some few cases, when there is no automatic-conversion possible, then you may need to manually convert between java-objects/ruby-objects:

  • a java-method can only work with arguments that are java-object internally: so if in the jruby-program you give it an argument that is a ruby-object, then jruby will internally try to convert that ruby-object into a java-object; but if jruby cannot do the conversion automatically (because it does not have an automatic conversion defined for that specific pair of ruby-object>>java-object types) then you need to make the conversion manually in you jruby-program (because the java-method needs java-objects as arguments, it does not understand ruby-objects) and craft yourself an already-converted-java-object (instead of the unconverteable-ruby-object) to use it as the argument for the java-method.
  • on the other hand, for return-values its much simpler: if Jruby cannot convert the internal-java-object-return-value to a suitable ruby-object, then it will simply deliver the java-object to the jruby-program (jruby-programs understand java-objects along with ruby-objects).



The conversions can be made in 2 directions: ruby-object -> java-object and java-object -> ruby-object

For the most used ruby-types and java-types there are predefined "conversions" that are automatic. For other ruby/java types the conversion are not automatic and should be made manually by the programmer (you)


Automatic jruby<->java conversions

The automatic conversions (made seamlessly by Jruby) between most used ruby-types and java-types, are:

FreeplaneJrubyInstaller 160908 170934.png

For more indepth-info, see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#ruby-to-java and https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#java-to-ruby


Manual jruby<->java conversions

Here are some manual conversions that you as programmer might have to use, for some ruby-types >> java-types, and ruby-types << java-types:

  • java.util.List --> ruby-Array
   # java.util.List --> ruby-Array: use .to_a
   a_java_array_list = java.util.ArrayList.new([1,2,3])  # or Vector, LinkedList, Collection, ...
   a_ruby_array = a_java_array_list.to_a
   #=> [1, 2, 3]
  • java.util.Map --> ruby-Hash
   # java.util.Map --> ruby-Hash: use .to_hash
   a_java_hash_map = java.util.HashMap.new(:a => 123) # or Hashtable
   a_ruby_hash = a_java_hash_map.to_hash
   #=> {:a = 123}
  • ruby-Time --> java.util.Date
   # ruby-Time --> java.util.Date: use .to_java
   a_ruby_time = Time.now
   a_java_date = a_ruby_time.to_java
   #=> #<Java::JavaUtil::Date:0x747541f8>
  • java.util.Date --> ruby-Time
   # java.util.Date --> ruby-Time
   a_java_date = java.util.Date.new
   a_ruby_time = Time.at(a_java_date.getTime/1000)
   #=> 2018-01-26 23:49:32 +0100
Examples of jruby<->java conversions (automatic and manual)

Some examples of conversions:

   # NodeProxy.setNoteText() expects as argument a java.lang.String object
   # but we can give as argument a ruby-String, and Jruby will automatically convert the ruby-String into a java.lang.String
   # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html#setNoteText-java.lang.String-
   node.setNoteText("Hi Im a ruby-string")
   # NodeProxy.getNoteText() returns a java.lang.String object, which Jruby automatically converts
   # into a ruby-String
   # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getNoteText--
   a_ruby_string = node.getNoteText()
   # NodeProxy.getChildren() returns a java.util.List<Proxy.Node>
   # Jruby cannot automatically convert a java.util.List, but you can convert it manually into a ruby-Array
   # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getChildren--
   a_java_list = node.getChildren()
   a_ruby_array = a_java_list.to_a
   



Joining the pieces together

We have come a long way !

Dont stress if you didnt get all of it - the strange thing would be for someone to understand it all so fast! It takes its time. Just get an idea and re-read it when you need to.

The journey was:


Create your own Freeplane-Ruby-Script

Creating your own freeplane-ruby-scripts is (on purpose!) very easy:


FreeplaneJrubyInstaller_note.png
Resuming

A freeplane-ruby-script should:

  • be placed inside the directory freeplane_user_dir/scripts (ex: freeplane_user_dir/scripts/ruby_sample_script.rb )
  • start the file with:
 require "freeplane_jruby_common_environment.rb"
 # The 'freeplane_jruby_environment.rb' file enables the following:
 # # [1] From now on, you can require the gems installed in "gem_home" (and only those)
 # # [2] Enables these omnipresent-global-methods: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config, invokeAndWait
 # # [3] Enables RubyLiveDebugger.open_debug_here(binding)
  • A new ruby script is detected after Freeplane restarts, and since then can be executed by clicking in menu Tools/Scripts/Ruby_Sample_Script.
  • Once the ruby script appears in freeplane, you can (re)edit the ruby-script and execute it without restarting freeplane (ie, no need to restart freeplane everytime you change an existing ruby script!).




TODO: video example of how to use debugger to test code -> copy/paste into freeplane-script and leave or remove invokeAndWait/invokeLater



Inside a freeplane-ruby-script you dont have to use invokeLater/invokeAndWait, but if you do there is no problem either. So when you copy/paste code from the RubyLiveDebugger into a ruby-script, you can either leave or remove the invokeLater{} and invokeAndWait{} wraps (because scripts are run inside the EDT-thread by default). What is this strange talk about invokeLater/invokeAndWait and RubyLiveDebugger? Read on to the next section



Playing with the RubyLiveDebugger

While learning Ruby did you saw something called irb that is like a debug-window where you write ruby code and see the result when you press ENTER? The irb is just great to quickly test little pieces of ruby code and see their result. Its so usefull that someone made an irb-on-steroids that can be called inside a running ruby program. And even better, the FreeplaneJrubyInstaller Addon comes with an improved irb-on-steroids-for-freeplane-ruby-scripts that is called RubyLiveDebugger - just for your supreme satisfaction :)

So what is this RubyLiveDebugger thing? Its simple: you can "pause" and "inspect" any freeplane-ruby-script, by just putting a line with
RubyLiveDebugger.open_debug_here(binding)
anywhere in the script; when the script is running and gets to that line, it will "pause" the execution and give you a window where you can see the values of the variables in that point, try ruby commands and see their results, and then finally write "!!@" to close the window and "unpause" the script that will continue executing normally.

So, its like "pause"-and-"peek" into your script, in the middle of execution!!!. Very usefull for debugging your freeplane-ruby-scripts :)

To use the RubyLiveDebugger you need to make some preparations/requirements, and to close the window you must use !!@ (and not exit nor quit nor closing the window directly!). While in "pause", the Freeplane window is "freezed"


FreeplaneJrubyInstaller_note.png
Resuming

Requisites to use RubyLiveDebugger

  • certain Preferences have to be enabled in Freeplane
Preferences RubyLiveDebugger.png
  • the freeplane-ruby-script must have a require "freeplane_jruby_common_environment.rb"


  • RubyLiveDebugger.open_debug_here(binding)
    • all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line RubyLiveDebugger.open_debug_here(binding) and when the script is executed it will open a debug window in that point of the script. The debug-session will execute (in the EDT-thread) with freeplane-freezed untill the debug-session and the script finish-executing
    • exit from a debug session with command !!@ - and never with 'exit' nor 'quit' nor closing the debug window directly









TODO:

 * explain the debugger and how to use invokeLater/invokeAndWait
 
 
 
FreeplaneJrubyInstaller_note.png
Resuming

Requisites to use RubyLiveDebugger (for both EDT-Thread (Gui freezed) and Parallel-Thread (Gui responsive) )

  • certain Preferences have to be enabled
Preferences RubyLiveDebugger.png
  • the freeplane-ruby-script must have a require "freeplane_jruby_common_environment.rb"


  • RubyLiveDebugger.open_debug_here(binding)
    • all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line RubyLiveDebugger.open_debug_here(binding) and when the script is executed it will open a debug window in that point of the script. The debug-session will execute in the EDT-thread, with freeplane-freezed untill the debug-session and script finish-executing
    • exit from a debug session with command !!@ - and never with 'exit' nor 'quit' nor closing the debug window directly


  • RubyLiveDebugger: Parallel-Thread (Gui responsive)
    • all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line Thread.new { RubyLiveDebugger.open_debug_here(binding) } and when the script is executed it will open a debug window in that point of the script. The *debug-session* will execute in a *paralell thread*, and the script will resume executing inmediately *at the same time* in the EDT-Thread untill it finishes and then make freeplane unfreeze instantaneously. The debug-session (debug-window) will continue executing in paralell to the unfreezed freeplane application.
    • in the debug-session of the paralell-thread, be carefull:
      • freeplane-java-objects can be read directly
        CORRECT: puts(node.text)
      • freeplane-java-objects can not be changed directly
        INCORRECT: node.text="NO, wrong. Cannot change objects in *paralell-debug* without *invokeAndWait*. Dont do this!"
        CORRECT: invokeAndWait { node.text="Yes!!! *paralell-debug* can change objects (only with) *invokeAndWait*"}
        freeplane-java-objects can be changed indirectly by using invokeAndWait {} or invokeLater {}
    • exit from a debug session with command !!@ and never with 'exit' nor 'quit' or closing the debug window directly



When/if you need, there is more (much much more) info about the RubyLiveDebugger in the FreeplaneJrubyInstaller - making of






Jruby coding samples and tips and whatever else

  • After editing an existing script, you dont need to restart freeplane. That is, you can just edit the ruby script, save it, and then in freeplane rerun the script, without restarting freeplane!


   # In Jruby to access a Java-class-field, use :: like this SomeJavaClass::SomeJavaField
   # Access FreeplaneVersion::XML_VERSION (see http://www.freeplane.org/doc/api/org/freeplane/core/util/FreeplaneVersion.html)
   the_java_class = Java::org.freeplane.core.util.FreeplaneVersion
   the_value_of_field = the_java_class::XML_VERSION
   # To call a java-class-constructor, just:
   a_java_instance = JavaClass.new()
   a_java_instance = JavaClass.new(x,y,z)
   
   # toggle folding of node branch
   node.folded = ! node.folded?     # same as:    node.setFolded( ! node.isFolded() )
   
   # Show message in new window
   ui.informationMessage("Hello World!")
   
   # Show message in status bar (bottom of freeplane) 
   c.statusInfo = "Hello World again!"
   # Description:
   #     Lambda that receives a target_node
   #     and returns an array of all ancestor nodes (including the root-node) 
   # Arguments: 
   #     target_node   - a Proxy.Node 
   # Returns:
   #     Array of nodes, where array[0] is root-node, and array[-1] is the target_node
   #    [root-node, ..., parent-of-parent-of-target_node, parent-of-target_node, target_node]
   # Usage:
   #    the_ancestry_of_node  = get_ancestry_array.call(node)
   #    the_ancestry_of_node.map {|n| n.text }
   #
   #    the_ancestry_of_nodeX = get_ancestry_array.call(nodeX)
   #    the_ancestry_of_nodeX.map {|n| n.text }
   #
   get_ancestry_array = lambda do |target_node| 
     ancestry = [target_node]
     ancestry.unshift(ancestry[0].parentNode) while ( not ancestry[0].root? )
     ancestry
   end
   
   the_ancestry_of_node  = get_ancestry_array.call(node)
   the_ancestry_of_node.map {|n| n.text }
   
   # Example:
   FreeplaneJrubyInstaller 161001 230705.png


   # Show the Java-class of an object
   node.java_class
   #=> class org.freeplane.plugin.script.proxy.NodeProxy



  • invokeAndWait{} or invokeLater{}:
    • does not returns a value from the block (always returns nil)
    • Exceptions occuring inside the block (which will execute in a paralell thread) will only kill their thread but not propagate to launching thread. Nonetheless the exceptions are always written to freeplane log file.
   pry(main)> invokeAndWait { 1 }
   => nil
   pry(main)> invokeAndWait { 5 }
   => nil
   pry(main)> invokeAndWait { true }
   => nil
   pry(main)> invokeAndWait { "invokeAndWait always returns nil, no matter what happens inside the block" }
   => nil
   pry(main)> invokeAndWait { raise "Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file" }
   => nil
   pry(main)> invokeAndWait { node.thisMethodDoesNotExist }
   => nil
   
   STDERR: Exception in thread "AWT-EventQueue-0" 
   STDERR: org.jruby.exceptions.RaiseException: (RuntimeError) Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file
   STDERR:   at RUBY.block in evaluate_ruby((pry):18)
    
   # exception is registered in freeplane logs: <freeplane_user_dir>/logs/log.0
   STDERR: Exception in thread "AWT-EventQueue-0" 
   STDERR: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `thisMethodDoesNotExist' for #<Java::OrgFreeplanePluginScriptProxy::NodeProxy:0xdbe721>
   STDERR:   at RUBY.block in evaluate_ruby((pry):6)


  • Clipboard helpers
   module RubyClipboard
     # Returns: string or nil
     def self.readClipboardContentsAsString
       java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().getContents(nil).getTransferData(java.awt.datatransfer.DataFlavor.stringFlavor) || ""
     end
   
     # Arguments: 
     #    html_for_clipboard - a string
     def self.writeClipboardContentAsHtml(html_for_clipboard="")
       org.freeplane.core.util.TextUtils.copyHtmlToClipboard(html_for_clipboard)  
     end
   end



FreeplaneJrubyInstaller Addon - The making of

If you want to go even more deep into the rabbit hole, into how this addon was made, understanding the details, mechanism and design-decisions about the addon plugin and how this all came together into freeplane, then read the FreeplaneJrubyInstaller - making of.

Its a completely optional and big reading - if your starting with scripting, leave it for a latter day :)