Conversation
Scilab cells showed as plain text in the live JupyterLab editor even
though PDF/HTML export was already highlighted (nbconvert/Pygments
picks language_info['name'] == 'scilab', a real Pygments lexer, so
that path was unaffected).
JupyterLab has no CodeMirror language for "scilab", and picks a mode
by matching language_info against its registry. Reporting
codemirror_mode: "octave" (Scilab's syntax being close to
Octave/Matlab) makes it resolve to the closest available mode.
Note that mimetype alone is not enough: JupyterLab 4.6's
getMimeTypeByLanguage() builds a fallback object as
{mimetype, name, ext}, but the language registry's findBest()
destructures {mime, name, extensions} -- a key-name mismatch that
silently drops the mimetype-only path back to plaintext. Setting
codemirror_mode explicitly goes through the working code path
(findByName(), case-insensitive) instead.
Known limitation: Octave's CodeMirror mode only recognizes "%" as a
line comment, while Scilab uses "//", so "//" comments aren't styled
and JupyterLab's Toggle Comment (Ctrl+/) inserts "%" -- invalid
Scilab syntax. A full fix needs a dedicated Scilab CodeMirror
language (a companion JupyterLab extension); this gets every other
token (strings, numbers, operators, keywords) highlighted correctly
in the meantime.
Fixes: https://discourse.jupyter.org/t/syntax-highlighting-scilab-language/37910
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jupyterlab-scilab (https://pypi.org/project/jupyterlab-scilab/) is now published: a real CodeMirror language for Scilab (correct "//" comments, ~2200 keywords/builtins sourced from Scilab's own getscilabkeywords()/what()), so the Octave-mode stopgap from this PR is no longer needed -- and is now actively harmful: an explicit codemirror_mode always wins over JupyterLab's own name-match fallback, so keeping "octave" here would keep forcing Octave-style highlighting even once jupyterlab-scilab is installed. Switched language_info/kernel.json back to codemirror_mode: "scilab" / mimetype: "text/x-scilab", and added jupyterlab-scilab as an optional dependency (`pip install scilab-kernel[jupyterlab]`) rather than a hard one, since scilab_kernel is also used outside JupyterLab (classic Notebook, other frontends) where pulling in the full jupyterlab package would be unwanted bloat. Verified: a live kernel now reports codemirror_mode: "scilab" in its kernel_info_reply, and this resolves correctly to text/x-scilab against jupyterlab-scilab's registered language (confirmed both by a faithful reproduction of JupyterLab 4.6's actual getMimeTypeByLanguage()/findBest()/findByName() matching code, and by a live JupyterLab instance with jupyterlab-scilab installed from PyPI). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not caused by this PR -- confirmed by cloning master fresh and running ruff there directly: it already fails with the same 39 errors. CI runs ruff over the whole repo rather than just the diff, so PR Calysto#58 inherited an already-broken check. Fixing it here since that's what's actually blocking this PR's CI. - I001/UP008/UP010/UP021/W605: mechanical (ruff --fix), reviewed -- import sorting, super() without arguments, dropped the unneeded py2 __future__ import, text=True instead of universal_newlines=True, and raw-string prefixes for the two genuinely-intended backslash literals. - UP031 (percent formatting): converted to f-strings throughout. - PLW1510: subprocess.run() for the macOS bundle-detection mdfind call now passes check=False explicitly, preserving the existing behavior (a non-zero/empty result is already handled by the `len(bundles) > 0` check right after). - TRY201: `raise e` -> `raise` in extract_figures' re-raise path (e is still used in the error_handler() branch, so the `as e` binding stays; only the bare re-raise changes, preserving the original traceback). - BLE001/S110 in _handle_svg: narrowed from bare `except Exception: pass` to `except (ValueError, ExpatError)`, since minidom parsing and the int()/unpacking calls right after are the only things that realistically fail here (GnuPlot producing an SVG shape the code doesn't expect), logged via self.log.debug instead of silently swallowed. - BLE001 in check.py: kept as a blind `except Exception`, with a justifying noqa -- it's a standalone diagnostic script whose whole purpose is to report *any* connection failure instead of crashing with a raw traceback. - RUF012: annotated the three intentionally-shared class-level dicts/ lists (language_info, and jupyter_kernel_test's own code_display_data/completion_samples convention) as typing.ClassVar. Verified: `ruff check .` passes clean, all files still py_compile, and a live kernel smoke-tested end to end (disp, %plot producing a real PNG display_data through the edited figure_size/axes_size f-strings, correct language_info). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tab completion ran completion("obj") directly and parsed Scilab's own
console echo of the result. That echo used to be an unquoted, bracketed
format Scilab dropped at least ~2 years ago in favor of displaying
string arrays quoted (`"a" "b" ...`), which the parsing here was
never updated for -- so every completion candidate now comes back
wrapped in literal double quotes.
Fixed by using printf("%s\n", completion(...)) instead, which prints
each match bare, one per line, sidestepping Scilab's own display
formatting entirely (and the quoting behind whatever future formatting
changes might come). Two things needed care:
- printf errors out on an empty array, so it's now only called when
completion() isn't empty (checked without assigning the result to a
variable -- get_completions() runs in the same persistent Scilab
session as the user's own code, so nothing here should risk shadowing
a variable of theirs).
- Scilab's terminal control codes end up glued to the front of the
*first* line of any console output. With the old quoted/bracketed
format this only ever landed on a line that got filtered out by
coincidence; with printf's plain output it could leak into a real
match (reproduced with a single-match completion, e.g. "getscilabkeyword"
-> "\x1b[4l \x08\x1b[0mgetscilabkeywords" instead of
"getscilabkeywords"). Now stripped via a small regex before parsing.
Verified against a live kernel: multi-match, single-match, and
no-match completions all return clean results, and `who_user()`
confirms nothing gets left behind in the session.
Also includes the same pre-existing ruff cleanup as PR Calysto#58 (this
branch was cut from master, which still has it) -- see that PR's
commit message for the itemized list; same fixes, same reasoning.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3 tasks done
…ghlighting # Conflicts: # scilab_kernel/kernel.py
Running a cell that leaves a block unclosed -- function x=f(y) with no
endfunction, an if/for/while/select/try with no matching end -- left
Scilab sitting at its continuation prompt waiting for the rest of the
block, and the kernel just hung. metakernel's REPLWrapper does detect
this (fast, well under a second) and has a built-in recovery: send
Ctrl-C, wait for a normal prompt, then raise a clean, catchable
ValueError. But scilab-adv-cli does not respond to SIGINT while
waiting for more input, so that recovery wait always ran out its full
budget (pexpect's default of 30s) before giving up -- so every
incomplete cell cost a good 30 seconds before showing a generic
"Timed out" error, though the session did eventually recover on its
own (confirmed: the next cell executes normally afterward).
Fixed with a small REPLWrapper subclass (_ScilabREPLWrapper) that
overrides just the continuation-prompt recovery step. A bare "end"
reliably closes any Scilab block type (function/if/for/while/...) and
returns to the top-level prompt in well under a second -- verified
directly against a live Scilab process for each block type. Nested
unclosed blocks (e.g. an unclosed if containing an unclosed for) need
one "end" per level, so it's sent repeatedly (capped at 50 attempts)
until a normal prompt reappears, matching what a user closing the
blocks by hand would have to type anyway.
Everything else about metakernel's flow is unchanged -- the "soft
continuation" empty-line retry, the eventual ValueError, and its
message ("Continuation prompt found - input was incomplete:\n<code>")
are all metakernel's own, already well-suited to surfacing a clear
error to the user; this only fixes the part that was actually broken
for Scilab specifically.
Verified against a live kernel: unclosed function/if/for now each
report the error in ~0.2-0.3s (down from 30s+), a nested unclosed
if+for needs and gets two "end"s and also recovers in ~0.3s, normal
complete multi-line blocks (if/end, function/endfunction, for/end)
are unaffected and execute correctly, and the session remains fully
functional for subsequent cells in every case.
Also includes the same pre-existing ruff cleanup as Calysto#58/Calysto#59 (this
branch was cut from master, which still has that debt).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5 tasks done
…hting # Conflicts: # scilab_kernel/kernel.py
metakernel's do_complete() calls get_completions() unconditionally,
even when the cursor sits right after a non-identifier character
(e.g. "+", "-") and there is no partial word to complete -- info['obj']
is "" in that case. get_completions() didn't guard against that, so it
ran completion(""), which matches every name Scilab knows (thousands),
and the substring filter `if info['obj'] in line` let all of them
through too (every string contains "" as a substring) -- a huge,
useless completion menu on a bare Tab after an operator.
Verified against a live kernel: "+" and "-" now return zero matches;
normal completions ("plo", "sin", single/no-match cases) are
unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
metakernel's parser sets cursor_start to 0 (start of the whole buffer)
whenever there is no partial identifier at the cursor -- regardless of
whether any completions were actually found. After "plot(1:10,", that
gives cursor_start=0, cursor_end=10 with zero matches from Scilab.
With no matches that wide range should be irrelevant to a kernel-only
completer, but JupyterLab also runs a generic word-from-document
completer alongside the kernel one, and hands it a range that happens
to span real words ("plot", "1", "10") already on the line -- so it
offers them as if they were completions, even though nothing was
actually being completed.
Fixed by collapsing cursor_start to cursor_end (a zero-width point at
the cursor) whenever get_completions() finds nothing, so no wide
buffer range is ever reported for JupyterLab's other completer to act
on. Normal completions are unaffected -- their cursor_start still
reflects the real start of the partial word being completed.
Verified against a live kernel: "plot(1:10," and "plot(" both now
report matches=0 with cursor_start == cursor_end (previously
cursor_start=0); "+"/"-" unaffected (already 0 matches, now also
correctly zero-width); "plo"/"sin" completions unaffected (still
return their normal candidate list with the correct word-start range).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reverts the "return [] when info['obj'] is empty" guard added a couple of commits back. That guard was meant to fix a real problem (4199 raw, quoted-looking matches dumped after typing e.g. "+"), but suppressing the kernel's own completions entirely on an empty prefix isn't actually the right fix for that, and isn't how Jupyter kernels conventionally behave: IPython itself shows the full registered-name list when completing on an empty prefix, and scilab-adv-cli's own interactive completion does the same. That original "huge menu" complaint is better understood as being about the quoting/parsing bug fixed earlier in this same function, not about the match count. Confirmed there is no way to selectively suppress this for Scilab: JupyterLab's completion providers (kernel, context, ...) are always all queried and their results unconditionally merged (checked directly in @jupyterlab/completer's ProviderReconciliator) -- there is no per-language opt-out, and no rank-based short-circuiting. So the right shape for scilab_kernel's own contribution is simply to match the established, expected convention, same as every other kernel. Verified against a live kernel: "+", "-", and "plot(1:10," now all return the full ~4199-entry name list with a correct zero-width cursor range (cursor_start == cursor_end, from metakernel's own existing correction once matches is non-empty); "plo"/"sin" prefix completions and "zzzzzz" (genuinely no matches) are unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mottelet
added a commit
to mottelet/jupyterlab-scilab
that referenced
this pull request
Sep 24, 2026
The previous README described this as an in-progress stopgap replacement -- "not yet wired up to scilab_kernel", "not built or tested against a real JupyterLab instance yet", scilab_kernel "currently" using Octave mode -- none of which has been true since Calysto/scilab_kernel#58 switched to codemirror_mode: "scilab" and both were verified end-to-end together. Rewritten to describe the extension as it actually is now: published, installable with a single pip command, and picked up automatically by scilab_kernel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Scilab runtime errors (undefined variable, wrong dimensions, syntax
errors, a user's own error(...) call, ...) were printed as plain
stdout -- only the unrelated continuation-prompt hang message (a
synthetic Python-side error from metakernel) got the red stderr
treatment.
lasterror() is Scilab's own record of the last error: it returns []
if the command that just ran didn't error, or the error text
otherwise, and reading it clears it -- a reliable signal regardless
of the message's content, unlike pattern-matching (which can't tell
a user's own error("some text") apart from ordinary output).
Since a Scilab error is always the last thing printed for a command
(execution stops as soon as one occurs), Write() now holds back each
output chunk by one: a new chunk flushes the previous one immediately
as plain stdout (live streaming during long loops is unaffected),
while the true last chunk waits until the command finishes, when
lasterror() says whether it should go out via Write() or Error().
Error() itself now also wraps its message with a blank line on each
side (matching the continuation-prompt message) and pads out with
newlines instead of spaces by delegating to metakernel's own
sep-joining, and strips Scilab's own terminal control codes from the
message first -- left in, an embedded reset code cancels the red
partway through.
Verified against a live kernel: undefined variables, wrong
dimensions, syntax errors, nested-function errors, and custom
error() messages (including ones with %/quotes) all show red;
plain output, plotting, tab completion, error recovery in the next
cell, and python -m scilab_kernel.check all unaffected.
Added code_stderr to test_scilab_kernel.py so jupyter_kernel_test's
own stderr test (previously skipped) now exercises this end-to-end.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Error() override added a leading/trailing blank line around every red message for legibility, but on reflection it's not wanted: drop it and go back to metakernel's own Error() as-is. The error detection/coloring itself (_had_scilab_error(), _flush_pending_output()) is unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the lack of syntax highlighting reported in this Jupyter Discourse thread: Scilab cells showed as plain text in the live JupyterLab editor, even though PDF/HTML export via
nbconvertwas already highlighted (that path uses Pygments, matched onlanguage_info['name'] == 'scilab', a real Pygments lexer, so it was unaffected).JupyterLab has no built-in Scilab CodeMirror mode. jupyterlab-scilab is now published to provide one — a real CodeMirror language for Scilab, with correct
//comments (not%, which is what Octave/Matlab-derived modes use) and ~2200 keywords/builtins sourced directly from Scilab's owngetscilabkeywords()/what(). Source: https://github.com/mottelet/jupyterlab-scilab.This PR:
codemirror_mode: "scilab"/mimetype: "text/x-scilab"inlanguage_infoandkernel.jsonso JupyterLab picks up jupyterlab-scilab's registered language. Without it installed, cells fall back to plain text in the live editor (as before this PR) —nbconvert/Pygments export is unaffected either way.jupyterlab-scilabas an optional dependency:pip install scilab-kernel[jupyterlab]. Not a hard dependency, since scilab_kernel is also used outside JupyterLab (classic Notebook, other frontends), where pulling in the fulljupyterlabpackage (a transitive dependency of jupyterlab-scilab) would be unwanted bloat.completion("obj")directly and parsed Scilab's own console echo, which changed format at least ~2 years ago (now quotes string-array output), breaking every completion candidate. Fixed by usingprintf("%s\n", completion(...))instead, which sidesteps Scilab's own display formatting; guarded against the empty-match case (printferrors on an empty array) without ever assigning to a variable that could shadow one of the user's own, since completion runs in the same persistent Scilab session as their code. Also fixed a latent bug the change exposed: Scilab's terminal control codes glued to the front of the first line of output could leak into a genuine single-match result.+,-, or insideplot(1:10,) now returns the full registered-name list, matching IPython's own convention, rather than either the old broken quoted dump or an artificially suppressed empty list.function x=f(y)with noendfunction, anif/for/while/... with no matchingend).metakernel's own recovery for a stuck continuation prompt sends Ctrl-C, butscilab-adv-clidoesn't respond to SIGINT while mid-block, so that recovery always burned its full 30s timeout before showing a generic "Timed out" error. Fixed with a smallREPLWrappersubclass that recovers via a bareendinstead — closes any block type in well under a second, with nested unclosed blocks getting oneendper level.rufffindings that were blocking CI (confirmed unrelated to any of the above by cloningmasterfresh and runningruffthere directly — it already failed with the same errors).Test plan
jupyterlab-scilabinstalled, a fresh notebook with the Scilab kernel highlights keywords/strings/numbers/comments correctly, including previously-tricky cases (//comments, transpose'after]/), primitives likereal/imag/cvode/plot2d2).kernel_info_replyquery that the running kernel reportscodemirror_mode: "scilab", and via a faithful reproduction of JupyterLab 4.6'sgetMimeTypeByLanguage()/findBest()/findByName()matching code that this resolves correctly against jupyterlab-scilab's registered language.who_user()that nothing is left behind in the Scilab session.function/if/for(including nested) now report a clear error in ~0.2-0.3s instead of hanging ~30s; normal complete multi-line blocks are unaffected; the session stays fully functional afterward.ruff check .andpy_compileclean on all changed files.🤖 Generated with Claude Code