Skip to content

Commit fd68f9b

Browse files
committed
Resolved #107 -- Added support for line & cell magics by subclassing MetaKernel
- Subclassed MetaKernel to implement custom line and cell magic commands - Updated output methods to follow MetaKernel’s conventions - Adjusted execution logic to align with MetaKernel's do_execute function
1 parent 6b41e4f commit fd68f9b

File tree

1 file changed

+18
-57
lines changed

1 file changed

+18
-57
lines changed

dyalog_kernel/kernel.py

Lines changed: 18 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
from collections import deque
1111

12-
13-
from ipykernel.kernelbase import Kernel
12+
from metakernel import MetaKernel
13+
from IPython.display import HTML, Image
1414
from dyalog_kernel import __version__
1515

1616
if sys.platform.lower().startswith('win'):
@@ -49,7 +49,7 @@ def writeln(s):
4949
sys.stdout = tmp_stdout
5050

5151

52-
class DyalogKernel(Kernel):
52+
class DyalogKernel(MetaKernel):
5353

5454
implementation = 'Dyalog'
5555
implementation_version = __version__
@@ -72,60 +72,19 @@ class DyalogKernel(Kernel):
7272
dyalog_subprocess = None
7373

7474
def out_error(self, s):
75-
_content = {
76-
'output_type': 'stream',
77-
'name': 'stderr', # stdin or stderr
78-
'text': s
79-
}
80-
self.send_response(self.iopub_socket, 'stream', _content)
75+
self.Error(s)
8176

8277
def out_png(self, s):
83-
_content = {
84-
'output_type': 'display_data',
85-
'data': {
86-
#'text/plain' : ['multiline text data'],
87-
'image/png': s,
88-
#'application/json':{
89-
# JSON data is included as-is
90-
# 'json':'data',
91-
#},
92-
},
93-
'metadata': {
94-
'image/svg': {
95-
'width': 120,
96-
'height': 80,
97-
},
98-
},
99-
}
100-
self.send_response(self.iopub_socket, 'display_data', _content)
78+
#width and height values are fixed to maintain consistency with original implementation
79+
self.Display(Image(data=s, width=120, height=80))
10180

10281
def out_html(self, s):
103-
_content = {
104-
# 'output_type': 'display_data',
105-
'data': {'text/html': s},
106-
'execution_count': self.execution_count,
107-
'metadata': ''
108-
# 'transient': ''
109-
}
110-
self.send_response(self.iopub_socket, 'execute_result', _content)
82+
self.Display(HTML(s))
11183

11284
def out_result(self, s):
113-
# injecting css: white-space:pre. Means no wrapping, RIDE SetPW will take care about line wrapping
114-
11585
html_start = '<pre class="language-APL">'
11686
html_end = '</pre>'
117-
118-
_content = {
119-
# 'output_type': 'display_data',
120-
# 'data': {'text/plain': s},
121-
'data': {'text/html': html_start + html.escape(s, False) + html_end},
122-
'execution_count': self.execution_count,
123-
'metadata': {},
124-
125-
# 'transient': ''
126-
}
127-
128-
self.send_response(self.iopub_socket, 'execute_result', _content)
87+
self.Display(HTML(html_start + html.escape(s, False) + html_end))
12988

13089
def out_stream(self, s):
13190
_content = {
@@ -195,7 +154,7 @@ def __init__(self, **kwargs):
195154
#from ipykernel import get_connection_file
196155
#s = get_connection_file()
197156
# debug("########## " + str(s))
198-
157+
super(DyalogKernel, self).__init__(**kwargs)
199158
self._port = DYALOG_PORT
200159
# lets find first available port, starting from default DYALOG_PORT (:4502)
201160
# this makes sense only if Dyalog APL and Jupyter executables are on the same host (localhost)
@@ -254,8 +213,6 @@ def __init__(self, **kwargs):
254213
self.dyalog_subprocess = subprocess.Popen([dyalog, '+s', '-q', os.path.dirname(os.path.abspath(
255214
__file__)) + '/init.dws'], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=dyalog_env)
256215

257-
Kernel.__init__(self, **kwargs)
258-
259216
self.dyalog_ride_connect()
260217

261218
def recv_all(self, msg_len):
@@ -327,8 +284,9 @@ def ride_send(self, d):
327284

328285
self.dyalogTCP.sendall(_data)
329286
debug("SEND " + _data[8:].decode("utf-8"))
330-
331-
def do_execute(self, code, silent, store_history=True, user_expressions=None,
287+
288+
#renamed to _do_execute to avoid overriding the do_execute method in MetaKernel
289+
def _do_execute(self, code, silent=False, store_history=True, user_expressions=None,
332290
allow_stdin=True):
333291
global SUSPEND
334292
code = code.strip()
@@ -355,9 +313,8 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None,
355313
'JUPYTER NOTEBOOK: UNDEFINED ARGUMENT TO %suspend, USE EITHER on OR off')
356314
lines = lines[1:]
357315
elif lines[0].lower() == ']dinput':
358-
lines = lines[1:]
316+
lines = lines[1:]
359317
try:
360-
# the windows interpreter can only handle ~125 chacaters at a time, so we do one line at a time
361318
pt = None
362319
for line in lines:
363320
line = line + '\n'
@@ -452,6 +409,10 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None,
452409
}
453410

454411
return reply_content
412+
413+
def do_execute_direct(self, code, silent=False):
414+
#defers non-magic code execution to the do_execute function defined by Dyalog
415+
self._do_execute(code, silent)
455416

456417
def execute_line(self, line):
457418
self.ride_send(["Execute", {"trace": 0, "text": line}])
@@ -463,7 +424,7 @@ def define_function(self, lines):
463424
self.execute_line("⎕SE.Dyalog.ipyFn,←⊂," + quoted + "\n")
464425
self.ride_receive_wait()
465426
dq.clear()
466-
if re.match('^\\s*:namespace|:class|:interface',lines[0].lower()):
427+
if re.match('^\\s*:namespace|:class|:interface',lines[0].lower()):
467428
self.execute_line("{0::'DEFN ERROR'⋄⎕FIX ⍵}⎕SE.Dyalog.ipyFn\n")
468429
else:
469430
self.execute_line("{''≢0⍴r←⎕FX ⍵:511 ⎕SIGNAL⍨'DEFN ERROR: Issue on line ',⍕r}⎕SE.Dyalog.ipyFn\n")

0 commit comments

Comments
 (0)