B4J Library CodeMirror wrapper and example

After the proof of concept test, here is a wrapper for the CodeMirror javascript libarary which runs quite happily in a Webview.

Current functionality:
  • Highlighting Mode for B4x (and 100+ other languages)
  • Minimal wrapper implementation in library, see the attached CodeEditor for an example of a fuller implementation
  • implemented the available visual themes

In addition, the CodeEditor (also a b4xlib) is an example implementation. The only thing not implemented is file loading and saving as the would really depend on the structure of your project.

Dependencies:

CodeMirrorWrapper:

CodeEditor
  • CodeMirrorWrapper

CMMinimal Implementaion - Project
  • CodeMirrorWrapper

Downloads: (Dropbox)

Both the CodeMirror b4xlib and the CodeMirror project are too large to attach to the forum as they contain the CodeMirror javascript library.
You can download them from my dropbox here:

Version 1.5

  • Separated from the CodeEditor module for easier minimal implementation.
  • Added ReadOnly option
  • Added Themes
    • Requires an allowedthemes List to be passed so they can be validataed against the defined themes.
    • You can pass a subset of the defined themes so only your preferred themes are available.

Update 1.51

  • Small process changes to enable minimal implementation
    • Please download library / Project from the dropbox link
    • added minimal implementation example.

1608562768624.png

Update 1.52
  • Fixed readOnly bug
  • Added LineWrapping
  • Added Autofocus
  • Removed dependency on JScriptEngine
  • Added highlighting for jBasicScript (as B4s)
Please download the b4xlib and or project from my dropbox, links above. If you have a previous version the CodeMirror js library should update itself on first run. If you have made your own changes to the CodeMirror js library, save them first and re-apply.


The CodeEditor.b4xlib and Project and documentation XML files for both libraries are attached.

I hope you find them useful. Let me know how you get on with them.
 

Attachments

  • CodeEditor.b4xlib
    4.7 KB · Views: 294
  • Codeeditor.zip
    7.1 KB · Views: 310
  • CodeEditor.xml
    4.5 KB · Views: 275
  • CMMinimal.zip
    2.1 KB · Views: 285
  • CodeMirrorWrapper.xml
    27.9 KB · Views: 175
Last edited:

stevel05

Expert
Licensed User
Longtime User
Update 1.51

  • Small process changes to enable minimal implementation
    • Please download updated library / Project from the dropbox link
    • added minimal implementation example.
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
Update 1.52
  • Fixed readOnly bug
  • Added LineWrapping
  • Added Autofocus
  • Removed dependency on JScriptEngine
  • Added highlighting for jBasicScript (as B4s)
Please download the b4xlib and or project from my dropbox, links in 1st post. CodeMirror js library should update itself on first run.
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
V1.52 I forgot to remove the dependency from the CodemirrorWrapper b4xlib manifest for jScriptEngine. Please download it again or just edit the manifest file. If you have jScriptEngine in your additional libraries, you won't see an error.
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Hi @stevel05 ,

I've tried your library, but both examples without success.

I explain here what I doing:

because I work on an old 32 bit system, and the latest B4J version that works on 32 bit machines is 7.32 (I cannot update my system now), I managed your examples in different ways.
7.32 support CustomViews, but it do not recognize b4xlibs, so I downloaded your project, dependences and all needed material, then I extracted b4xlibs in project folders of two examples you posted and imported in the IDE all classes.
It compile, but the form do not show the code editor, I''ve tried it in debug mode and seem that all initialize in the right way, but it do not show the code editor in a JFX WebView.
I've tried to write on it or put some code at runtime, but write on it is impossible, the control do not respond to keyboard events, is static, nothing change and cannot be edited.

Note that in the CodeEditor example it show all languages and themes without problems and there are no errors on both examples.

When I press on some buttons that sends to a CodeMirror some Javascript the app crashes, here the log:
State: READY
State: SCHEDULED
State: RUNNING
State: CANCELLED
webengine._executescript (java line: 155)
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:132)
at b4j.example.webengine._executescript(webengine.java:155)
at b4j.example.codemirrorwrapper._executescript(codemirrorwrapper.java:312)
at b4j.example.codemirrorwrapper._getselectedcode(codemirrorwrapper.java:333)
at b4j.example.codeeditor._btncopy_click(codeeditor.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:91)
at anywheresoftware.b4a.BA$1.run(BA.java:216)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
Caused by: netscape.javascript.JSException: ReferenceError: Can't find variable: editor
at com.sun.webkit.dom.JSObject.fwkMakeException(JSObject.java:137)
at com.sun.webkit.WebPage.twkExecuteScript(Native Method)
at com.sun.webkit.WebPage.executeScript(WebPage.java:1473)
at javafx.scene.web.WebEngine.executeScript(WebEngine.java:982)
... 22 more
Can't find variable: editor <<<<<<

The SUCCEDED event in the CodeMirrorWrapper LoadProgress_Event function never fires on both examples, it only show SCHEDULED, RUNNING, CANCELLED so this part of code is never called:
B4X:
Log("State: " & NewState)
 
    If NewState  = "SUCCEEDED" Then  ' <<<<<< NEVER RETURN SUCCEEDED, ONLY SCHEDULED, RUNNING, CANCELLED
        Log("Page LOADED")
        ' Setup the javascript interface
        Set_Bridge
        Sleep(0)
        Loaded = True
        Dim Su As StringUtils
        ExecuteScript($"setCode("${Su.EncodeBase64(Code.GetBytes("UTF8"))}");"$)
        If mByFileType <> "" Then
            ExecuteScript($"changeType("${mByFileType}")"$)
        Else If MimeType <> "" Then
            ExecuteScript($"changeType("${MimeType}")"$)
        End If
        If CurrentTheme <> "" Then ExecuteScript($"editor.setOption("theme", "${CurrentTheme}");"$)
        If mReadOnly <> "false" Then ExecuteScript($"editor.setOption("readOnly", ${mReadOnly});"$)
        If mLineWrapping <> "false" Then ExecuteScript($"editor.setOption("lineWrapping", ${mLineWrapping});"$)
        If mAutofocus <> "false" Then ExecuteScript($"editor.setOption("autofocus", ${mAutofocus});"$)
        SetSize(mWidth + 10, mHeight + 10)
        CallSub(mCallBack,mEventName & "_Loaded")
    End If

Personally I've searched it on the Forum because I need a code editor for B4A, my purpose is to try adapt it on top of B4A WebView using WebViewExtras or UltimateWebView libraries, but if it do not work on B4J I cannot try to adapt it for this project I started, I only need HTML and Javascript integration, . If it works on B4J I think it should work on B4A too because it just work on browser right ?

I want to use it on this project, at this moment I just use a simple EditText:
https://www.b4x.com/android/forum/threads/pass-more-than-3-arguments-from-b4a-to-webview-javascript-function.139554/#post-88386

If you interested, some time ago I started to wrap RSSyntaxTextArea directly on Java using Eclipse. This control only works on top of Swing, for this I discharged a JFX Form and created a Swing Form, but now these days I tried to use the JFX SwingNode and maybe I can adapt it as control to put it on JFX Forms, I know that SwingNode only support lightweight controls, so not sure if it can work, need to try, I already tried it with Java3D Canvas without success, it is not a lightweight control.
RSyntaxTextArea worked well, I just wrapped some parts, with these I succesfully created a simple Arduino IDE with colored syntax, code completion, code folding, keyboard shortcuts for code templates, even tried the same for Java, C++ and Lua (to use with ESP8266), then I changed something wrong and now it only show a screen where all the text is red, something wrong with Theme.xls file I suppose.
This is another very good code editor, Scintilla is another one, I've managed it some ages ago, only on Win platform, but it exists for Java too so can be used as multiplatform code editor.

So.... returning to our CodeMirror project... Seem that I do something wrong on it, all seem to work apart the editor itself.
I've attached both (not working) examples as zip, note that I removed the codemirror.zip from projects Files folder because it is too big and I cannot post it. Please can you see what is wrong ?

Many thanks
 

Attachments

  • CMMinimal.zip
    22.5 KB · Views: 132
  • CodeEditor.zip
    27.6 KB · Views: 122
Last edited:

MicroDrie

Well-Known Member
Licensed User
The complaint is that the Editor variable was not found. To start with, you would put a break point just before every reference to the variable Editor.
Then you can proceed step by step in the hope of being able to see which step is followed by the notification. And then hope you find the step where the program stumbles.
Sometimes it is useful to pass on the break point after a number of problem-free steps, because usually you do not immediately find the line with the program code where it goes wrong.
 

stevel05

Expert
Licensed User
Longtime User
Hi Max,

I just tried downloading both or your projects, copied in the CodeMirror zip and both work for me.

The language list is stored within the Java code and does not access javascript.

I can't recreate your setup to test.

What version of Java do you have installed? I don't know what's available for a 32 bit system.
 

stevel05

Expert
Licensed User
Longtime User
If it works on B4J I think it should work on B4A too because it just work on browser right ?
Maybe, it's a long time since I've looked at javascript on the Android Webview. The interface to javascript required is not straightforward and may need quite a bit of work to get running.
 

max123

Well-Known Member
Licensed User
Longtime User
Hi @stevel05 and @MicroDrie ,

many thanks for replies, I've tested it for one day, even in debug mode and I cannot make it to work.

@stevel05 I compile with Java 8, with precision 1.8.,0_11. This sound strange, I never had problems with Java, maybe you compiled it with Java9 or Java11 ?

I will try now for a second day follow your suggestions and search to know what is wrong, if this work for you should work for me too.

Many thanks
 

max123

Well-Known Member
Licensed User
Longtime User
@stevel05 note that these days I worked on ThreeJS to make the same work accessing it with Javascript and worked well.

But with B4A.

I iteract with it the same way you iteract with CodeMirror, so I created an HTML template at runtime with StringBuilder, added imports for three.js and OrbitBehaviors.js (imported as text file inside HTML from Dir.Assets) then I insert all my JS calls inside the <script> tag and this worked well.

At the end it create a full HTML with all necessary that I can start at runtime just loading it in a webview: WebView.loadUrl.

NOTE: All scripts, styles etc... are embedded inside as String, not only a reference, this way it do not need any external dependency files, no .js, no .css, NOTHING, only the Index.html file that can be started on browser or loaded in a WebView.

The program even save the HTML file on internal memory, if I click it open in Firefox and works without problems.

But this on B4A using WebViewExtras and WebViewExtras.executeJavascript(script).

So I found that there are 2 ways to do it, or just send Javascript calls after HTML is created and loaded, or any time add any JS call inside an StringBuilder to collect all cals, then at the end save the full HTML with all JS inside it. I prefer the second way because the HTML file created contains all and just by loading it in the browser it work like an app, all bundled inside.

So very strange it won't work...
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
@MicroDrie the error only appeear if I press some buttons that call JS calls to a webview, launching the app I have no errors at all, just I do not see the CodeEditor loaded in the WebView, so it is a bit complicated to know even in debug mode.
 

stevel05

Expert
Licensed User
Longtime User
I don't have Java 1.8.,0_11, I tried it with 1.8.0_271 with no problem.

The clue is that the loadprogress_event is never called with NewState = "Succeeded" which implies the javascript is failing.

Have you changed the wrapper.js file? You may be able to debug that with some console.log's which should be redirected to the IDE's log if it is getting as far as setting that up of course.
 

MicroDrie

Well-Known Member
Licensed User
@MicroDrie the error only appeear if I press some buttons that call JS calls to a webview, launching the app I have no errors at all, just I do not see the CodeEditor loaded in the WebView, so it is a bit complicated to know even in debug mode.
Then take plan B add a break point half way your initialization routine and go from there. If the problem arise before the break point move the break point to a line before the current one. And you could follow each step from the start with single step so you can see if every program step is doing what they must do.

An other tip you can add a sleep(0) which is the replacement for DoEvents function to let the screen refresh.

The best tip is: get a 🤔 (old) 64bit machine which support the newest B4X program stuff. 😉
 

max123

Well-Known Member
Licensed User
Longtime User
Hi @stevel05

You said: Have you changed the wrapper.js file?
No

Now I opened the wrapper.js I see not big, just 170 lines, I want add some console.log to any function, but I do not know how to debug it in conjunction with B4J.

I see the callB4j(funct, param1, param2) function, maybe I can create on B4J a function that just pass a string in the log to show on the IDE ?

This way if possible can help to debug.

EDIT: OOH sorry now I can see a function to redirect console already exists... do I can just add some console.log and it redirect to B4J console?
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User

stevel05

Expert
Licensed User
Longtime User
OOH sorry now I can see a function to redirect console already exists... do I can just add some console.log and it redirect to B4J console?
Yes, that's the idea. But the javascript has to have loaded that part first. If it's failing it might not work.

I'm sure that a few years ago I managed to get debugging working with the Chrome Web Tools, but I can't for the life of me remember how.
 

max123

Well-Known Member
Licensed User
Longtime User
I found a strange thing,

the path of file matchbrackets.js request from B4J is different than the path inside a codemirror.zip file.
Even I have another strange path: codemirror-5.58.3/mode/%N/%N.js
and do not know what is it.

All other seem to coincide.

Attached a screenshot.
 

Attachments

  • Screen Shot 04-03-22 at 05.35 PM.PNG
    Screen Shot 04-03-22 at 05.35 PM.PNG
    129 KB · Views: 146

stevel05

Expert
Licensed User
Longtime User
I'm pretty sure that is part of the internal workings of the CodeMirror library I haven't looked at it for a while.
 

max123

Well-Known Member
Licensed User
Longtime User
I'm pretty sure that is part of the internal workings of the CodeMirror library I haven't looked at it for a while.
Ok, but point to an unexistent file inside a codemirror.zip
B4X:
    Dim CSSFileURI As String = $"${mCodeMirrorPath}/lib/codemirror.css"$
    Dim CodeFileURI As String = $"${mCodeMirrorPath}/lib/codemirror.js"$
    Dim LoadModePath As String =  $"${mCodeMirrorPath}/addon/mode/loadmode.js"$
'    Dim MatchBrackesPath As String = $"${mCodeMirrorPath}/addon/mode/matchbrackets.js"$ ' Original, point to unexistent file
    Dim MatchBrackesPath As String = $"${mCodeMirrorPath}/addon/edit/matchbrackets.js"$ ' Replaced, point to a right file
    Dim MetaPath As String =  $"${mCodeMirrorPath}/mode/meta.js"$
    Dim ModeURL As String =   $"${mCodeMirrorPath}/mode/%N/%N.js"$

%N/%N is misterious......

The final HTML file:
<!doctype html
<html>
<head>
<link rel="stylesheet" href="codemirror-5.58.3/lib/codemirror.css" />
<link rel="stylesheet" href="wrapper.css" />
<script src="codemirror-5.58.3/lib/codemirror.js"></script>
<script src="codemirror-5.58.3/addon/mode/loadmode.js"></script>
<script src="codemirror-5.58.3/addon/edit/matchbrackets.js"></script>
<script src="codemirror-5.58.3/mode/meta.js"></script>
<script src="wrapper.js"></script>
<link rel="stylesheet" href="codemirror-5.58.3/theme/3024-day.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/3024-night.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/abcdef.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/ambiance.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/ayu-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/ayu-mirage.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/base16-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/base16-light.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/bespin.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/blackboard.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/cobalt.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/colorforth.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/darcula.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/dracula.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/duotone-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/duotone-light.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/eclipse.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/elegant.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/erlang-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/gruvbox-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/hopscotch.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/icecoder.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/idea.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/isotope.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/lesser-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/liquibyte.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/lucario.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/material.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/material-darker.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/material-palenight.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/material-ocean.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/mbo.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/mdn-like.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/midnight.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/monokai.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/moxer.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/neat.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/neo.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/night.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/nord.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/oceanic-next.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/panda-syntax.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/paraiso-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/paraiso-light.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/pastel-on-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/railscasts.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/rubyblue.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/seti.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/shadowfox.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/solarizeddark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/solarizedlight.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/the-matrix.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/tomorrow-night-bright.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/tomorrow-night-eighties.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/ttcn.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/twilight.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/vibrant-ink.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/xq-dark.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/xq-light.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/yeti.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/yonce.css"</link>
<link rel="stylesheet" href="codemirror-5.58.3/theme/zenburn.css"</link>
<style>
body{
overflow:hidden;
}
.CodeMirror{
width: 790px;
height: 350px;
font-size: 12pt;
}
</style>
</head>
<body>
</body>
<script>
var doc;
CodeMirror.modeURL = "codemirror-5.58.3/mode/%N/%N.js";
var editor = CodeMirror(document.body, {
lineNumbers: true,
matchBrackets: true,
indentUnit: 4
});
//Set event For callback To B4j when the Code changes
editor.on('change',function(cm, change){
callB4j("codechanged",change.text);
//console.log("call commented");
});
function setCode(Code){
editor.setValue(atob(Code));
doc = editor.getDoc();
doc.clearHistory();
}

function changeType(val) {
var mode,spec,m;
if (m = /.+\.([^.]+)$/.exec(val)) {
var info = CodeMirror.findModeByExtension(m[1]);
if (info) {
mode = info.mode;
spec = info.mime;
}
} else if (/\//.test(val)) {
var info = CodeMirror.findModeByMIME(val);
if (info) {
mode = info.mode;
spec = val;
}
} else {
mode = spec = val;
}
if (mode) {
editor.setOption("mode", spec);
CodeMirror.autoLoadMode(editor, mode);
} else {
console.log("Could not find a mode corresponding to " + val);
}
}

</script>
</html>
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
%N/%N is misterious......
That's how the setup is supposed to be. I assume that it is a placeholder that gets replaced when code mirror runs.
 
Top