├── .gitignore ├── COPYING.md ├── README.md ├── bin ├── tm_jupyter └── tm_kernelspecs ├── doc └── tests.tm ├── jupyter_texmacs ├── __init__.py ├── __main__.py ├── _version.py ├── app.py ├── completer.py ├── protocol.py ├── tmshell.py └── zmqhistory.py └── progs ├── init-jupyter.scm └── jupyter-widgets.scm /.gitignore: -------------------------------------------------------------------------------- 1 | jupyter_texmacs/__pycache__ 2 | -------------------------------------------------------------------------------- /COPYING.md: -------------------------------------------------------------------------------- 1 | # Licensing terms 2 | 3 | This project is licensed under the terms of the Modified BSD License 4 | (also known as New or Revised or 3-Clause BSD), as follows: 5 | 6 | - Copyright (c) 2001-2015, IPython Development Team 7 | - Copyright (c) 2015-, Jupyter Development Team 8 | 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions are met: 13 | 14 | Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 17 | Redistributions in binary form must reproduce the above copyright notice, this 18 | list of conditions and the following disclaimer in the documentation and/or 19 | other materials provided with the distribution. 20 | 21 | Neither the name of the Jupyter Development Team nor the names of its 22 | contributors may be used to endorse or promote products derived from this 23 | software without specific prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 28 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 29 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 31 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | 36 | ## About the Jupyter Development Team 37 | 38 | The Jupyter Development Team is the set of all contributors to the Jupyter project. 39 | This includes all of the Jupyter subprojects. 40 | 41 | The core team that coordinates development on GitHub can be found here: 42 | https://github.com/jupyter/. 43 | 44 | ## Our Copyright Policy 45 | 46 | Jupyter uses a shared copyright model. Each contributor maintains copyright 47 | over their contributions to Jupyter. But, it is important to note that these 48 | contributions are typically only changes to the repositories. Thus, the Jupyter 49 | source code, in its entirety is not the copyright of any single person or 50 | institution. Instead, it is the collective copyright of the entire Jupyter 51 | Development Team. If individual contributors want to maintain a record of what 52 | changes/contributions they have specific copyright on, they should indicate 53 | their copyright in the commit message of the change, when they commit the 54 | change to one of the Jupyter repositories. 55 | 56 | With this in mind, the following banner should be used in any source code file 57 | to indicate the copyright and license terms: 58 | 59 | # Copyright (c) Jupyter Development Team. 60 | # Distributed under the terms of the Modified BSD License. 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jupyter client for TeXmacs 2 | 3 | Version 0.2.0 -- (c) 2021 Massimiliano Gubinelli, Jeroen Wouters 4 | 5 | Allows for interactive TeXmacs sessions with Jupyter kernels such as IPython, IJulia, IRKernel, IHaskell. 6 | 7 | In development, many things to fix: history, rich output, non-textual output, interaction, help... 8 | 9 | Based on jupyter_console, a terminal-based console frontend for Jupyter kernels. 10 | jupyter_console is released under a modified BSD license, see COPYING.md 11 | 12 | - [Jupyter protocol](https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging) 13 | 14 | ## Installation 15 | 16 | To install the plugin, clone the repository, rename its directory to `jupyter` and copy it into either the `$TEXMACS_PATH/plugins` or `$TEXMACS_HOME_PATH/plugins`. As stated in the TeXmacs documentation: by default, the path `$TEXMACS_HOME_PATH` equals `%appdata%\TeXmacs` on Windows or `$HOME/.TeXmacs` on GNU/Linux and macOS. 17 | 18 | The list of installed Jupyter kernels will appear under Insert → Session → Jupyter. 19 | 20 | ## Additional resources 21 | - [Project Jupyter website](https://jupyter.org) 22 | - [Documentation for Jupyter Console](https://jupyter-console.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-console/latest/jupyter-notebook.pdf)] 23 | - [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] 24 | - [Issues](https://github.com/jupyter/jupyter_console/issues) 25 | - [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) 26 | -------------------------------------------------------------------------------- /bin/tm_jupyter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ############################################################################### 3 | ## 4 | ## MODULE : tm_jupyter 5 | ## DESCRIPTION : TeXmacs interface to jupyter kernels 6 | ## COPYRIGHT : (C) 2019 Massimiliano Gubinelli 7 | ## 8 | ## This software falls under the GNU general public license version 3 or later. 9 | ## It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE 10 | ## in the root directory or . 11 | 12 | import os 13 | import sys 14 | sys.path.append(os.environ.get("TEXMACS_PATH") + "/plugins/jupyter") 15 | sys.path.append(os.environ.get("TEXMACS_HOME_PATH") + "/plugins/jupyter") 16 | 17 | from jupyter_texmacs import app 18 | 19 | if __name__ == '__main__': 20 | app.main() 21 | -------------------------------------------------------------------------------- /bin/tm_kernelspecs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ############################################################################### 3 | ## 4 | ## MODULE : tm_kernelspecs 5 | ## DESCRIPTION : list the installed Jupyter kernels 6 | ## COPYRIGHT : (C) 2021 Jeroen Wouters 7 | ## 8 | ## This software falls under the GNU general public license version 3 or later. 9 | ## It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE 10 | ## in the root directory or . 11 | 12 | 13 | import jupyter_client 14 | specManager = jupyter_client.kernelspec.KernelSpecManager() 15 | for kernel in specManager.find_kernel_specs().keys(): 16 | print(kernel) 17 | -------------------------------------------------------------------------------- /doc/tests.tm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <\body> 6 | <\wide-tabular> 7 | ||||| 9 | Some test for the session, for the moment using the 10 | kernel. 11 | >>> 12 | 13 | 14 | \; 15 | 16 | 17 | 18 | <\itemize-dot> 19 | Loot at 20 | for examples of interactive code we could support. 21 | 22 | PS image output still does not work from matplotlib. Try to 23 | understand what really happens in IPython 24 | 25 | History and command completion are not yet implemented. 26 | 27 | Tracebacks and shell commands returns ANSI colored text, we need to 28 | override this or parse it correctly. 29 | 30 | We would like to have syntax-coloring for the code, maybe direclty 31 | supported from the Jupyter kernel (e.g. IPython) since we do not want to 32 | implement all the possible syntax coloring ourselves (but this could have 33 | the advantage to be available offline) 34 | 35 | 36 | \; 37 | 38 | <\session|jupyter|default> 39 | <\output> 40 | Jupyter console 0.1.0dev 41 | 42 | \; 43 | 44 | Python 3.7.3 (default, Mar 27 2019, 09:23:15)\ 45 | 46 | Type 'copyright', 'credits' or 'license' for more information 47 | 48 | IPython 7.5.0 -- An enhanced Interactive Python. Type '?' for help. 49 | 50 | 51 | <\unfolded-io> 52 | In [1]:\ 53 | <|unfolded-io> 54 | ## this shows ASCII codes we still not handle correctly 55 | 56 | !ls 57 | <|unfolded-io> 58 | [34m3rdparty[m[m \ \ \ \ \ \ Makefile.in \ \ \ [34mbenchmark[m[m 59 | \ \ \ \ \ [31mconfigure[m[m \ \ \ \ \ [34msrc[m[m 60 | 61 | CMakeLists.txt README.md \ \ \ \ \ [34mcmake[m[m 62 | \ \ \ \ \ \ \ \ \ configure.in \ \ [34mtests[m[m 63 | 64 | COMPILE \ \ \ \ \ \ \ SVNREV \ \ \ \ \ \ \ \ [31mconfig.guess[m[m 65 | \ \ [31minstall-sh[m[m 66 | 67 | COPYING \ \ \ \ \ \ \ TODO \ \ \ \ \ \ \ \ \ \ config.log 68 | \ \ \ \ [34mmisc[m[m 69 | 70 | LICENSE \ \ \ \ \ \ \ [34mTeXmacs[m[m 71 | \ \ \ \ \ \ \ [31mconfig.status[m[m \ [34mpackages[m[m 72 | 73 | Makefile \ \ \ \ \ \ aclocal.m4 \ \ \ \ [31mconfig.sub[m[m 74 | \ \ \ \ [34mplugins[m[m 75 | 76 | 77 | <\unfolded-io> 78 | In [2]:\ 79 | <|unfolded-io> 80 | <\code> 81 | ## we can write directly to stdout 82 | 83 | print("hi, stdout") 84 | 85 | <|unfolded-io> 86 | hi, stdout 87 | 88 | 89 | <\unfolded-io> 90 | In [4]:\ 91 | <|unfolded-io> 92 | ## we can write directly to stderr 93 | 94 | import sys 95 | 96 | print('hi, stderr', file=sys.stderr) 97 | <|unfolded-io> 98 | <\errput> 99 | hi, stderr 100 | 101 | 102 | 103 | <\unfolded-io> 104 | In [5]:\ 105 | <|unfolded-io> 106 | ## what about multiple delayed output?? 107 | 108 | import time, sys 109 | 110 | for i in range(8): 111 | 112 | \ \ \ \ print(i) 113 | 114 | \ \ \ \ time.sleep(0.5) 115 | <|unfolded-io> 116 | 0 117 | 118 | 1 119 | 120 | 2 121 | 122 | 3 123 | 124 | 4 125 | 126 | 5 127 | 128 | 6 129 | 130 | 7 131 | 132 | 133 | <\unfolded-io> 134 | In [1]:\ 135 | <|unfolded-io> 136 | <\code> 137 | import matplotlib 138 | 139 | matplotlib.use('PS') \ \ # generate postscript output by default 140 | 141 | import matplotlib.pyplot as plt 142 | 143 | import numpy as np 144 | 145 | \; 146 | 147 | <|unfolded-io> 148 | <\errput> 149 | {'header': {'msg_id': '294df570-a7604fe99d22454db55e15b9', 150 | 'msg_type': 'status', 'username': 'mgubi', 'session': 151 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 152 | 6, 17, 15, 24, 42, 631823, tzinfo=tzutc()), 'version': '5.3'}, 153 | 'msg_id': '294df570-a7604fe99d22454db55e15b9', 'msg_type': 'status', 154 | 'parent_header': {'msg_id': 'b8089324-4c1de77570fe55d5f605a9c8', 155 | 'msg_type': 'kernel_info_request', 'username': 'mgubi', 'session': 156 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 157 | 6, 17, 15, 24, 41, 312403, tzinfo=tzutc()), 'version': '5.3'}, 158 | 'metadata': {}, 'content': {'execution_state': 'busy'}, 'buffers': 159 | []} 160 | 161 | {'header': {'msg_id': '9ecf7aa5-2743b7335cd5eef9d40e73ec', 162 | 'msg_type': 'status', 'username': 'mgubi', 'session': 163 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 164 | 6, 17, 15, 24, 42, 634490, tzinfo=tzutc()), 'version': '5.3'}, 165 | 'msg_id': '9ecf7aa5-2743b7335cd5eef9d40e73ec', 'msg_type': 'status', 166 | 'parent_header': {'msg_id': 'b8089324-4c1de77570fe55d5f605a9c8', 167 | 'msg_type': 'kernel_info_request', 'username': 'mgubi', 'session': 168 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 169 | 6, 17, 15, 24, 41, 312403, tzinfo=tzutc()), 'version': '5.3'}, 170 | 'metadata': {}, 'content': {'execution_state': 'idle'}, 'buffers': 171 | []} 172 | 173 | {'header': {'msg_id': 'e3e72450-50f9095be37bcf4d6abc29fb', 174 | 'msg_type': 'status', 'username': 'mgubi', 'session': 175 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 176 | 6, 17, 15, 24, 42, 637696, tzinfo=tzutc()), 'version': '5.3'}, 177 | 'msg_id': 'e3e72450-50f9095be37bcf4d6abc29fb', 'msg_type': 'status', 178 | 'parent_header': {'msg_id': '44bf248a-554cacda2d81ed719eeca7ca', 179 | 'msg_type': 'kernel_info_request', 'username': 'mgubi', 'session': 180 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 181 | 6, 17, 15, 24, 41, 315539, tzinfo=tzutc()), 'version': '5.3'}, 182 | 'metadata': {}, 'content': {'execution_state': 'busy'}, 'buffers': 183 | []} 184 | 185 | {'header': {'msg_id': '186b8b71-c27d08d6698a4f8f986dc26c', 186 | 'msg_type': 'status', 'username': 'mgubi', 'session': 187 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 188 | 6, 17, 15, 24, 42, 640108, tzinfo=tzutc()), 'version': '5.3'}, 189 | 'msg_id': '186b8b71-c27d08d6698a4f8f986dc26c', 'msg_type': 'status', 190 | 'parent_header': {'msg_id': '44bf248a-554cacda2d81ed719eeca7ca', 191 | 'msg_type': 'kernel_info_request', 'username': 'mgubi', 'session': 192 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 193 | 6, 17, 15, 24, 41, 315539, tzinfo=tzutc()), 'version': '5.3'}, 194 | 'metadata': {}, 'content': {'execution_state': 'idle'}, 'buffers': 195 | []} 196 | 197 | {'header': {'msg_id': 'c34e2fb5-155d24f6d720e3550b266d9e', 198 | 'msg_type': 'status', 'username': 'mgubi', 'session': 199 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 200 | 6, 17, 15, 24, 42, 643944, tzinfo=tzutc()), 'version': '5.3'}, 201 | 'msg_id': 'c34e2fb5-155d24f6d720e3550b266d9e', 'msg_type': 'status', 202 | 'parent_header': {'msg_id': 'ee745461-e3888fcb6d44e2a1b1054a42', 203 | 'msg_type': 'history_request', 'username': 'mgubi', 'session': 204 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 205 | 6, 17, 15, 24, 42, 642228, tzinfo=tzutc()), 'version': '5.3'}, 206 | 'metadata': {}, 'content': {'execution_state': 'busy'}, 'buffers': 207 | []} 208 | 209 | {'header': {'msg_id': 'd524b283-e997ed594100e714955aa288', 210 | 'msg_type': 'status', 'username': 'mgubi', 'session': 211 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 212 | 6, 17, 15, 24, 42, 652139, tzinfo=tzutc()), 'version': '5.3'}, 213 | 'msg_id': 'd524b283-e997ed594100e714955aa288', 'msg_type': 'status', 214 | 'parent_header': {'msg_id': 'ee745461-e3888fcb6d44e2a1b1054a42', 215 | 'msg_type': 'history_request', 'username': 'mgubi', 'session': 216 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 217 | 6, 17, 15, 24, 42, 642228, tzinfo=tzutc()), 'version': '5.3'}, 218 | 'metadata': {}, 'content': {'execution_state': 'idle'}, 'buffers': 219 | []} 220 | 221 | {'header': {'msg_id': '8f35cf69-383b2c84d363bccd1a3bc013', 222 | 'msg_type': 'status', 'username': 'mgubi', 'session': 223 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 224 | 6, 17, 15, 24, 42, 706180, tzinfo=tzutc()), 'version': '5.3'}, 225 | 'msg_id': '8f35cf69-383b2c84d363bccd1a3bc013', 'msg_type': 'status', 226 | 'parent_header': {'msg_id': 'c4bf032b-3f630c66d60a9ff559b8836f', 227 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 228 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 229 | 6, 17, 15, 24, 42, 704165, tzinfo=tzutc()), 'version': '5.3'}, 230 | 'metadata': {}, 'content': {'execution_state': 'busy'}, 'buffers': 231 | []} 232 | 233 | {'header': {'msg_id': 'b4e3988c-b21441982fd28e59ca49c530', 234 | 'msg_type': 'execute_input', 'username': 'mgubi', 'session': 235 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 236 | 6, 17, 15, 24, 42, 706873, tzinfo=tzutc()), 'version': '5.3'}, 237 | 'msg_id': 'b4e3988c-b21441982fd28e59ca49c530', 'msg_type': 238 | 'execute_input', 'parent_header': {'msg_id': 239 | 'c4bf032b-3f630c66d60a9ff559b8836f', 'msg_type': 'execute_request', 240 | 'username': 'mgubi', 'session': 'c3870339-7d48ea325f778b60fba909c3', 241 | 'date': datetime.datetime(2019, 6, 17, 15, 24, 42, 704165, 242 | tzinfo=tzutc()), 'version': '5.3'}, 'metadata': {}, 'content': 243 | {'code': "import matplotlib\\nmatplotlib.use('PS') \ \ # generate 244 | postscript output by default\\nimport matplotlib.pyplot as 245 | plt\\nimport numpy as np", 'execution_count': 1}, 'buffers': []} 246 | 247 | {'header': {'msg_id': '2c7aa96e-6be896cd516541a70b86a1fd', 248 | 'msg_type': 'status', 'username': 'mgubi', 'session': 249 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 250 | 6, 17, 15, 24, 43, 535942, tzinfo=tzutc()), 'version': '5.3'}, 251 | 'msg_id': '2c7aa96e-6be896cd516541a70b86a1fd', 'msg_type': 'status', 252 | 'parent_header': {'msg_id': 'c4bf032b-3f630c66d60a9ff559b8836f', 253 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 254 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 255 | 6, 17, 15, 24, 42, 704165, tzinfo=tzutc()), 'version': '5.3'}, 256 | 'metadata': {}, 'content': {'execution_state': 'idle'}, 'buffers': 257 | []} 258 | 259 | 260 | 261 | <\unfolded-io> 262 | In [2]:\ 263 | <|unfolded-io> 264 | fig = plt.figure() \ # an empty figure with no axes 265 | 266 | fig.suptitle('No axes on this figure') \ # Add a title so we know which 267 | it is 268 | 269 | \; 270 | 271 | fig, ax_lst = plt.subplots(2, 2) \ # a figure with a 2x2 grid of Axes 272 | <|unfolded-io> 273 | <\errput> 274 | {'header': {'msg_id': '4d88f629-0c374bd5897a6ead28c58624', 275 | 'msg_type': 'status', 'username': 'mgubi', 'session': 276 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 277 | 6, 17, 15, 24, 45, 469227, tzinfo=tzutc()), 'version': '5.3'}, 278 | 'msg_id': '4d88f629-0c374bd5897a6ead28c58624', 'msg_type': 'status', 279 | 'parent_header': {'msg_id': '87b368e9-d9c15ea1e77a6f2d965f3ba2', 280 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 281 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 282 | 6, 17, 15, 24, 45, 468254, tzinfo=tzutc()), 'version': '5.3'}, 283 | 'metadata': {}, 'content': {'execution_state': 'busy'}, 'buffers': 284 | []} 285 | 286 | {'header': {'msg_id': 'f1563d87-620db945c6ffc61acf4fdc5e', 287 | 'msg_type': 'execute_input', 'username': 'mgubi', 'session': 288 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 289 | 6, 17, 15, 24, 45, 469435, tzinfo=tzutc()), 'version': '5.3'}, 290 | 'msg_id': 'f1563d87-620db945c6ffc61acf4fdc5e', 'msg_type': 291 | 'execute_input', 'parent_header': {'msg_id': 292 | '87b368e9-d9c15ea1e77a6f2d965f3ba2', 'msg_type': 'execute_request', 293 | 'username': 'mgubi', 'session': 'c3870339-7d48ea325f778b60fba909c3', 294 | 'date': datetime.datetime(2019, 6, 17, 15, 24, 45, 468254, 295 | tzinfo=tzutc()), 'version': '5.3'}, 'metadata': {}, 'content': 296 | {'code': "fig = plt.figure() \ # an empty figure with no 297 | axes\\nfig.suptitle('No axes on this figure') \ # Add a title so we 298 | know which it is\\nfig, ax_lst = plt.subplots(2, 2) \ # a figure with 299 | a 2x2 grid of Axes", 'execution_count': 2}, 'buffers': []} 300 | 301 | {'header': {'msg_id': '25e1afdb-bead81c58603bf09dd334550', 302 | 'msg_type': 'status', 'username': 'mgubi', 'session': 303 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 304 | 6, 17, 15, 24, 45, 516856, tzinfo=tzutc()), 'version': '5.3'}, 305 | 'msg_id': '25e1afdb-bead81c58603bf09dd334550', 'msg_type': 'status', 306 | 'parent_header': {'msg_id': '87b368e9-d9c15ea1e77a6f2d965f3ba2', 307 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 308 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 309 | 6, 17, 15, 24, 45, 468254, tzinfo=tzutc()), 'version': '5.3'}, 310 | 'metadata': {}, 'content': {'execution_state': 'idle'}, 'buffers': 311 | []} 312 | 313 | 314 | 315 | <\unfolded-io> 316 | In [3]:\ 317 | <|unfolded-io> 318 | x = np.linspace(0, 2, 100) 319 | 320 | \; 321 | 322 | plt.plot(x, x, label='linear') 323 | 324 | plt.plot(x, x**2, label='quadratic') 325 | 326 | plt.plot(x, x**3, label='cubic') 327 | 328 | \; 329 | 330 | plt.xlabel('x label') 331 | 332 | plt.ylabel('y label') 333 | 334 | \; 335 | 336 | plt.title("Simple Plot") 337 | 338 | \; 339 | 340 | plt.legend() 341 | 342 | \; 343 | 344 | plt.show() 345 | <|unfolded-io> 346 | <\errput> 347 | {'header': {'msg_id': '7aadd7a5-bcddda973c6b2a34fba514e6', 348 | 'msg_type': 'status', 'username': 'mgubi', 'session': 349 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 350 | 6, 17, 15, 25, 25, 796688, tzinfo=tzutc()), 'version': '5.3'}, 351 | 'msg_id': '7aadd7a5-bcddda973c6b2a34fba514e6', 'msg_type': 'status', 352 | 'parent_header': {'msg_id': '98026e92-79cb3634ae3be2e314a81649', 353 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 354 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 355 | 6, 17, 15, 25, 25, 795737, tzinfo=tzutc()), 'version': '5.3'}, 356 | 'metadata': {}, 'content': {'execution_state': 'busy'}, 'buffers': 357 | []} 358 | 359 | {'header': {'msg_id': 'f647e284-38e13287a522943134d0a6f4', 360 | 'msg_type': 'execute_input', 'username': 'mgubi', 'session': 361 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 362 | 6, 17, 15, 25, 25, 796886, tzinfo=tzutc()), 'version': '5.3'}, 363 | 'msg_id': 'f647e284-38e13287a522943134d0a6f4', 'msg_type': 364 | 'execute_input', 'parent_header': {'msg_id': 365 | '98026e92-79cb3634ae3be2e314a81649', 'msg_type': 'execute_request', 366 | 'username': 'mgubi', 'session': 'c3870339-7d48ea325f778b60fba909c3', 367 | 'date': datetime.datetime(2019, 6, 17, 15, 25, 25, 795737, 368 | tzinfo=tzutc()), 'version': '5.3'}, 'metadata': {}, 'content': 369 | {'code': 'x = np.linspace(0, 2, 100)\\nplt.plot(x, x, 370 | label=\\'linear\\')\\nplt.plot(x, x**2, 371 | label=\\'quadratic\\')\\nplt.plot(x, x**3, 372 | label=\\'cubic\\')\\nplt.xlabel(\\'x label\\')\\nplt.ylabel(\\'y 373 | label\\')\\nplt.title("Simple Plot")\\nplt.legend()\\nplt.show()', 374 | 'execution_count': 3}, 'buffers': []} 375 | 376 | {'header': {'msg_id': '4925b794-a3d63202a6302312804e93c5', 377 | 'msg_type': 'stream', 'username': 'mgubi', 'session': 378 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 379 | 6, 17, 15, 25, 25, 831581, tzinfo=tzutc()), 'version': '5.3'}, 380 | 'msg_id': '4925b794-a3d63202a6302312804e93c5', 'msg_type': 'stream', 381 | 'parent_header': {'msg_id': '98026e92-79cb3634ae3be2e314a81649', 382 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 383 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 384 | 6, 17, 15, 25, 25, 795737, tzinfo=tzutc()), 'version': '5.3'}, 385 | 'metadata': {}, 'content': {'name': 'stderr', 'text': 386 | "/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:9: 387 | UserWarning: Matplotlib is currently using ps, which is a non-GUI 388 | backend, so cannot show the figure.\\n \ if __name__ == 389 | '__main__':\\n"}, 'buffers': []} 390 | 391 | /usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:9: 392 | UserWarning: Matplotlib is currently using ps, which is a non-GUI 393 | backend, so cannot show the figure. 394 | 395 | \ \ if __name__ == '__main__': 396 | 397 | {'header': {'msg_id': 'cae52008-afae9cb50a4dcad2ed8bf964', 398 | 'msg_type': 'status', 'username': 'mgubi', 'session': 399 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 400 | 6, 17, 15, 25, 25, 836161, tzinfo=tzutc()), 'version': '5.3'}, 401 | 'msg_id': 'cae52008-afae9cb50a4dcad2ed8bf964', 'msg_type': 'status', 402 | 'parent_header': {'msg_id': '98026e92-79cb3634ae3be2e314a81649', 403 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 404 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 405 | 6, 17, 15, 25, 25, 795737, tzinfo=tzutc()), 'version': '5.3'}, 406 | 'metadata': {}, 'content': {'execution_state': 'idle'}, 'buffers': 407 | []} 408 | 409 | 410 | 411 | <\unfolded-io> 412 | In [4]:\ 413 | <|unfolded-io> 414 | plt.show() 415 | <|unfolded-io> 416 | <\errput> 417 | {'header': {'msg_id': 'a6f20fa4-0355aff7852b0e6c2e3398f7', 418 | 'msg_type': 'status', 'username': 'mgubi', 'session': 419 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 420 | 6, 17, 15, 26, 26, 25110, tzinfo=tzutc()), 'version': '5.3'}, 421 | 'msg_id': 'a6f20fa4-0355aff7852b0e6c2e3398f7', 'msg_type': 'status', 422 | 'parent_header': {'msg_id': '18708dd2-156a60545c4e4a558e41a94b', 423 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 424 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 425 | 6, 17, 15, 26, 26, 24188, tzinfo=tzutc()), 'version': '5.3'}, 426 | 'metadata': {}, 'content': {'execution_state': 'busy'}, 'buffers': 427 | []} 428 | 429 | {'header': {'msg_id': '6dd7c1e9-f3851432473e78f0b78eb526', 430 | 'msg_type': 'execute_input', 'username': 'mgubi', 'session': 431 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 432 | 6, 17, 15, 26, 26, 25309, tzinfo=tzutc()), 'version': '5.3'}, 433 | 'msg_id': '6dd7c1e9-f3851432473e78f0b78eb526', 'msg_type': 434 | 'execute_input', 'parent_header': {'msg_id': 435 | '18708dd2-156a60545c4e4a558e41a94b', 'msg_type': 'execute_request', 436 | 'username': 'mgubi', 'session': 'c3870339-7d48ea325f778b60fba909c3', 437 | 'date': datetime.datetime(2019, 6, 17, 15, 26, 26, 24188, 438 | tzinfo=tzutc()), 'version': '5.3'}, 'metadata': {}, 'content': 439 | {'code': 'plt.show()', 'execution_count': 4}, 'buffers': []} 440 | 441 | {'header': {'msg_id': '3ccef453-df073af780174f259ea352fa', 442 | 'msg_type': 'stream', 'username': 'mgubi', 'session': 443 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 444 | 6, 17, 15, 26, 26, 26840, tzinfo=tzutc()), 'version': '5.3'}, 445 | 'msg_id': '3ccef453-df073af780174f259ea352fa', 'msg_type': 'stream', 446 | 'parent_header': {'msg_id': '18708dd2-156a60545c4e4a558e41a94b', 447 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 448 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 449 | 6, 17, 15, 26, 26, 24188, tzinfo=tzutc()), 'version': '5.3'}, 450 | 'metadata': {}, 'content': {'name': 'stderr', 'text': 451 | '/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:1: 452 | UserWarning: Matplotlib is currently using ps, which is a non-GUI 453 | backend, so cannot show the figure.\\n \ """Entry point for launching 454 | an IPython kernel.\\n'}, 'buffers': []} 455 | 456 | /usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:1: 457 | UserWarning: Matplotlib is currently using ps, which is a non-GUI 458 | backend, so cannot show the figure. 459 | 460 | \ \ """Entry point for launching an IPython kernel. 461 | 462 | {'header': {'msg_id': '0f250775-c5c51a8d7482cc11cd02c3ae', 463 | 'msg_type': 'status', 'username': 'mgubi', 'session': 464 | 'cc1ef1e9-4e10253ded3f8e0d64748264', 'date': datetime.datetime(2019, 465 | 6, 17, 15, 26, 26, 28512, tzinfo=tzutc()), 'version': '5.3'}, 466 | 'msg_id': '0f250775-c5c51a8d7482cc11cd02c3ae', 'msg_type': 'status', 467 | 'parent_header': {'msg_id': '18708dd2-156a60545c4e4a558e41a94b', 468 | 'msg_type': 'execute_request', 'username': 'mgubi', 'session': 469 | 'c3870339-7d48ea325f778b60fba909c3', 'date': datetime.datetime(2019, 470 | 6, 17, 15, 26, 26, 24188, tzinfo=tzutc()), 'version': '5.3'}, 471 | 'metadata': {}, 'content': {'execution_state': 'idle'}, 'buffers': 472 | []} 473 | 474 | 475 | 476 | <\unfolded-io> 477 | In [8]:\ 478 | <|unfolded-io> 479 | x = np.arange(0, 10, 0.2) 480 | 481 | y = np.sin(x) 482 | 483 | fig, ax = plt.subplots() 484 | 485 | ax.plot(x, y) 486 | 487 | plt.show() 488 | <|unfolded-io> 489 | \Figure size 432x288 with 1 Axes\ 490 | 491 | 492 | <\unfolded-io> 493 | In [3]:\ 494 | <|unfolded-io> 495 | _2 496 | <|unfolded-io> 497 | <\errput> 498 | [0;31m---------------------------------------------------------------------------[0m[0;31mNameError[0m 499 | \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Traceback 500 | (most recent call last)[0;32m\ipython-input-3-725b9f20c2d7\[0m 501 | in [0;36m\module\[0;34m[0m 502 | 503 | [0;32m----\ 1[0;31m [0m_2[0m[0;34m[0m[0;34m[0m[0m 504 | 505 | [0m[0;31mNameError[0m: name '_2' is not defined 506 | 507 | 508 | 509 | <\unfolded-io> 510 | In [4]:\ 511 | <|unfolded-io> 512 | _ 513 | <|unfolded-io> 514 | Out[4]: '' 515 | 516 | 517 | <\unfolded-io> 518 | In [5]:\ 519 | <|unfolded-io> 520 | ___ 521 | <|unfolded-io> 522 | Out[5]: '' 523 | 524 | 525 | <\unfolded-io> 526 | In [6]:\ 527 | <|unfolded-io> 528 | _i2 529 | <|unfolded-io> 530 | Out[6]: "fig = plt.figure() \ # an empty figure with no 531 | axes\\nfig.suptitle('No axes on this figure') \ # Add a title so we 532 | know which it is\\nfig, ax_lst = plt.subplots(2, 2) \ # a figure with a 533 | 2x2 grid of Axes" 534 | 535 | 536 | <\unfolded-io> 537 | In [7]:\ 538 | <|unfolded-io> 539 | _2 540 | <|unfolded-io> 541 | <\errput> 542 | [0;31m---------------------------------------------------------------------------[0m[0;31mNameError[0m 543 | \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Traceback 544 | (most recent call last)[0;32m\ipython-input-7-725b9f20c2d7\[0m 545 | in [0;36m\module\[0;34m[0m 546 | 547 | [0;32m----\ 1[0;31m [0m_2[0m[0;34m[0m[0;34m[0m[0m 548 | 549 | [0m[0;31mNameError[0m: name '_2' is not defined 550 | 551 | 552 | 553 | <\input> 554 | In [8]:\ 555 | <|input> 556 | \; 557 | 558 | 559 | 560 | 561 | <\initial> 562 | <\collection> 563 | 564 | 565 | 566 | 567 | -------------------------------------------------------------------------------- /jupyter_texmacs/__init__.py: -------------------------------------------------------------------------------- 1 | """Jupyter TeXmacs console""" 2 | 3 | from ._version import version_info, __version__ 4 | -------------------------------------------------------------------------------- /jupyter_texmacs/__main__.py: -------------------------------------------------------------------------------- 1 | from jupyter_texmacs import app 2 | 3 | if __name__ == '__main__': 4 | app.launch_new_instance() 5 | -------------------------------------------------------------------------------- /jupyter_texmacs/_version.py: -------------------------------------------------------------------------------- 1 | """ For beta/alpha/rc releases, the version number for a beta is X.Y.ZbN 2 | **without dots between the last 'micro' number and b**. N is the number of 3 | the beta released i.e. 1, 2, 3 ... 4 | 5 | See PEP 440 https://www.python.org/dev/peps/pep-0440/ 6 | """ 7 | 8 | version_info = (0, 1, 0, 'dev') 9 | 10 | __version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:]) 11 | -------------------------------------------------------------------------------- /jupyter_texmacs/app.py: -------------------------------------------------------------------------------- 1 | """ A minimal application using the ZMQ-based terminal IPython frontend. 2 | 3 | This is not a complete console app, as subprocess will not be able to receive 4 | input, there is no real readline support, among other limitations. 5 | """ 6 | 7 | # Copyright (c) Massimiliano Gubinelli. 8 | # Copyright (c) IPython Development Team. 9 | # Distributed under the terms of the Modified BSD License. 10 | 11 | from __future__ import print_function 12 | 13 | import logging 14 | import signal 15 | import sys 16 | 17 | from traitlets import ( 18 | Dict, Any 19 | ) 20 | from traitlets.config import catch_config_error, boolean_flag 21 | 22 | from jupyter_core.application import JupyterApp, base_aliases, base_flags, NoStart 23 | from jupyter_client.consoleapp import ( 24 | JupyterConsoleApp, app_aliases, app_flags, 25 | ) 26 | 27 | from jupyter_texmacs.tmshell import ZMQTerminalInteractiveShell 28 | from jupyter_texmacs import __version__ 29 | 30 | #----------------------------------------------------------------------------- 31 | # Globals 32 | #----------------------------------------------------------------------------- 33 | 34 | _examples = """ 35 | jupyter console # start the ZMQ-based console 36 | jupyter console --existing # connect to an existing ipython session 37 | """ 38 | 39 | #----------------------------------------------------------------------------- 40 | # Flags and Aliases 41 | #----------------------------------------------------------------------------- 42 | 43 | # copy flags from mixin: 44 | flags = dict(base_flags) 45 | # start with mixin frontend flags: 46 | frontend_flags = dict(app_flags) 47 | # update full dict with frontend flags: 48 | flags.update(frontend_flags) 49 | flags.update(boolean_flag( 50 | 'simple-prompt', 'ZMQTerminalInteractiveShell.simple_prompt', 51 | "Force simple minimal prompt using `raw_input`", 52 | "Use a rich interactive prompt with prompt_toolkit" 53 | )) 54 | 55 | # copy flags from mixin 56 | aliases = dict(base_aliases) 57 | # start with mixin frontend flags 58 | frontend_aliases = dict(app_aliases) 59 | # load updated frontend flags into full dict 60 | aliases.update(frontend_aliases) 61 | 62 | # get flags&aliases into sets, and remove a couple that 63 | # shouldn't be scrubbed from backend flags: 64 | frontend_aliases = set(frontend_aliases.keys()) 65 | frontend_flags = set(frontend_flags.keys()) 66 | 67 | 68 | #----------------------------------------------------------------------------- 69 | # Classes 70 | #----------------------------------------------------------------------------- 71 | 72 | 73 | class ZMQTerminalIPythonApp(JupyterApp, JupyterConsoleApp): 74 | name = "jupyter-console" 75 | version = __version__ 76 | """Start a terminal frontend to the IPython zmq kernel.""" 77 | 78 | description = """ 79 | The Jupyter terminal-based Console. 80 | 81 | This launches a Console application inside a terminal. 82 | 83 | The Console supports various extra features beyond the traditional 84 | single-process Terminal IPython shell, such as connecting to an 85 | existing ipython session, via: 86 | 87 | jupyter console --existing 88 | 89 | where the previous session could have been created by another ipython 90 | console, an ipython qtconsole, or by opening an ipython notebook. 91 | 92 | """ 93 | examples = _examples 94 | 95 | classes = [ZMQTerminalInteractiveShell] + JupyterConsoleApp.classes 96 | flags = Dict(flags) 97 | aliases = Dict(aliases) 98 | frontend_aliases = Any(frontend_aliases) 99 | frontend_flags = Any(frontend_flags) 100 | 101 | subcommands = Dict() 102 | 103 | force_interact = True 104 | 105 | def parse_command_line(self, argv=None): 106 | super(ZMQTerminalIPythonApp, self).parse_command_line(argv) 107 | self.build_kernel_argv(self.extra_args) 108 | 109 | def init_shell(self): 110 | JupyterConsoleApp.initialize(self) 111 | # relay sigint to kernel 112 | signal.signal(signal.SIGINT, self.handle_sigint) 113 | self.shell = ZMQTerminalInteractiveShell.instance(parent=self, 114 | manager=self.kernel_manager, 115 | client=self.kernel_client, 116 | confirm_exit=self.confirm_exit, 117 | ) 118 | self.shell.own_kernel = not self.existing 119 | 120 | def init_gui_pylab(self): 121 | # no-op, because we don't want to import matplotlib in the frontend. 122 | pass 123 | 124 | def handle_sigint(self, *args): 125 | if self.shell._executing: 126 | if self.kernel_manager: 127 | self.kernel_manager.interrupt_kernel() 128 | else: 129 | print("ERROR: Cannot interrupt kernels we didn't start.", 130 | file = sys.stderr) 131 | else: 132 | # raise the KeyboardInterrupt if we aren't waiting for execution, 133 | # so that the interact loop advances, and prompt is redrawn, etc. 134 | raise KeyboardInterrupt 135 | 136 | @catch_config_error 137 | def initialize(self, argv=None): 138 | """Do actions after construct, but before starting the app.""" 139 | super(ZMQTerminalIPythonApp, self).initialize(argv) 140 | if self._dispatching: 141 | return 142 | # create the shell 143 | self.init_shell() 144 | # and draw the banner 145 | self.init_banner() 146 | 147 | def init_banner(self): 148 | """optionally display the banner""" 149 | self.shell.show_banner() 150 | # Make sure there is a space below the banner. 151 | #if self.log_level <= logging.INFO: print() 152 | 153 | def start(self): 154 | # JupyterApp.start dispatches on NoStart 155 | super(ZMQTerminalIPythonApp, self).start() 156 | self.log.debug("Starting the jupyter console mainloop...") 157 | self.shell.mainloop() 158 | 159 | 160 | main = launch_new_instance = ZMQTerminalIPythonApp.launch_instance 161 | 162 | 163 | if __name__ == '__main__': 164 | main() 165 | 166 | -------------------------------------------------------------------------------- /jupyter_texmacs/completer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Adapt readline completer interface to make ZMQ request.""" 3 | 4 | # Copyright (c) IPython Development Team. 5 | # Distributed under the terms of the Modified BSD License. 6 | 7 | try: 8 | from queue import Empty # Py 3 9 | except ImportError: 10 | from Queue import Empty # Py 2 11 | 12 | from traitlets.config import Configurable 13 | from traitlets import Float 14 | 15 | class ZMQCompleter(Configurable): 16 | """Client-side completion machinery. 17 | 18 | How it works: self.complete will be called multiple times, with 19 | state=0,1,2,... When state=0 it should compute ALL the completion matches, 20 | and then return them for each value of state.""" 21 | 22 | timeout = Float(5.0, config=True, help='timeout before completion abort') 23 | 24 | def __init__(self, shell, client, config=None): 25 | super(ZMQCompleter,self).__init__(config=config) 26 | 27 | self.shell = shell 28 | self.client = client 29 | self.matches = [] 30 | 31 | def complete_request(self, code, cursor_pos): 32 | # send completion request to kernel 33 | # Give the kernel up to 5s to respond 34 | msg_id = self.client.complete( 35 | code=code, 36 | cursor_pos=cursor_pos, 37 | ) 38 | 39 | msg = self.client.shell_channel.get_msg(timeout=self.timeout) 40 | if msg['parent_header']['msg_id'] == msg_id: 41 | return msg['content'] 42 | 43 | return {'matches': [], 'cursor_start': 0, 'cursor_end': 0, 44 | 'metadata': {}, 'status': 'ok'} 45 | 46 | -------------------------------------------------------------------------------- /jupyter_texmacs/protocol.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ############################################################################### 3 | ## 4 | ## MODULE : protocol.py 5 | ## DESCRIPTION : The TeXmacs plugin protocol impl 6 | ## COPYRIGHT : (C) 2004 Ero Carrera, ero@dkbza.org 7 | ## (C) 2012 Adrian Soto 8 | ## (C) 2014 Miguel de Benito Delgado, mdbenito@texmacs.org 9 | ## (C) 2019 Darcy Shen 10 | ## (C) 2021 Jeroen Wouters 11 | ## 12 | ## This software falls under the GNU general public license version 3 or later. 13 | ## It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE 14 | ## in the root directory or . 15 | 16 | import os 17 | import re 18 | 19 | DATA_BEGIN = chr(2) 20 | DATA_END = chr(5) 21 | DATA_ESCAPE = chr(27) 22 | DATA_COMMAND = chr(16) 23 | 24 | TM_DEBUG = False 25 | 26 | def data_begin(): 27 | """Signal the beginning of data to TeXmacs.""" 28 | os.sys.stdout.write(DATA_BEGIN) 29 | 30 | 31 | def data_end(): 32 | """Signal the end of data to TeXmacs.""" 33 | os.sys.stdout.write(DATA_END) 34 | os.sys.stdout.flush() 35 | 36 | 37 | def texmacs_escape(data): 38 | return data.replace(DATA_ESCAPE.encode(), (DATA_ESCAPE + DATA_ESCAPE).encode()) \ 39 | .replace(DATA_BEGIN.encode(), (DATA_ESCAPE + DATA_BEGIN).encode()) \ 40 | .replace(DATA_END.encode(), (DATA_ESCAPE + DATA_END).encode()) 41 | 42 | def filter_ansi(text): 43 | # FIlter out ansi color codes 44 | # TODO: use regex matches to substitute with TeXmacs Scheme tags and flush_scheme 45 | # 46 | # https://en.wikipedia.org/wiki/ANSI_escape_code#Colors 47 | # CSI n m sets the appearance of the characters with n the SGR param 48 | # where 49 | # CSI is 'ESC [' 50 | # and 51 | # SGR param 30 to 37 select the foreground color 52 | # SGR code 1 is bold 53 | # SGR code 0 resets the appearance 54 | 55 | # match opening \x1B\[ followed by 30-37, 0, 1 or 01 one or more times seperated by ; and closed by m. Use matching groups to retrieve the codes. 56 | ansi_color_re = re.compile(r'\x1B\[(3[0-7]|0|0?1|4|22|24|39)(?:;(3[0-7]|0|0?1|4|22|24|39))*m') 57 | # Julia help also uses [4m (underline), [22m (normal intensity), [24m (not underlined), [39m (default foreground color), 58 | return ansi_color_re.sub('', text) 59 | 60 | def flush_any (out_str): 61 | """Feed data back to TeXmacs. 62 | 63 | Output results back to TeXmacs, with the DATA_BEGIN, 64 | DATA_END control characters.""" 65 | data_begin() 66 | os.sys.stdout.write(filter_ansi(out_str)) 67 | data_end() 68 | 69 | def flush_verbatim(content): 70 | flush_any ("verbatim:" + content) 71 | 72 | def flush_latex(content): 73 | flush_any ("latex:" + content) 74 | 75 | def flush_prompt(prompt): 76 | flush_any ("prompt#" + prompt) 77 | 78 | def flush_command(command): 79 | flush_any ("command:" + command) 80 | 81 | def flush_scheme(scheme): 82 | flush_any ("scheme:" + scheme) 83 | 84 | def flush_file(path): 85 | flush_any ("file:" + path) 86 | 87 | def flush_ps(content): 88 | flush_any ("ps:" + content) 89 | 90 | def flush_texmacs(content): 91 | flush_any ("texmacs:" + content) 92 | 93 | def flush_err(content): 94 | os.sys.stderr.write(DATA_BEGIN) 95 | os.sys.stderr.write("verbatim:" + filter_ansi(content)) 96 | os.sys.stderr.write(DATA_END) 97 | os.sys.stderr.flush() 98 | 99 | def flush_debug(content): 100 | if TM_DEBUG: 101 | flush_err(content) 102 | -------------------------------------------------------------------------------- /jupyter_texmacs/tmshell.py: -------------------------------------------------------------------------------- 1 | """IPython terminal interface using TeXmacs protocol""" 2 | 3 | from __future__ import print_function 4 | 5 | import base64 6 | import binascii 7 | import uuid 8 | import errno 9 | from getpass import getpass 10 | from io import BytesIO 11 | import os 12 | import signal 13 | import subprocess 14 | import sys 15 | import time 16 | from warnings import warn 17 | 18 | try: 19 | from queue import Empty # Py 3 20 | except ImportError: 21 | from Queue import Empty # Py 2 22 | 23 | from zmq import ZMQError 24 | from IPython.core import page 25 | from ipython_genutils.py3compat import cast_unicode_py2, input 26 | from ipython_genutils.tempdir import NamedFileInTemporaryDirectory 27 | from traitlets import (Bool, Integer, Float, Unicode, List, Dict, Enum, 28 | Instance, Any) 29 | from traitlets.config import SingletonConfigurable 30 | 31 | from .completer import ZMQCompleter 32 | from .zmqhistory import ZMQHistoryManager 33 | from . import __version__ 34 | 35 | from .protocol import * 36 | 37 | def as_scm_string (text): 38 | return '"%s"' % text.replace('\\', '\\\\').replace('"', '\\"') 39 | 40 | py_ver = sys.version_info[0] 41 | if py_ver == 3: 42 | _input = input 43 | else: 44 | _input = raw_input 45 | 46 | ### From tmpy/completion.py 47 | def from_scm_string(s): 48 | if len(s) > 2 and s[0] == '"' and s[-1] == '"': 49 | return s[1:-1] 50 | return s 51 | 52 | def parse_complete_command(s): 53 | """HACK""" 54 | t1 = s.strip().strip('()').split(' ', 1) 55 | t2 = t1[1].rsplit(' ', 1) 56 | # Don't use strip('"') in case there are several double quotes 57 | return [t1[0], from_scm_string(t2[0]), int(t2[1])] 58 | ### 59 | 60 | 61 | class ZMQTerminalInteractiveShell(SingletonConfigurable): 62 | readline_use = False 63 | 64 | pt_cli = None 65 | 66 | _executing = False 67 | _execution_state = Unicode('') 68 | _pending_clearoutput = False 69 | _eventloop = None 70 | own_kernel = False # Changed by ZMQTerminalIPythonApp 71 | 72 | history_load_length = Integer(1000, config=True, 73 | help="How many history items to load into memory" 74 | ) 75 | 76 | banner = Unicode('Jupyter console {version}\n\n{kernel_banner}', config=True, 77 | help=("Text to display before the first prompt. Will be formatted with " 78 | "variables {version} and {kernel_banner}.") 79 | ) 80 | 81 | kernel_timeout = Float(60, config=True, 82 | help="""Timeout for giving up on a kernel (in seconds). 83 | 84 | On first connect and restart, the console tests whether the 85 | kernel is running and responsive by sending kernel_info_requests. 86 | This sets the timeout in seconds for how long the kernel can take 87 | before being presumed dead. 88 | """ 89 | ) 90 | 91 | image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'), 92 | 'PIL', config=True, allow_none=True, help= 93 | """ 94 | Handler for image type output. This is useful, for example, 95 | when connecting to the kernel in which pylab inline backend is 96 | activated. There are four handlers defined. 'PIL': Use 97 | Python Imaging Library to popup image; 'stream': Use an 98 | external program to show the image. Image will be fed into 99 | the STDIN of the program. You will need to configure 100 | `stream_image_handler`; 'tempfile': Use an external program to 101 | show the image. Image will be saved in a temporally file and 102 | the program is called with the temporally file. You will need 103 | to configure `tempfile_image_handler`; 'callable': You can set 104 | any Python callable which is called with the image data. You 105 | will need to configure `callable_image_handler`. 106 | """ 107 | ) 108 | 109 | stream_image_handler = List(config=True, help= 110 | """ 111 | Command to invoke an image viewer program when you are using 112 | 'stream' image handler. This option is a list of string where 113 | the first element is the command itself and reminders are the 114 | options for the command. Raw image data is given as STDIN to 115 | the program. 116 | """ 117 | ) 118 | 119 | tempfile_image_handler = List(config=True, help= 120 | """ 121 | Command to invoke an image viewer program when you are using 122 | 'tempfile' image handler. This option is a list of string 123 | where the first element is the command itself and reminders 124 | are the options for the command. You can use {file} and 125 | {format} in the string to represent the location of the 126 | generated image file and image format. 127 | """ 128 | ) 129 | 130 | callable_image_handler = Any(config=True, help= 131 | """ 132 | Callable object called via 'callable' image handler with one 133 | argument, `data`, which is `msg["content"]["data"]` where 134 | `msg` is the message from iopub channel. For example, you can 135 | find base64 encoded PNG data as `data['image/png']`. If your function 136 | can't handle the data supplied, it should return `False` to indicate 137 | this. 138 | """ 139 | ) 140 | 141 | mime_preference = List( 142 | default_value=['application/pdf', 'image/eps', 'image/ps', 'image/png', 143 | 'image/jpeg', 'image/svg+xml', 'text/html', 'text/markdown'], 144 | config=True, help= 145 | """ 146 | Preferred object representation MIME type in order. First 147 | matched MIME type will be used. 148 | """ 149 | ) 150 | 151 | use_kernel_is_complete = Bool(True, config=True, 152 | help="""Whether to use the kernel's is_complete message 153 | handling. If False, then the frontend will use its 154 | own is_complete handler. 155 | """ 156 | ) 157 | 158 | kernel_is_complete_timeout = Float(1, config=True, 159 | help="""Timeout (in seconds) for giving up on a kernel's is_complete 160 | response. 161 | 162 | If the kernel does not respond at any point within this time, 163 | the kernel will no longer be asked if code is complete, and the 164 | console will default to the built-in is_complete test. 165 | """ 166 | ) 167 | 168 | # This is configurable on JupyterConsoleApp; this copy is not configurable 169 | # to avoid a duplicate config option. 170 | confirm_exit = Bool(True, 171 | help="""Set to display confirmation dialog on exit. 172 | You can always use 'exit' or 'quit', to force a 173 | direct exit without any confirmation. 174 | """ 175 | ) 176 | 177 | manager = Instance('jupyter_client.KernelManager', allow_none=True) 178 | client = Instance('jupyter_client.KernelClient', allow_none=True) 179 | 180 | def _client_changed(self, name, old, new): 181 | self.session_id = new.session.session 182 | session_id = Unicode() 183 | 184 | def _banner1_default(self): 185 | return "Jupyter Console {version}\n".format(version=__version__) 186 | 187 | simple_prompt = Bool(False, 188 | help="""Use simple fallback prompt. Features may be limited.""" 189 | ).tag(config=True) 190 | 191 | def __init__(self, **kwargs): 192 | # This is where traits with a config_key argument are updated 193 | # from the values on config. 194 | super(ZMQTerminalInteractiveShell, self).__init__(**kwargs) 195 | self.configurables = [self] 196 | 197 | self.init_history() 198 | self.init_completer() 199 | 200 | self.init_kernel_info() 201 | self.init_prompt_toolkit_cli() 202 | self.keep_running = True 203 | self.execution_count = 1 204 | 205 | def init_completer(self): 206 | """Initialize the completion machinery. 207 | 208 | This creates completion machinery that can be used by client code, 209 | either interactively in-process (typically triggered by the readline 210 | library), programmatically (such as in test suites) or out-of-process 211 | (typically over the network by remote frontends). 212 | """ 213 | self.Completer = ZMQCompleter(self, self.client, config=self.config) 214 | 215 | def init_history(self): 216 | """Sets up the command history. """ 217 | self.history_manager = ZMQHistoryManager(client=self.client) 218 | self.configurables.append(self.history_manager) 219 | 220 | def get_prompt_tokens(self): 221 | return [ 222 | (Token.Prompt, 'In ['), 223 | (Token.PromptNum, str(self.execution_count)), 224 | (Token.Prompt, ']: '), 225 | ] 226 | 227 | def get_continuation_tokens(self, width): 228 | return [ 229 | (Token.Prompt, (' ' * (width - 2)) + ': '), 230 | ] 231 | 232 | def get_out_prompt_tokens(self): 233 | return [ 234 | (Token.OutPrompt, 'Out['), 235 | (Token.OutPromptNum, str(self.execution_count)), 236 | (Token.OutPrompt, ']: ') 237 | ] 238 | 239 | def print_out_prompt(self): 240 | tokens = self.get_out_prompt_tokens() 241 | print_formatted_text(PygmentsTokens(tokens), end='', 242 | style = self.pt_cli.app.style) 243 | 244 | kernel_info = {} 245 | 246 | def init_kernel_info(self): 247 | """Wait for a kernel to be ready, and store kernel info""" 248 | timeout = self.kernel_timeout 249 | tic = time.time() 250 | self.client.hb_channel.unpause() 251 | msg_id = self.client.kernel_info() 252 | while True: 253 | try: 254 | reply = self.client.get_shell_msg(timeout=1) 255 | except Empty: 256 | if (time.time() - tic) > timeout: 257 | raise RuntimeError("Kernel didn't respond to kernel_info_request") 258 | else: 259 | if reply['parent_header'].get('msg_id') == msg_id: 260 | self.kernel_info = reply['content'] 261 | return 262 | 263 | def show_banner(self): 264 | flush_verbatim (self.banner.format(version=__version__, 265 | kernel_banner=self.kernel_info.get('banner', ''))) 266 | 267 | def print_out_prompt(self): 268 | flush_verbatim ('Out[%d]: ' % self.execution_count) 269 | 270 | def init_prompt_toolkit_cli(self): 271 | # Pre-populate history from IPython's history database 272 | history = [] 273 | last_cell = u"" 274 | for _, _, cell in self.history_manager.get_tail(self.history_load_length, 275 | include_latest=True): 276 | # Ignore blank lines and consecutive duplicates 277 | cell = cast_unicode_py2(cell.rstrip()) 278 | if cell and (cell != last_cell): 279 | history.append(cell) 280 | 281 | def check_complete(self, code): 282 | if self.use_kernel_is_complete: 283 | msg_id = self.client.is_complete(code) 284 | try: 285 | return self.handle_is_complete_reply(msg_id, 286 | timeout=self.kernel_is_complete_timeout) 287 | except SyntaxError: 288 | return False, "" 289 | else: 290 | lines = code.splitlines() 291 | if len(lines): 292 | more = (lines[-1] != "") 293 | return more, "" 294 | else: 295 | return False, "" 296 | 297 | def ask_exit(self): 298 | self.keep_running = False 299 | 300 | # This is set from payloads in handle_execute_reply 301 | next_input = None 302 | 303 | def mainloop(self): 304 | self.keepkernel = not self.own_kernel 305 | 306 | # Main session loop. 307 | while self.keep_running: 308 | flush_prompt ('In [%d]: ' % self.execution_count) 309 | line = _input () 310 | if not line: 311 | continue 312 | if line[0] == DATA_COMMAND: 313 | # TODO: handle completion in multiline input by adding a :commander 314 | # in the plugin configuration (similar to :serializer) and parse its output here 315 | sf = parse_complete_command(line[1:]) 316 | if sf[0] == 'complete': 317 | msg_id = self.client.complete(sf[1],sf[2]) 318 | # wait for the complete reply 319 | while self.client.is_alive(): 320 | try: 321 | self.handle_complete_reply(msg_id, sf[1], timeout=0.05) 322 | except Empty: 323 | pass 324 | else: 325 | break 326 | continue 327 | lines = [line] 328 | while line != "": 329 | line = _input () 330 | if line == '': 331 | continue 332 | lines.append(line) 333 | code = '\n'.join(lines[:-1]) 334 | self.run_cell(code, store_history=True) 335 | 336 | if self._eventloop: 337 | self._eventloop.close() 338 | if self.keepkernel and not self.own_kernel: 339 | flush_verbatim ('keeping kernel alive') 340 | elif self.keepkernel and self.own_kernel: 341 | flush_verbatim ("owning kernel, cannot keep it alive") 342 | self.client.shutdown() 343 | else: 344 | flush_verbatim ("Shutting down kernel") 345 | self.client.shutdown() 346 | 347 | def run_cell(self, cell, store_history=True): 348 | """Run a complete IPython cell. 349 | 350 | Parameters 351 | ---------- 352 | cell : str 353 | The code (including IPython code such as %magic functions) to run. 354 | store_history : bool 355 | If True, the raw and translated cell will be stored in IPython's 356 | history. For user code calling back into IPython's machinery, this 357 | should be set to False. 358 | """ 359 | if (not cell) or cell.isspace(): 360 | # pressing enter flushes any pending display 361 | self.handle_iopub() 362 | return 363 | 364 | # flush stale replies, which could have been ignored, due to missed heartbeats 365 | while self.client.shell_channel.msg_ready(): 366 | self.client.shell_channel.get_msg() 367 | # execute takes 'hidden', which is the inverse of store_hist 368 | msg_id = self.client.execute(cell, not store_history) 369 | 370 | # start reply to TeXmacs 371 | sys.stdout.write(DATA_BEGIN + "verbatim:") 372 | sys.stdout.flush() 373 | 374 | # first thing is wait for any side effects (output, stdin, etc.) 375 | self._executing = True 376 | self._execution_state = "busy" 377 | while self._execution_state != 'idle' and self.client.is_alive(): 378 | try: 379 | self.handle_input_request(msg_id, timeout=0.05) 380 | except Empty: 381 | # display intermediate print statements, etc. 382 | self.handle_iopub(msg_id) 383 | except ZMQError as e: 384 | # Carry on if polling was interrupted by a signal 385 | if e.errno != errno.EINTR: 386 | raise 387 | 388 | # after all of that is done, wait for the execute reply 389 | while self.client.is_alive(): 390 | try: 391 | self.handle_execute_reply(msg_id, timeout=0.05) 392 | except Empty: 393 | pass 394 | else: 395 | break 396 | self._executing = False 397 | 398 | # end reply to TeXmacs 399 | sys.stdout.write(DATA_END) 400 | sys.stdout.flush() 401 | 402 | 403 | #----------------- 404 | # message handlers 405 | #----------------- 406 | 407 | def handle_execute_reply(self, msg_id, timeout=None): 408 | msg = self.client.shell_channel.get_msg(block=False, timeout=timeout) 409 | if msg["parent_header"].get("msg_id", None) == msg_id: 410 | 411 | self.handle_iopub(msg_id) 412 | 413 | content = msg["content"] 414 | status = content['status'] 415 | 416 | if status == 'aborted': 417 | self.write('Aborted\n') ##FIXME: What to do with this? 418 | return 419 | elif status == 'ok': 420 | # handle payloads 421 | for item in content.get("payload", []): 422 | source = item['source'] 423 | if source == 'page': 424 | page.page(item['data']['text/plain']) 425 | elif source == 'set_next_input': 426 | self.next_input = item['text'] 427 | elif source == 'ask_exit': 428 | self.keepkernel = item.get('keepkernel', False) 429 | self.ask_exit() 430 | 431 | elif status == 'error': 432 | pass 433 | 434 | self.execution_count = int(content["execution_count"] + 1) 435 | 436 | def handle_complete_reply(self, msg_id, code=None, timeout=None): 437 | msg = self.client.shell_channel.get_msg(block=False, timeout=timeout) 438 | if msg["parent_header"].get("msg_id", None) == msg_id: 439 | status = msg["content"].get("status", None) 440 | matches = msg["content"].get("matches", None) 441 | cursor_start = msg["content"].get("cursor_start", None) 442 | cursor_end= msg["content"].get("cursor_end", None) 443 | if status == 'ok': 444 | # Jupyter sends autocompletion with a prefix, e.g. '%alias' to complete 'a' 445 | # TeXmacs can't handle these (?) 446 | matches = [m[(cursor_end-cursor_start):] for m in matches if m.startswith(code[cursor_start:cursor_end])] 447 | code = "\"" + code[cursor_start:cursor_end] + "\"" 448 | # TODO: handle cases where cursor_start == cursor_end, e.g. when completing 'my_var.' 449 | # Jupyter indicates the text to be replaced by the completion, while TeXmacs expects a non-empty root 450 | matches = ' '.join(["\"" + m + "\"" for m in matches]) 451 | flush_scheme("(tuple " + code + " " + matches + ")") 452 | 453 | def handle_is_complete_reply(self, msg_id, timeout=None): 454 | """ 455 | Wait for a repsonse from the kernel, and return two values: 456 | more? - (boolean) should the frontend ask for more input 457 | indent - an indent string to prefix the input 458 | Overloaded methods may want to examine the comeplete source. Its is 459 | in the self._source_lines_buffered list. 460 | """ 461 | ## Get the is_complete response: 462 | msg = None 463 | try: 464 | msg = self.client.shell_channel.get_msg(block=True, timeout=timeout) 465 | except Empty: 466 | warn('The kernel did not respond to an is_complete_request. ' 467 | 'Setting `use_kernel_is_complete` to False.') 468 | self.use_kernel_is_complete = False 469 | return False, "" 470 | ## Handle response: 471 | if msg["parent_header"].get("msg_id", None) != msg_id: 472 | warn('The kernel did not respond properly to an is_complete_request: %s.' % str(msg)) 473 | return False, "" 474 | else: 475 | status = msg["content"].get("status", None) 476 | indent = msg["content"].get("indent", "") 477 | ## Return more? and indent string 478 | if status == "complete": 479 | return False, indent 480 | elif status == "incomplete": 481 | return True, indent 482 | elif status == "invalid": 483 | raise SyntaxError() 484 | elif status == "unknown": 485 | return False, indent 486 | else: 487 | warn('The kernel sent an invalid is_complete_reply status: "%s".' % status) 488 | return False, indent 489 | 490 | include_other_output = Bool(False, config=True, 491 | help="""Whether to include output from clients 492 | other than this one sharing the same kernel. 493 | 494 | Outputs are not displayed until enter is pressed. 495 | """ 496 | ) 497 | other_output_prefix = Unicode("[remote] ", config=True, 498 | help="""Prefix to add to outputs coming from clients other than this one. 499 | 500 | Only relevant if include_other_output is True. 501 | """ 502 | ) 503 | 504 | def from_here(self, msg): 505 | """Return whether a message is from this session""" 506 | return msg['parent_header'].get("session", self.session_id) == self.session_id 507 | 508 | def include_output(self, msg): 509 | """Return whether we should include a given output message""" 510 | from_here = self.from_here(msg) 511 | if msg['msg_type'] == 'execute_input': 512 | # only echo inputs not from here 513 | return self.include_other_output and not from_here 514 | 515 | if self.include_other_output: 516 | return True 517 | else: 518 | return from_here 519 | 520 | def handle_iopub(self, msg_id=''): 521 | """Process messages on the IOPub channel 522 | 523 | This method consumes and processes messages on the IOPub channel, 524 | such as stdout, stderr, execute_result and status. 525 | 526 | It only displays output that is caused by this session. 527 | """ 528 | while self.client.iopub_channel.msg_ready(): 529 | sub_msg = self.client.iopub_channel.get_msg() 530 | msg_type = sub_msg['header']['msg_type'] 531 | parent = sub_msg["parent_header"] 532 | 533 | ## log the full messages to TeXmacs (for debugging only) 534 | flush_debug("--- IOPub message ---\n") 535 | flush_debug(str(sub_msg) + "\n") 536 | 537 | # Update execution_count in case it changed in another session 538 | if msg_type == "execute_input": 539 | self.execution_count = int(sub_msg["content"]["execution_count"]) + 1 540 | 541 | if self.include_output(sub_msg): 542 | if msg_type == 'status': 543 | self._execution_state = sub_msg["content"]["execution_state"] 544 | elif msg_type == 'stream': 545 | if sub_msg["content"]["name"] == "stdout": 546 | if self._pending_clearoutput: 547 | flush_verbatim ("\r") 548 | self._pending_clearoutput = False 549 | flush_verbatim (sub_msg["content"]["text"]) 550 | sys.stdout.flush() 551 | elif sub_msg["content"]["name"] == "stderr": 552 | if self._pending_clearoutput: 553 | flush_err ("\r") 554 | self._pending_clearoutput = False 555 | flush_err (sub_msg["content"]["text"]) 556 | sys.stderr.flush() 557 | elif msg_type == 'execute_result': 558 | if self._pending_clearoutput: 559 | flush_verbatim ("\r") 560 | self._pending_clearoutput = False 561 | self.execution_count = int(sub_msg["content"]["execution_count"]) 562 | if not self.from_here(sub_msg): 563 | flush_verbatim (self.other_output_prefix) 564 | format_dict = sub_msg["content"]["data"] 565 | self.handle_rich_data(format_dict) 566 | 567 | if 'text/latex' in format_dict: 568 | flush_command ('(tmju-open-help %s)' % (as_scm_string(format_dict['text/latex']),)) 569 | continue 570 | elif 'text/html' in format_dict: 571 | flush_any("html:" + format_dict['text/html']) 572 | continue 573 | elif 'text/plain' not in format_dict: 574 | continue 575 | 576 | # prompt_toolkit writes the prompt at a slightly lower level, 577 | # so flush streams first to ensure correct ordering. 578 | sys.stdout.flush() 579 | sys.stderr.flush() 580 | self.print_out_prompt() 581 | text_repr = format_dict['text/plain'] 582 | if '\n' in text_repr: 583 | # For multi-line results, start a new line after prompt 584 | flush_verbatim ('\n') 585 | flush_verbatim (text_repr) 586 | 587 | elif msg_type == 'display_data': 588 | data = sub_msg["content"]["data"] 589 | handled = self.handle_rich_data(data) 590 | if not handled: 591 | if not self.from_here(sub_msg): 592 | sys.stdout.write(self.other_output_prefix) 593 | # if it was an image, we handled it by now 594 | if 'text/plain' in data: 595 | flush_verbatim (data['text/plain']) 596 | 597 | elif msg_type == 'execute_input': 598 | content = sub_msg['content'] 599 | if not self.from_here(sub_msg): 600 | flush_verbatim (self.other_output_prefix) 601 | flush_verbatim ('In [{}]: '.format(content['execution_count'])) 602 | flush_verbatim (content['code'] + '\n') 603 | 604 | elif msg_type == 'clear_output': 605 | if sub_msg["content"]["wait"]: 606 | self._pending_clearoutput = True 607 | else: 608 | flush_verbatim ("\r") 609 | 610 | elif msg_type == 'error': 611 | for frame in sub_msg["content"]["traceback"]: 612 | flush_err (frame + "\n") 613 | 614 | _imagemime = { 615 | 'application/pdf': 'pdf', 616 | 'image/eps': 'eps', 617 | 'image/ps': 'ps', 618 | 'image/png': 'png', 619 | 'image/jpeg': 'jpeg', 620 | 'image/svg+xml': 'svg', 621 | } 622 | 623 | def handle_rich_data(self, data): 624 | flush_debug("---handle_rich_data---\n") 625 | for k,v in data.items(): 626 | flush_debug(k + ":" + v + "\n") 627 | 628 | for mime in self.mime_preference: 629 | if mime in data and mime in self._imagemime: 630 | if self.handle_image(data, mime): 631 | return True 632 | return False 633 | 634 | def handle_image(self, data, mime): 635 | imageformat = self._imagemime[mime] 636 | if (mime == 'image/svg+xml') or (mime == 'image/ps') or (mime == 'image/eps'): 637 | raw = data[mime].encode("utf-8") 638 | else: 639 | raw = base64.decodebytes(data[mime].encode("ascii")) 640 | flush_texmacs("|jupyter-output-" + str(uuid.uuid1()) + "." + imageformat + ">|0.618par|||>" ) 643 | return True 644 | 645 | def handle_input_request(self, msg_id, timeout=0.1): 646 | """ Method to capture raw_input 647 | """ 648 | req = self.client.stdin_channel.get_msg(timeout=timeout) 649 | # in case any iopub came while we were waiting: 650 | self.handle_iopub(msg_id) 651 | if msg_id == req["parent_header"].get("msg_id"): 652 | # wrap SIGINT handler 653 | real_handler = signal.getsignal(signal.SIGINT) 654 | 655 | def double_int(sig, frame): 656 | # call real handler (forwards sigint to kernel), 657 | # then raise local interrupt, stopping local raw_input 658 | real_handler(sig, frame) 659 | raise KeyboardInterrupt 660 | signal.signal(signal.SIGINT, double_int) 661 | content = req['content'] 662 | read = getpass if content.get('password', False) else input 663 | try: 664 | raw_data = read(content["prompt"]) 665 | except EOFError: 666 | # turn EOFError into EOF character 667 | raw_data = '\x04' 668 | except KeyboardInterrupt: 669 | sys.stdout.write('\n') 670 | return 671 | finally: 672 | # restore SIGINT handler 673 | signal.signal(signal.SIGINT, real_handler) 674 | 675 | # only send stdin reply if there *was not* another request 676 | # or execution finished while we were reading. 677 | if not (self.client.stdin_channel.msg_ready() or 678 | self.client.shell_channel.msg_ready()): 679 | self.client.input(raw_data) 680 | -------------------------------------------------------------------------------- /jupyter_texmacs/zmqhistory.py: -------------------------------------------------------------------------------- 1 | """ ZMQ Kernel History accessor and manager. """ 2 | #----------------------------------------------------------------------------- 3 | # Copyright (C) 2010-2011 The IPython Development Team. 4 | # 5 | # Distributed under the terms of the BSD License. 6 | # 7 | # The full license is in the file COPYING.txt, distributed with this software. 8 | #----------------------------------------------------------------------------- 9 | 10 | #----------------------------------------------------------------------------- 11 | # Imports 12 | #----------------------------------------------------------------------------- 13 | 14 | from IPython.core.history import HistoryAccessorBase 15 | from traitlets import Dict, List 16 | 17 | try: 18 | from queue import Empty # Py 3 19 | except ImportError: 20 | from Queue import Empty # Py 2 21 | 22 | class ZMQHistoryManager(HistoryAccessorBase): 23 | """History accessor and manager for ZMQ-based kernels""" 24 | input_hist_parsed = List([""]) 25 | output_hist = Dict() 26 | dir_hist = List() 27 | output_hist_reprs = Dict() 28 | 29 | def __init__(self, client): 30 | """ 31 | Class to load the command-line history from a ZMQ-based kernel, 32 | and access the history. 33 | 34 | Parameters 35 | ---------- 36 | 37 | client: `IPython.kernel.KernelClient` 38 | The kernel client in order to request the history. 39 | """ 40 | self.client = client 41 | 42 | def _load_history(self, raw=True, output=False, hist_access_type='range', 43 | **kwargs): 44 | """ 45 | Load the history over ZMQ from the kernel. Wraps the history 46 | messaging with loop to wait to get history results. 47 | """ 48 | history = [] 49 | if hasattr(self.client, "history"): 50 | ## In tests, KernelClient may not have a history method 51 | msg_id = self.client.history(raw=raw, output=output, 52 | hist_access_type=hist_access_type, 53 | **kwargs) 54 | while True: 55 | try: 56 | reply = self.client.get_shell_msg(timeout=1) 57 | except Empty: 58 | break 59 | else: 60 | if reply['parent_header'].get('msg_id') == msg_id: 61 | history = reply['content'].get('history', []) 62 | break 63 | return history 64 | 65 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): 66 | return self._load_history(hist_access_type='tail', n=n, raw=raw, 67 | output=output) 68 | 69 | def search(self, pattern="*", raw=True, search_raw=True, 70 | output=False, n=None, unique=False): 71 | return self._load_history(hist_access_type='search', pattern=pattern, 72 | raw=raw, search_raw=search_raw, 73 | output=output, n=n, unique=unique) 74 | 75 | def get_range(self, session, start=1, stop=None, raw=True,output=False): 76 | return self._load_history(hist_access_type='range', raw=raw, 77 | output=output, start=start, stop=stop, 78 | session=session) 79 | 80 | def get_range_by_str(self, rangestr, raw=True, output=False): 81 | return self._load_history(hist_access_type='range', raw=raw, 82 | output=output, rangestr=rangestr) 83 | 84 | def end_session(self): 85 | """ 86 | Nothing to do for ZMQ-based histories. 87 | """ 88 | pass 89 | 90 | def reset(self, new_session=True): 91 | """ 92 | Nothing to do for ZMQ-based histories. 93 | """ 94 | pass 95 | 96 | -------------------------------------------------------------------------------- /progs/init-jupyter.scm: -------------------------------------------------------------------------------- 1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2 | ;; 3 | ;; MODULE : init-jupyter.scm 4 | ;; DESCRIPTION : Initialize Jupyter plugin 5 | ;; COPYRIGHT : (C) 2019 Massimiliano Gubinelli 6 | ;; COPYRIGHT : (C) 2021 Jeroen Wouters 7 | ;; 8 | ;; This software falls under the GNU general public license version 3 or later. 9 | ;; It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE 10 | ;; in the root directory or . 11 | ;; 12 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 13 | 14 | (define (jupyter-serialize lan t) 15 | (with u (pre-serialize lan t) 16 | (with s (texmacs->code (stree->tree u) "SourceCode") 17 | (string-append s "\n\n")))) 18 | 19 | (define (jupyter-launcher kernel) 20 | (let ((script (if (os-mingw?) "tm_jupyter.bat" "tm_jupyter") )) 21 | (if (!= kernel "") (string-append script " --kernel=" kernel) 22 | script))) 23 | 24 | (define (jupyter-launchers) 25 | (map (lambda (u) `(:launch ,u ,(jupyter-launcher u))) 26 | (filter (lambda (k) (!= "" k)) 27 | (string-split (eval-system "tm_kernelspecs") #\newline) 28 | ) 29 | ) 30 | ) 31 | 32 | (plugin-configure jupyter 33 | (:require (url-exists-in-path? "python")) 34 | (:require (url-exists-in-path? "tm_jupyter")) 35 | (:require (url-exists-in-path? "tm_kernelspecs")) 36 | (:launch ,(jupyter-launcher "")) 37 | ,@(jupyter-launchers) 38 | (:serializer ,jupyter-serialize) 39 | (:session "Jupyter") 40 | (:tab-completion #t)) 41 | 42 | (when (supports-jupyter?) 43 | (import-from (jupyter-widgets))) 44 | -------------------------------------------------------------------------------- /progs/jupyter-widgets.scm: -------------------------------------------------------------------------------- 1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2 | ;; 3 | ;; MODULE : jupyter-widgets.scm 4 | ;; DESCRIPTION : Widgets for the jupyter plugin 5 | ;; COPYRIGHT : (C) 2014 Miguel de Benito Delgado 6 | ;; 7 | ;; This software falls under the GNU general public license version 3 or later. 8 | ;; It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE 9 | ;; in the root directory or . 10 | ;; 11 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 12 | 13 | (texmacs-module (jupyter-widgets) 14 | (:use (kernel gui menu-define) (kernel gui menu-widget))) 15 | 16 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 17 | ;; A very simple help widget. 18 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 19 | 20 | (define tmju-help-latex "No object selected") 21 | 22 | (define (tmju-help-content) 23 | `(with "bg-color" "#fdfdfd" 24 | (document 25 | (with "ornament-shape" "rounded" "ornament-color" "pastel yellow" 26 | "par-left" "2em" "par-right" "2em" 27 | (ornamented 28 | (document 29 | (concat (htab "5mm") (large (strong "Description")) 30 | (htab "5mm"))))) 31 | (with "par-left" "1em" "par-right" "1em" ; ignored, why? 32 | ,tmju-help-latex)))) 33 | 34 | (tm-widget (tmju-help-widget close) 35 | (resize ("400px" "800px" "4000px") ("300px" "600px" "4000px") 36 | (refreshable "tmju-help-widget-content" 37 | (scrollable 38 | (texmacs-output 39 | (stree->tree (tmju-help-content)) 40 | '(style "generic")))) 41 | (bottom-buttons >>> ("Close" (close))))) 42 | 43 | (tm-define (tmju-open-help text) 44 | (set! tmju-help-latex 45 | (tree-ref (convert text "latex-document" "texmacs-tree") 2 0)) 46 | ;(acons "verbatim->texmacs:wrap" "on" '()))) 47 | (dialogue-window tmju-help-widget noop "Jupyters help")) 48 | 49 | --------------------------------------------------------------------------------