├── .gitignore ├── .travis.yml ├── Cask ├── LICENSE ├── Makefile ├── README.org ├── emacs_sage_shell.py ├── emacs_sage_shell_test.py ├── emacs_sage_shell_view.py ├── images ├── ac.png ├── helm.png ├── helm1.png └── indent.png ├── sage-shell-blocks.el ├── sage-shell-mode.el ├── sage-shell-view.el └── test └── sage-shell-mode-test.el /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | /.ropeproject/* 3 | /sage-shell-autoloads.el 4 | /.cask/ 5 | *.pyc 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: false 3 | before_install: 4 | - export PATH="/home/travis/.evm/bin:$PATH" 5 | - export PATH="/home/travis/.cask/bin:$PATH" 6 | - git clone https://github.com/rejeep/evm.git /home/travis/.evm 7 | - evm config path /tmp 8 | - evm install emacs-24.4-travis --use --skip 9 | - curl -fsSkL https://raw.github.com/cask/cask/master/go | python 10 | # - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh 11 | # - evm install $EVM_EMACS --use --skip 12 | - cask 13 | env: 14 | - EVM_EMACS=emacs-24.4-travis 15 | - EVM_EMACS=emacs-24.5-travis 16 | - EVM_EMACS=emacs-25.1-travis 17 | - EVM_EMACS=emacs-25.2-travis 18 | script: 19 | - emacs --version 20 | - make test 21 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | ;; -*- mode: emacs-lisp -*- 2 | (source gnu) 3 | (source melpa) 4 | 5 | (package-file "sage-shell-mode.el") 6 | (depends-on "cl-lib") 7 | (depends-on "deferred") 8 | (depends-on "let-alist") 9 | (development 10 | (depends-on "ert")) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EMACS ?= emacs 2 | CASK ?= cask 3 | 4 | message: 5 | @echo "\nThis make file is provided for testing purposes only using Cask." 6 | @echo "To use sage-shell-mode from this clone, simply add the following" 7 | @echo "to your Emacs init file:" 8 | @echo "\n(add-to-list 'load-path \"$(PWD)\")\n(require 'sage-shell-mode)\n" 9 | 10 | test-compile: 11 | $(CASK) exec $(EMACS) -Q -eval "(progn (setq byte-compile-delete-errors nil) (setq byte-compile-error-on-warn t) (add-to-list 'load-path \"$(PWD)\"))" \ 12 | -batch -f batch-byte-compile sage-shell-mode.el sage-shell-blocks.el sage-shell-view.el 13 | 14 | test: clean test-compile 15 | $(CASK) exec $(EMACS) -Q -batch -L . -l test/sage-shell-mode-test.el -f ert-run-tests-batch-and-exit 16 | 17 | clean: 18 | rm -f *.elc 19 | 20 | .PHONY: message test-compile test clean 21 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * sage-shell-mode 2 | [[http://melpa.org/#/sage-shell-mode][file:http://melpa.org/packages/sage-shell-mode-badge.svg]] 3 | [[http://stable.melpa.org/#/sage-shell-mode][file:http://stable.melpa.org/packages/sage-shell-mode-badge.svg]] 4 | [[https://travis-ci.org/sagemath/sage-shell-mode][https://travis-ci.org/sagemath/sage-shell-mode.svg]] 5 | 6 | ** Overview 7 | 8 | =sage-shell-mode= is an elisp package and provides an Emacs front 9 | end for [[http://www.sagemath.org/][Sage]]. 10 | 11 | =sage-shell-mode= provides two main features: 12 | 13 | 1. =sage-shell-mode= to run the Sage terminal inside Emacs. 14 | 15 | 1. =sage-shell:sage-mode= for editing =.sage= source files and sending their contents directly to the Sage terminal. This major mode is derived from =python-mode=. 16 | 17 | The package supports extensions with [[https://github.com/stakemori/auto-complete-sage][auto-complete-sage]], [[https://github.com/stakemori/helm-sage][helm-sage]], 18 | [[https://github.com/stakemori/anything-sage][anything-sage]] and [[https://github.com/stakemori/ob-sagemath][ob-sagemath]]. 19 | 20 | ** Table of Contents :TOC: 21 | - [[#requirements][Requirements]] 22 | - [[#installation-and-setup][Installation and Setup]] 23 | - [[#aliases][Aliases]] 24 | - [[#basic-usage][Basic Usage]] 25 | - [[#input-history][Input History]] 26 | - [[#emulating-worksheets-code-blocks][Emulating Worksheets: code blocks]] 27 | - [[#inline-display-of-latex-outputs-and-plots-a-port-of-sage-view][Inline display of LaTeX outputs and plots (a port of =sage-view=)]] 28 | - [[#sagetex][SageTeX]] 29 | - [[#customization][Customization]] 30 | - [[#extensions][Extensions]] 31 | - [[#screenshots][Screenshots]] 32 | - [[#workaround-for-flycheck][Workaround for Flycheck]] 33 | - [[#license][License]] 34 | 35 | ** Requirements 36 | 37 | 1. GNU Emacs 24.4 later. 38 | 39 | 2. Local installation of [[http://www.sagemath.org/][Sage]]. 40 | 41 | 3. The Emacs package [[https://github.com/kiwanami/emacs-deferred][deferred]]. This will be installed automatically if you follow the guide below. 42 | 43 | ** Installation and Setup 44 | 45 | The most convenient is to use the Emacs package manager to install =sage-shell-mode= from [[https://github.com/milkypostman/melpa.git][MELPA]]: 46 | 47 | 1. See http://melpa.org/#/getting-started if you do not have a 48 | configuration for MELPA. 49 | 50 | 2. Install =sage-shell-mode= by 51 | - =M-x package-refresh-contents= 52 | - =M-x package-install RET sage-shell-mode=. 53 | 54 | (Alternatively, you can use [[https://github.com/jwiegley/use-package][=use-package=]] (available from MELPA) for installation and setup). 55 | 56 | # 3. If you have Sage 7.3 or earlier, then add the following to your Emacs init file (=~/.emacs.d/init.el=): 57 | 58 | # #+BEGIN_SRC elisp 59 | # (setq sage-shell:use-prompt-toolkit nil) 60 | # #+END_SRC 61 | 62 | 3. Configuration. 63 | 64 | Sage internally uses ~IPython~ to manage user interaction at the console, ~Jupyter~ cell or, in our case, ~emacs~ buffer. Due to IPython evolution (governed /upstream/ Sage), this interaction has to be managed differently by =sage-shell-mode= according to the ~Ipython~ version available, itself depending on Sage's version. This configuration is governed by two customizable variables : 65 | 66 | * =sage-shell:use-prompt-toolkit=, which must be =t= for Sage versions >= 7.4.beta0 and < 9.2.beta8, and =nil= otherwise ; 67 | 68 | * =sage-shell:use-simple-prompt=, which must be =t= for Sage versions >= 9.2.beta9, and =nil= otherwise. 69 | 70 | (Note : Sage 9.2.beta8 is a lost cause ; complain fiercely, then upgrade to something newer...). 71 | 72 | The function =sage-shell:set-ipython-version= does this automatically, but takes a couple of seconds to execute at startup. Its use is controlled by the custom variable =sage-shell:set-ipython-version-on-startup= (=t= by default). 73 | 74 | Similarly, =sage-shell:check-ipython-version= (controlled by the custom variable =sage-shell:check-ipython-version-on-startup=) checks that your options are acceptable to your IPython version, and will yell at you (in the =*Messages*= buffer) if this is not the case. 75 | 76 | By default, both functions are enabled at startup. A (reckless) user of (say) Sage 9.2.beta10 could shave a couple seconds startup time by setting : 77 | 78 | #+begin_src elisp 79 | (custom-set-variables 80 | '(sage-shell:use-prompt-toolkit nil) 81 | '(sage-shell:use-simple-prompt t) 82 | '(sage-shell:set-ipython-version-on-startup nil) 83 | '(sage-shell:check-ipython-version-on-startup nil)) 84 | #+end_src 85 | 86 | in its =init= file (or in the =:init= clause of its =sage-shell-mode= =use-package= call). 87 | 88 | 6. You can now run Sage inside Emacs by =M-x sage-shell:run-sage=, if Emacs 89 | can find the executable file of Sage. 90 | 91 | If Emacs cannot find the executable file, add the following line to your Emacs init file: 92 | 93 | #+BEGIN_SRC elisp 94 | (setq sage-shell:sage-root "/path/to/sage/root_directory") 95 | #+END_SRC 96 | 97 | And replace =/path/to/sage_root_directory= by the root directory of 98 | Sage, i.e. =$SAGE_ROOT=. If you do not know the root directory of 99 | Sage, evaluate the following code in a Sage terminal: 100 | 101 | #+BEGIN_SRC python 102 | import os; print os.environ["SAGE_ROOT"] 103 | #+END_SRC 104 | 105 | Alternatively, instead of setting =sage-shell:sage-root=, you may 106 | set the variable =sage-shell:sage-executable=. 107 | 108 | #+BEGIN_SRC elisp 109 | (setq sage-shell:sage-executable "/path/to/sage/executable") 110 | #+END_SRC 111 | 112 | Here =/path/to/sage/executable= is the path of the executable file 113 | of Sage. This may be a symbolic link. 114 | 115 | *** Sample configuration 116 | Here is a sample configuration. 117 | 118 | #+BEGIN_SRC elisp 119 | ;; Run SageMath by M-x run-sage instead of M-x sage-shell:run-sage 120 | (sage-shell:define-alias) 121 | 122 | ;; Turn on eldoc-mode in Sage terminal and in Sage source files 123 | (add-hook 'sage-shell-mode-hook #'eldoc-mode) 124 | (add-hook 'sage-shell:sage-mode-hook #'eldoc-mode) 125 | #+END_SRC 126 | 127 | ** Aliases 128 | 129 | The official Emacs major mode for Sage used to be [[https://bitbucket.org/gvol/sage-mode/src][sage-mode]]. To avoid name conflicts 130 | with this package, =sage-shell-mode= prefixes all names with =sage-shell=. 131 | 132 | If you are not using =sage-mode= at all, you can define more convenient 133 | aliases for =sage-shell-mode= by adding the following to your Emacs init file 134 | after the line =(require 'sage-shell-mode)=: 135 | 136 | #+BEGIN_SRC elisp 137 | (sage-shell:define-alias) 138 | #+END_SRC 139 | 140 | The following aliases will be defined: 141 | 142 | | Original name | Alias | 143 | |---------------------------+----------------| 144 | | sage-shell:run-sage | run-sage | 145 | | sage-shell:run-new-sage | run-new-sage | 146 | | sage-shell:sage-mode | sage-mode | 147 | | sage-shell:sage-mode-map | sage-mode-map | 148 | | sage-shell:sage-mode-hook | sage-mode-hook | 149 | 150 | This means e.g. that you can do =M-x run-sage= to run Sage, instead of =M-x 151 | sage-shell:run-sage=. 152 | 153 | ** Basic Usage 154 | 155 | *** Running a Sage Process 156 | 157 | You can start a Sage process by =M-x sage-shell:run-sage=. If you need 158 | to open multiple Sage processes simultaneously, you can start new ones by 159 | =M-x sage-shell:run-new-sage=. You can restart the current process by 160 | =M-x sage-shell:restart-sage=. 161 | 162 | | Command | Alias | Description | 163 | |-------------------------+--------------+-----------------------------------| 164 | | sage-shell:run-sage | run-sage | Run a Sage process. | 165 | | sage-shell:run-new-sage | run-new-sage | Run another Sage process. | 166 | | sage-shell:restart-sage | None | Restart the current Sage process. | 167 | 168 | The major-mode of the Sage process buffer is =sage-shell-mode=. 169 | 170 | *** The Sage Process as a terminal 171 | 172 | The primary element of =sage-shell-mode= is interacting with the Sage process 173 | you just started. The Sage process buffer communicates directly with a Sage 174 | shell in the background and behaves very much like it. You just type and send 175 | the command with ==: 176 | 177 | 178 | #+BEGIN_SRC python 179 | sage: 2+2 180 | 4 181 | sage: (x^2 + 2*x + 1).factor() 182 | (x + 1)^2 183 | sage: 184 | #+END_SRC 185 | 186 | The buffer behaves like an Emacs shell: 187 | 188 | - =M-p= or =C-up= goes through earlier input. 189 | - Previous input and output is retained earlier in the buffer. You can move 190 | around just as usual and e.g. copy from it or search. 191 | - To exit, you can enter =quit= at the prompt or type =C-d= (bound to =sage-shell:delchar-or-maybe-eof=) at a blank line. 192 | 193 | The buffer also behaves much like the Sage terminal: 194 | 195 | **** Tab completion 196 | == at the prompt completes the current word. It understands all Sage and 197 | Python functions currently in scope, and it also completes attributes of 198 | objects. If there are multiple possibilities, they are presented in another 199 | window. 200 | 201 | #+BEGIN_SRC python 202 | sage: G = graphs.PetersenGraph() 203 | sage: G. 204 | 205 | sage: G.charp 206 | 207 | #+END_SRC 208 | 209 | By default, Tab completion uses =completion-at-point=. Alternatively, you can 210 | use =pcomplete= by adding the following to your Emacs init file: 211 | 212 | #+BEGIN_SRC elisp 213 | (setq sage-shell:completion-function 'pcomplete) 214 | #+END_SRC 215 | 216 | You can also use =auto-complete=, =anything= or =helm= for 217 | completion. This requires installing those extensions, see [[#extensions][Extensions]]. 218 | 219 | **** =?= Help 220 | 221 | By writing the name of an object at the prompt, followed by =?= and then =RET=, 222 | you are shown the documentation of that object: 223 | 224 | #+BEGIN_SRC python 225 | sage: G = graphs.PetersenGraph() 226 | sage: G.charpoly? 227 | 228 | #+END_SRC 229 | 230 | This is identical to running =C-c C-h= and then typing the name of the object. 231 | 232 | **** =??= Source Lookup 233 | 234 | If you use =??= instead =?= after a Sage object, then the *source code* for that object will be opened in a new buffer: 235 | 236 | #+BEGIN_SRC python 237 | sage: G = graphs.PetersenGraph() 238 | sage: G.charpoly?? 239 | 240 | #+END_SRC 241 | 242 | 243 | **** Most important key-bindings 244 | 245 | | Key Stroke | Command | Description | 246 | |------------+----------------------------------------------+----------------------------------------------------------------------------| 247 | | RET | sage-shell:send-input | Evaluate the expression written at the prompt. | 248 | | TAB | sage-shell-tab-command | Complete a partially written word or indent a line. | 249 | | C-d | sage-shell:delchar-or-maybe-eof | Delete the next input character. End the Sage process if nothing is input. | 250 | | C-c C-c | sage-shell:interrupt-subjob | Interrupt the current computation. | 251 | | M-p | comint-previous-input | Go backward through input history. | 252 | | M-n | sage-shell:next-input | Go forward through input history. | 253 | | C-c C-o | sage-shell:delete-output | Remove all output from Sage since last input prompt. | 254 | | C-c M-o | sage-shell:clear-current-buffer | Clear the entire Sage process buffer, leaving just the prompt. | 255 | | C-c C-l | sage-shell:load-file | Asks for a file and loads it into Sage | 256 | | C-c C-h | sage-shell:help | Ask for the name of a Sage object and show its documentation. | 257 | | ? RET | sage-shell-help::describe-symbol | Show the documentation of the object directly preceding the =?=. | 258 | | ?? RET | sage-shell:find-source-in-view-mode | Visits the source code of the object directly preceding the =??=. | 259 | | C-c o | sage-shell:list-outputs | List inputs and outputs in a buffer. | 260 | | C-c M-w | sage-shell:copy-previous-output-to-kill-ring | Copy the previous output to =kill-ring= | 261 | For more commands and key-bindings see the help using =M-x describe-mode 262 | sage-shell-mode=. 263 | 264 | *** Editing a Sage File 265 | 266 | When you visit a file with the suffix =.sage=, then 267 | =sage-shell:sage-mode= will be the major-mode of the buffer 268 | automatically. 269 | 270 | To switch to =sage-shell:sage-mode= on a =.py= file, run =M-x 271 | sage-shell:sage-mode=. To use =sage-shell:sage-mode= every time you visit 272 | that file, you can add the following magic comment at the first line of the 273 | file: 274 | 275 | #+BEGIN_SRC python 276 | # -*- mode: sage-shell:sage -*- 277 | #+END_SRC 278 | 279 | If you've activated [[#aliases][Aliases]] you can instead use the following magic comment: 280 | 281 | #+BEGIN_SRC python 282 | # -*- mode: sage -*- 283 | #+END_SRC 284 | 285 | The major mode =sage-shell:sage-mode= is almost the same as 286 | =python-mode=. The following new key-bindings are added: 287 | 288 | | Key | Command | Description | 289 | |---------+---------------------------------------+------------------------------------------------------------------| 290 | | C-c C-c | sage-shell-edit:send-buffer | Evaluate the contents of the current buffer in the Sage process. | 291 | | C-c C-r | sage-shell-edit:send-region | Evaluate the currently marked region in the Sage process. | 292 | | C-c C-j | sage-shell-edit:send-line* | Evaluate the current line in the Sage process. | 293 | | C-c C-l | sage-shell-edit:load-file | Load the current file in the Sage process. | 294 | | C-c C-z | sage-shell-edit:pop-to-process-buffer | Select the Sage process buffer. | 295 | 296 | If you run multiple Sage processes, use =M-x sage-shell:set-process-buffer= 297 | to change which one will be used for the above functions. 298 | 299 | ** Input History 300 | 301 | To save the history of input evaluated in a Sage process and use in future 302 | Sage process (using the =M-p= keybinding), add the following to your Emacs 303 | init file: 304 | 305 | #+BEGIN_SRC elisp 306 | (setq sage-shell:input-history-cache-file "~/.emacs.d/.sage_shell_input_history") 307 | #+END_SRC 308 | 309 | The file name in the above line is the path for storing the inputs and you can 310 | change it to what you prefer. 311 | 312 | ** Emulating Worksheets: code blocks 313 | 314 | Worksheets is a popular paradigm for structuring experiments in computer algebra systems, seen in Jupyter, the Sage Notebook, Maple and many other softwares. 315 | =sage-shell-mode= supports a lightweight type of this workflow using "code blocks". 316 | 317 | Essentially, you structure your source file in logical blocks of code, representing both your library code and your experiments. 318 | For instance: 319 | 320 | #+BEGIN_SRC python 321 | ### Implement the new algorithm 322 | def my_helper(a): 323 | return a*2 324 | 325 | def my_new_algorithm(x, y): 326 | return my_helper(x) + my_helper(y) 327 | 328 | 329 | ### Check the new algorithm on small input 330 | print my_new_algorithm(1, 2) 331 | 332 | ### Check the new algorithm on big input 333 | print my_new_algorithm(100, 300) 334 | 335 | ### Check that my algorithm is commutative using random input 336 | def my_random_number(): 337 | return randint(100, 200) 338 | 339 | a, b = my_random_number(), my_random_number() 340 | assert my_new_algorithm(a, b) == my_new_algorithm(b, a) 341 | 342 | #+END_SRC 343 | 344 | The blocks of code are logically delimited by lines starting with =###=. 345 | In this case =load(experiment.sage)= is not a good alternative to the way one works with the Jupyter Notebook: rather, you want to evaluate the code block by block. 346 | You also want to be able to modifying an earlier or later block, run that, and then return to the block in the middle, etc. 347 | 348 | =sage-shell-mode= comes with a small set of functions for accommodating this. In =sage-shell:sage-mode=, the following functions are provided: 349 | 350 | | Key | Command | Description | 351 | |------------+--------------------------------+-------------------------------------------------------------------| 352 | | C-M-{ | sage-shell-blocks:backward | Move backward one block, i.e. to previous =###= delimiter. | 353 | | C-M-} | sage-shell-blocks:forward | Move forward one block, i.e. to next =###= delimiter. | 354 | | C- | sage-shell-blocks:send-current | Send the block that the point is currently in to the Sage process | 355 | 356 | In the Sage process buffer, the following functions are provided: 357 | 358 | | Key | Command | Description | 359 | |------------+-----------------------------+------------------------------------------------------------| 360 | | C- | sage-shell-blocks:pull-next | Take the block from the last visited =sage-shell:sage-mode= buffer and send to the Sage process. | 361 | 362 | As an example, if the point is in the body of =my_new_algorithm=, then =C-= (or =M-x sage-shell-blocks:send-current=) would send the definitions of =my_helper= and =my_new_algorithm= to the Sage shell. Furthermore, it would print the "title" of the block: 363 | 364 | #+BEGIN_SRC python 365 | sage: load('/tmp/sage_shell_mode3946wC1/sage_shell_mode_temp.sage') 366 | --- Implement the new algorithm --- 367 | sage: 368 | #+END_SRC 369 | 370 | The delimiter =###= can be changed by =setq= the variable =sage-shell-blocs:delimiter=. 371 | 372 | ** Inline display of LaTeX outputs and plots (a port of =sage-view=) 373 | This feature is a port of [[https://bitbucket.org/gvol/sage-mode/src/44eb95cb6b27a05af68e11253649ddf8ae364a5b/emacs/sage-view.el?at=default&fileviewer=file-view-default][sage-view]]. This requires [[https://www.ctan.org/pkg/dvipng][dvipng]] and [[https://www.ctan.org/tex-archive/macros/latex/contrib/preview][preview.sty]]. 374 | 375 | To enable inline display of LaTeX outputs and plots in =sage-shell-mode= buffer, 376 | add the following code to your Emacs init file: 377 | 378 | #+BEGIN_SRC elisp 379 | ;; If you want to enable inline display of LaTeX outputs only, 380 | ;; uncomment the following line. 381 | ;; (setq sage-shell-view-default-commands 'output) 382 | 383 | ;; If you want to enable inline display of plots only, 384 | ;; uncomment the following line. 385 | ;; (setq sage-shell-view-default-commands 'plot) 386 | 387 | (add-hook 'sage-shell-after-prompt-hook #'sage-shell-view-mode) 388 | #+END_SRC 389 | 390 | You can use the following commands to enable/disable inline display of LaTeX outputs and plots. 391 | 392 | | Command | Description | 393 | |-----------------------------------------+---------------------------------------------------------------------------| 394 | | =sage-shell-view-toggle-inline-output= | Toggle inline display of LaTeX outputs while SageMath process is running. | 395 | | =sage-shell-view-toggle-inline-plots= | Toggle inline display of plots while SageMath process is running. | 396 | 397 | The following table shows some of customizable variables for =sage-shell-view-mode=. 398 | For further customization, run =M-x customize-group RET sage-shell-view RET=. 399 | 400 | | Customizable variable | Description | Default value | 401 | | =sage-shell-view-default-commands= | If equal to the symbol =plots= then will start inline plotting. If equal to the symbol =output= then will start typesetting output. Otherwise, if non-nil will start both. | =t= | 402 | | =sage-shell-view-default-resolution= | Resolution used when converting from PDF to PNG. This value is passed to the =-r= option of the ghostscript command. | 125 | 403 | | =sage-shell-view-latex-foreground-color= | Foreground color used in LaTeX image as string (e.g. "black", "white", "#de935f") or =nil=. If it is =nil=, then the default foreground color will be used. | =nil= | 404 | | =sage-shell-view-latex-background-color= | Similar to =sage-shell-view-latex-foreground-color= for background color. | =nil= | 405 | 406 | ** SageTeX 407 | 408 | =sage-shell-mode= can be conveniently used when writing Sage-powered LaTeX files 409 | using [[https://github.com/dandrake/sagetex][SageTeX]]. 410 | 411 | *** TEXINPUTS 412 | 413 | When a Sage process is spawned by =sage-shell:run-sage= or 414 | =sage-shell:run-new-sage=, then =sage-shell-mode= adds 415 | =$SAGE_ROOT/local/share/texmf/tex/generic/sagetex/= to the 416 | environment variable =TEXINPUTS= in Emacs. If you do not want to 417 | change the environment variable, set 418 | =sage-shell-sagetex:add-to-texinputs-p= to =nil=. 419 | 420 | *** Commands for SageTeX 421 | 422 | Here is a list of commands for =SageTeX=. These commands load a 423 | =.sagetex.sage= file generated by =SageTeX= to the existing Sage 424 | process. 425 | 426 | | Command | Run =latex= before loading | Run =latex= after loading | 427 | |--------------------------------------------+----------------------------+---------------------------| 428 | | sage-shell-sagetex:load-file | No | No | 429 | | sage-shell-sagetex:run-latex-and-load-file | Yes | No | 430 | | sage-shell-sagetex:compile-file | Yes | Yes | 431 | 432 | There are similar commands to above, 433 | =sage-shell-sagetex:load-current-file=, 434 | =sage-shell-sagetex:run-latex-and-load-current-file= and 435 | =sage-shell-sagetex:compile-current-file=. 436 | 437 | Here is a sample setting for =AUCTeX= users. 438 | 439 | #+BEGIN_SRC elisp 440 | (eval-after-load "latex" 441 | '(mapc (lambda (key-cmd) (define-key LaTeX-mode-map (car key-cmd) (cdr key-cmd))) 442 | `((,(kbd "C-c s c") . sage-shell-sagetex:compile-current-file) 443 | (,(kbd "C-c s C") . sage-shell-sagetex:compile-file) 444 | (,(kbd "C-c s r") . sage-shell-sagetex:run-latex-and-load-current-file) 445 | (,(kbd "C-c s R") . sage-shell-sagetex:run-latex-and-load-file) 446 | (,(kbd "C-c s l") . sage-shell-sagetex:load-current-file) 447 | (,(kbd "C-c s L") . sage-shell-sagetex:load-file) 448 | (,(kbd "C-c C-z") . sage-shell-edit:pop-to-process-buffer)))) 449 | #+END_SRC 450 | 451 | For example, you can run =sage-shell-sagetex:compile-current-file= 452 | by =C-c s c= in a =LaTeX-mode= buffer with this setting. 453 | 454 | *** Customize the =latex= Command 455 | 456 | You can change a =latex= command used by 457 | =sage-shell-sagetex:compile-file= and 458 | =sage-shell-sagetex:compile-current-file= by setting 459 | =sage-shell-sagetex:latex-command= or 460 | =sage-shell-sagetex:auctex-command-name=. 461 | 462 | If you are an =AUCTeX= user, then customize 463 | =sage-shell-sagetex:auctex-command-name= to change the =latex= 464 | command. The value of =sage-shell-sagetex:auctex-command-name= 465 | should be a =name= of a command in =TeX-command-list= (i.e =car= of 466 | an element of the list =TeX-command-list=), e.g.: 467 | 468 | #+BEGIN_SRC elisp 469 | (setq sage-shell-sagetex:auctex-command-name "LaTeX") 470 | #+END_SRC 471 | 472 | You can also use the variable =sage-shell-sagetex:latex-command= to 473 | change the =latex= command. For example, if you want to run 474 | =latexmk= after loading a =.sagetex.sage= file, then use the 475 | following setting: 476 | 477 | #+BEGIN_SRC elisp 478 | (setq sage-shell-sagetex:latex-command "latexmk") 479 | #+END_SRC 480 | 481 | The default value of =sage-shell-sagetex:latex-command= is =latex 482 | -interaction=nonstopmode=. If 483 | =sage-shell-sagetex:auctex-command-name= is =non-nil=, then the 484 | value of =sage-shell-sagetex:latex-command= is ignored. 485 | 486 | ** Customization 487 | 488 | To customize =sage-shell-mode=, =M-x customize-group RET sage-shell=, 489 | =M-x customize-group RET sage-shell-sagetex= or 490 | =M-x customize-group RET sage-shell-view=. 491 | 492 | 493 | ** Extensions 494 | 495 | - [[https://github.com/stakemori/auto-complete-sage][auto-complete-sage]] provides an [[https://github.com/auto-complete/auto-complete][auto-complete]] source for 496 | =sage-shell-mode=. 497 | - [[https://github.com/stakemori/helm-sage][helm-sage]] provides a [[https://github.com/emacs-helm/helm][helm]] source for =sage-shell-mode=. 498 | 499 | - [[https://github.com/stakemori/anything-sage][anything-sage]] provides an [[http://www.emacswiki.org/Anything][anything]] source for =sage-shell-mode=. 500 | 501 | - [[https://github.com/stakemori/ob-sagemath][ob-sagemath]] provides [[http://orgmode.org/worg/org-contrib/babel/][org-babel]] functions for Sage. 502 | 503 | ** Screenshots 504 | 505 | Automatic indentation and syntax highlighting work. 506 | 507 | #+CAPTION: alt text 508 | 509 | [[./images/indent.png]] 510 | 511 | Completion with [[https://github.com/stakemori/auto-complete-sage][auto-complete-sage]]. 512 | 513 | #+CAPTION: alt text 514 | 515 | [[./images/ac.png]] 516 | 517 | Completion with [[https://github.com/stakemori/helm-sage][helm-sage]]. 518 | 519 | #+CAPTION: alt text 520 | 521 | [[./images/helm.png]] 522 | #+CAPTION: alt text 523 | 524 | [[./images/helm1.png]] 525 | 526 | ** Workaround for Flycheck 527 | 528 | To use =flycheck-mode= in a =sage-shell:sage-mode= buffer and a 529 | =python-mode= buffer, try the following code. 530 | 531 | #+BEGIN_SRC elisp 532 | 533 | (dolist (ckr '(python-pylint python-flake8)) 534 | (flycheck-add-mode ckr 'sage-shell:sage-mode)) 535 | 536 | (defun sage-shell:flycheck-turn-on () 537 | "Enable flycheck-mode only in a file ended with py." 538 | (when (let ((bfn (buffer-file-name))) 539 | (and bfn (string-match (rx ".py" eol) bfn))) 540 | (flycheck-mode 1))) 541 | 542 | (add-hook 'python-mode-hook 'sage-shell:flycheck-turn-on) 543 | #+END_SRC 544 | 545 | ** License 546 | 547 | Licensed under the [[http://www.gnu.org/licenses/gpl.html][GPL]]. 548 | -------------------------------------------------------------------------------- /emacs_sage_shell.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 - 2018 Sho Takemori 2 | 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | 17 | import inspect 18 | import os 19 | import re 20 | import sys 21 | from pathlib import Path 22 | from contextlib import contextmanager 23 | 24 | import IPython 25 | from IPython.core.completerlib import module_completion 26 | from IPython import get_ipython 27 | 28 | import sage 29 | from sage.all import preparse 30 | 31 | try: 32 | ip = get_ipython() 33 | ip.autoindent = False 34 | except (NameError, AttributeError): # Older versions 35 | import IPython.ipapi 36 | ip = IPython.ipapi.get() 37 | ip.IP.shell.autoindent = False 38 | 39 | # Disable highlighting matching parentheses. 40 | try: 41 | if IPython.version_info[0] >= 5: 42 | sage.repl.interpreter.SageTerminalInteractiveShell.highlight_matching_brackets = False # noqa: E501 43 | except (NameError, AttributeError): 44 | pass 45 | 46 | # Disable color. 47 | ip.run_line_magic('colors', 'NoColor') 48 | 49 | # Disable the SQLite history. 50 | try: 51 | ip.run_line_magic("config", "HistoryManager.enabled = False") 52 | except Exception: 53 | pass 54 | 55 | interfaces = ip.ev('interfaces') 56 | 57 | _sage_const_regexp = re.compile("_sage_const_") 58 | 59 | 60 | class Memorize: 61 | 62 | def __init__(self, f) -> None: 63 | self._f = f 64 | self._cached: dict = {} 65 | 66 | def __call__(self, *args): 67 | if args in self._cached: 68 | return self._cached[args] 69 | 70 | res = self._f(*args) 71 | self._cached[args] = res 72 | return res 73 | 74 | 75 | memorize = Memorize 76 | 77 | 78 | def print_cpl_sexp(typs, compl_dct) -> None: 79 | def _to_lisp_str_ls(ls): 80 | return "(%s)" % " ".join(f'"{a}"' for a in ls) 81 | 82 | funcs = {"interface": all_commands, 83 | "attributes": all_attributes, 84 | "modules": all_modules, 85 | "vars-in-module": all_vars_in_module, 86 | "in-function-call": all_keyword_args} 87 | alst = [(tp, funcs[tp](compl_dct)) for tp in typs] 88 | conss = [f'("{tp}" . {_to_lisp_str_ls(ls)})' 89 | for tp, ls in alst if ls is not None] 90 | print("(" + "".join(conss) + ")") 91 | 92 | 93 | def all_modules(compl_dct: dict) -> list: 94 | try: 95 | module_name = compl_dct["module-name"] 96 | return _all_modules(module_name) 97 | except Exception: 98 | return [] 99 | 100 | 101 | def _all_modules(module_name) -> list[str]: 102 | if module_name is None: 103 | return list_modules_in_syspath() 104 | return [a.split(".")[-1] 105 | for a in module_completion(f"import {module_name}.")] 106 | 107 | 108 | def all_vars_in_module(compl_dct) -> list: 109 | try: 110 | module_name = compl_dct["module-name"] 111 | return _all_vars_in_module(module_name) 112 | except Exception: 113 | return [] 114 | 115 | 116 | special_att_regexp = re.compile("__[a-zA-Z0-9_]+__") 117 | 118 | 119 | def _all_vars_in_module(module_name) -> list: 120 | if module_name is None: 121 | return [] 122 | 123 | # If imported module, use dir. 124 | if module_name in sys.modules: 125 | res = dir(sys.modules[module_name]) 126 | else: 127 | try: 128 | p = resolve_module_path(module_name) 129 | except OSError: 130 | # If resolving fails, use module_completion. 131 | return module_completion(f"from {module_name} import ") 132 | res = None 133 | if p.is_dir(): 134 | res = list_modules_in(p) 135 | 136 | # Otherwise, parse the file. 137 | if res is None: 138 | res = [] 139 | regexp = re.compile( 140 | "^{name} *= *|^def +{name}|^class +{name}".format( 141 | name="([a-zA-Z0-9_]+)")) 142 | with p.open() as f: 143 | for line in f: 144 | m = regexp.match(line) 145 | if m is not None: 146 | res.extend([c for c in m.groups() if c is not None]) 147 | assert res is not None 148 | return [a for a in res if special_att_regexp.match(a) is None] 149 | 150 | 151 | def all_commands(compl_dct) -> list: 152 | interface = compl_dct["interface"] 153 | if interface == 'sage': 154 | liste = ip.ev('dir()') 155 | return [a for a in liste if _sage_const_regexp.match(a) is None] 156 | 157 | intfc = ip.ev(interface) 158 | if isinstance(intfc, sage.interfaces.expect.Expect): 159 | return sorted(_completions_attributes(interface)) 160 | 161 | return [] 162 | 163 | 164 | def all_attributes(compl_dct) -> list: 165 | varname = compl_dct["var-base-name"] 166 | try: 167 | regexp = re.compile("^[ a-zA-Z0-9._\\[\\]]+$") 168 | if regexp.match(varname) is None: 169 | return [] 170 | if varname in interfaces: 171 | ls = ip.ev(f'dir({varname})') 172 | else: 173 | ls = _completions_attributes(preparse(varname)) 174 | ls.extend(ip.ev(f'dir({preparse(varname)})')) 175 | ls = sorted(set(ls)) 176 | return ls 177 | except Exception: 178 | return [] 179 | 180 | 181 | def _completions_attributes(varname) -> list[str]: 182 | completions = ip.complete(f'{varname}.')[1] 183 | ln = len(varname) + 1 184 | return [a[ln:] for a in completions] 185 | 186 | 187 | def list_modules_in(p: Path) -> list[str]: 188 | res = (Path(a) for a in list_module_paths_in(p)) 189 | return [a.stem for a in res] 190 | 191 | 192 | def list_module_paths_in(p: Path) -> list: 193 | if not p.exists(): 194 | return [] 195 | 196 | if p.is_dir(): 197 | res = [] 198 | for f in p.iterdir(): 199 | a = is_module(f) 200 | if a: 201 | res.append(a) 202 | return res 203 | 204 | return [] 205 | 206 | 207 | mod_regexp = re.compile("^[A-Za-z0-9_.]+$") 208 | 209 | 210 | def is_module(p: Path): 211 | if not re.match(mod_regexp, p.name): 212 | return False 213 | 214 | if p.is_file(): 215 | if p.suffix == "py": 216 | return p 217 | 218 | if p.is_dir(): 219 | if (p / "__init__.py").exists(): 220 | return p 221 | 222 | 223 | @memorize 224 | def list_module_paths_in_syspath() -> list[Path]: 225 | res: list[Path] = [] 226 | for p in sys.path: 227 | res.extend(a for a in list_module_paths_in(Path(p))) 228 | return res 229 | 230 | 231 | def list_modules_in_syspath() -> list[str]: 232 | return module_completion("import ") 233 | 234 | 235 | @memorize 236 | def resolve_module_path(modname: str) -> Path: 237 | lmis = list_module_paths_in_syspath() 238 | root_mod_name = modname.split(".")[0] 239 | ls = [a for a in lmis if a.name == root_mod_name] 240 | if not ls: 241 | raise OSError("could not resolve module path") 242 | root_path = ls[0] 243 | pth = root_path.parent / Path(*modname.split(".")) 244 | if pth.is_dir(): 245 | return pth 246 | for ext in [".py", ".pyx"]: 247 | _pth = pth / ext 248 | if _pth.is_file(): 249 | return _pth 250 | raise ValueError("could not resolve module name") 251 | 252 | 253 | def lazy_import_get_obj(obj): 254 | _max = 10 255 | i = 0 256 | while (isinstance(obj, sage.misc.lazy_import.LazyImport) and 257 | i < _max): 258 | obj = obj._get_object() 259 | i += 1 260 | return obj 261 | 262 | 263 | def source_line(obj): 264 | return sage.misc.sageinspect.sage_getsourcelines(obj)[-1] 265 | 266 | 267 | def print_source_file_and_line_num(obj) -> None: 268 | obj = lazy_import_get_obj(obj) 269 | sf = sage.misc.sageinspect.sage_getfile(obj) 270 | sl = source_line(obj) 271 | print(sf, '*', sl) 272 | 273 | 274 | def print_source_line(obj) -> None: 275 | print(source_line(obj)) 276 | 277 | 278 | def print_sage_root() -> None: 279 | print(os.environ['SAGE_ROOT']) 280 | 281 | 282 | @contextmanager 283 | def current_dir(d: Path): 284 | cwd = Path.cwd() 285 | os.chdir(d) 286 | try: 287 | yield 288 | finally: 289 | os.chdir(cwd) 290 | 291 | 292 | def sage_tex_load(f: Path) -> None: 293 | d = f.expanduser().parent 294 | with current_dir(d): 295 | ip.ev(f'load("{f}")') 296 | 297 | 298 | def print_inputs_outputs(max_line_num, delim, reversed_ord) -> None: 299 | def show_func(s): 300 | if max_line_num is None: 301 | res = s 302 | else: 303 | ss = s.split("\n") 304 | if len(ss) > max_line_num: 305 | liste = ss[:max_line_num] + ["....."] 306 | else: 307 | liste = ss 308 | res = "\n".join(liste) 309 | if '\n' in res: 310 | return '\n' + res 311 | else: 312 | return res 313 | 314 | def format_func(obj): 315 | try: 316 | if hasattr(obj, "show"): 317 | return repr(obj) 318 | else: 319 | return ip.display_formatter.format(obj)[0]['text/plain'] 320 | except Exception: 321 | return repr(obj) 322 | 323 | outputs = ip.ev("_oh") 324 | if reversed_ord: 325 | def key_func(x): 326 | return -x[0] 327 | else: 328 | def key_func(x): 329 | return x 330 | outputs = sorted(outputs.items(), key=key_func) 331 | outputs = [(k, show_func(format_func(v))) for k, v in outputs] 332 | inputs = ip.ev("_ih") 333 | # TODO: Find a better way. 334 | regexp = re.compile( 335 | r'_emacs_sage_shell\.run_cell_and_print_msg_id\("_emacs_ob_sagemath\.run_cell_babel.+') 336 | for k, v in outputs: 337 | if regexp.match(inputs[k]) is None: 338 | print(f"In [{k}]: {inputs[k]}") 339 | print(f"Out[{k}]: {v}") 340 | print(delim) 341 | 342 | 343 | def _is_safe_str(s) -> bool: 344 | _func_call_reg = re.compile("[()]") 345 | return _func_call_reg.search(s) is None 346 | 347 | 348 | def print_info(name): 349 | run_cell(f"{name}?") 350 | 351 | 352 | ignore_classes = [sage.interfaces.gap.Gap, sage.misc.lazy_import.LazyImport] 353 | 354 | 355 | def _sage_getdef(name, base_name=None): 356 | try: 357 | if _is_safe_str(name) and (_should_be_ignored(name, base_name, clses=[]) 358 | is not None): 359 | gd_name = "sage.misc.sageinspect.sage_getdef" 360 | name_ob = ip.ev(preparse(name)) 361 | name_ob = lazy_import_get_obj(name_ob) 362 | if inspect.isclass(name_ob): 363 | df = ip.ev(f"{gd_name}({name}.__init__)") 364 | else: 365 | df = ip.ev(f"{gd_name}({preparse(name)})") 366 | return df 367 | except NameError: 368 | pass 369 | 370 | 371 | def sage_getdef(name, base_name=None) -> str: 372 | df = _sage_getdef(name, base_name=base_name) 373 | if df is not None: 374 | return f"{name}{df}" 375 | raise ValueError("name not found") 376 | 377 | 378 | _doc_delims = ["EXAMPLE", "EXAMPLES", "TESTS", "AUTHOR", "AUTHORS", 379 | "ALGORITHM"] 380 | 381 | _doc_delim_regexp = re.compile("|".join(_s + ":" for _s in _doc_delims)) 382 | 383 | 384 | def _should_be_ignored(name, base_name, clses=None): 385 | if clses is None: 386 | clses = ignore_classes 387 | if isinstance(base_name, str): 388 | base_ob = ip.ev(preparse(base_name)) 389 | if any(isinstance(base_ob, cls) for cls in clses): 390 | return None 391 | else: 392 | base_ob = None 393 | name_ob = ip.ev(preparse(name)) 394 | if any(isinstance(name_ob, cls) for cls in clses): 395 | return None 396 | else: 397 | return True 398 | 399 | 400 | def short_doc(name, base_name=None): 401 | ''' 402 | If name or base_name is an instance of one of ignore_classes, 403 | then this function returns None. 404 | ''' 405 | sd_name = "sage.misc.sageinspect.sage_getdoc" 406 | if _is_safe_str(name) and (_should_be_ignored(name, base_name) 407 | is not None): 408 | dc = ip.ev(f"{sd_name}({preparse(name)})") 409 | m = _doc_delim_regexp.search(dc) 410 | if m is not None: 411 | res = dc[:m.start()] 412 | else: 413 | res = dc 414 | return res.strip() 415 | 416 | 417 | def all_keyword_args(compl_dct) -> list: 418 | try: 419 | base_name = compl_dct["in-function-call-base-name"] 420 | name = compl_dct["in-function-call"] 421 | return keyword_args(name, base_name=base_name) 422 | except Exception: 423 | return [] 424 | 425 | 426 | def keyword_args(name, base_name=None) -> list[str]: 427 | gd = _sage_getdef(name, base_name=base_name) 428 | no_argspec_reg = re.compile(r"\[noargspec\]") 429 | if (not gd) or re.match(no_argspec_reg, gd): 430 | return [] 431 | 432 | args_str = gd[1:-1] 433 | reg = re.compile(r"\*+[a-zA-Z_0-9]+") 434 | args = args_str.split(", ") 435 | args = [a for a in args if reg.match(a) is None] 436 | reg = re.compile("[a-zA-Z_0-9]+") 437 | matches = (reg.match(a) for a in args) 438 | return [m.group() + "=" for m in matches if m] 439 | 440 | 441 | def print_short_doc(name, base_name=None) -> None: 442 | try: 443 | sd = short_doc(name, base_name=base_name) 444 | if sd is not None: 445 | print(sd) 446 | except Exception: 447 | pass 448 | 449 | 450 | def print_def(name, base_name=None) -> None: 451 | try: 452 | df = sage_getdef(name, base_name=base_name) 453 | if df is not None: 454 | print(df) 455 | except Exception: 456 | pass 457 | 458 | 459 | def print_short_doc_and_def(name, base_name=None) -> None: 460 | try: 461 | df = sage_getdef(name, base_name=base_name) 462 | if df is not None: 463 | print(df) 464 | except Exception: 465 | df = None 466 | 467 | try: 468 | sd = short_doc(name, base_name=base_name) 469 | if df is not None and sd is not None: 470 | print("") 471 | if sd is not None: 472 | print(sd) 473 | except Exception: 474 | pass 475 | 476 | 477 | def run_cell(code): 478 | res = ip.run_cell(code) 479 | ip.set_next_input("") 480 | return res 481 | 482 | 483 | def run_cell_and_print_state(code, msg_id_start, msg_id_end) -> None: 484 | print(msg_id_start) 485 | res = run_cell(code) 486 | if hasattr(res, 'success'): 487 | if res.success: 488 | print(0) 489 | else: 490 | print(1) 491 | else: 492 | # For old Sages. It always succeeds. 493 | print(0) 494 | print(msg_id_end) 495 | 496 | 497 | def run_cell_and_print_msg_id(code, msg_id_start, msg_id_end) -> None: 498 | print(msg_id_start) 499 | run_cell(code) 500 | print(msg_id_end) 501 | 502 | 503 | def read_file_and_run_cell(filename): 504 | with Path(filename).open() as fp: 505 | contents = fp.read() 506 | return ip.run_cell(contents) 507 | 508 | 509 | def read_file_and_run_contents(filename): 510 | read_file_and_run_cell(filename) 511 | -------------------------------------------------------------------------------- /emacs_sage_shell_test.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Currently this module does nothing. 3 | ''' 4 | -------------------------------------------------------------------------------- /emacs_sage_shell_view.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Emacs sage-shell-mode Backend for the Sage Rich Output System 3 | 4 | This module defines the Emacs backend for :mod:`sage.repl.rich_output` 5 | based on the IPython shell version. 6 | 7 | """ 8 | 9 | # Copyright (C) 2016 - 2018 Sho Takemori 10 | 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see . 23 | 24 | from emacs_sage_shell import ip 25 | from sage.repl.rich_output.output_basic import OutputLatex, OutputPlainText 26 | from sage.repl.rich_output.output_browser import OutputHtml 27 | 28 | from sage.repl.rich_output.output_catalog import OutputImagePng 29 | from sage.repl.rich_output.preferences import DisplayPreferences 30 | from sage.repl.rich_output.backend_ipython import BackendIPythonCommandline 31 | 32 | 33 | class BackendEmacs(BackendIPythonCommandline): 34 | 35 | def __init__(self, text=True, plot=True): 36 | super().__init__() 37 | if text: 38 | self.__text = "latex" 39 | else: 40 | self.__text = None 41 | self.__plot = plot 42 | 43 | def default_preferences(self): 44 | return DisplayPreferences(text=self.__text) 45 | 46 | def supported_output(self): 47 | return [OutputLatex, OutputPlainText, OutputHtml, OutputImagePng] 48 | 49 | def _repr_(self): 50 | return "Emacs babel" 51 | 52 | def displayhook(self, plain_text, rich_output): 53 | if self.__plot and isinstance(rich_output, OutputImagePng): 54 | msg = rich_output.png.filename(ext='png') 55 | msg = "BEGIN_PNG:%s:END_PNG" % msg 56 | return ({'text/plain': msg}, {}) 57 | 58 | if isinstance(rich_output, OutputHtml): 59 | text = "BEGIN_TEXT:" + str(plain_text.text.get(), 'utf-8') 60 | text += ":END_TEXTBEGIN_LATEX:" 61 | text += str(rich_output.latex.get(), 'utf-8') + ":END_LATEX" 62 | return ({'text/plain': text}, {}) 63 | 64 | return super().displayhook(plain_text, rich_output) 65 | 66 | 67 | def set_backend(text=True, plot=True): 68 | if text or plot: 69 | backend = BackendEmacs(text=text, plot=plot) 70 | else: 71 | backend = BackendIPythonCommandline() 72 | backend.get_display_manager().switch_backend(backend, shell=ip) 73 | -------------------------------------------------------------------------------- /images/ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/sage-shell-mode/4291700e981a2105d55fa56382ba25046d3d268d/images/ac.png -------------------------------------------------------------------------------- /images/helm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/sage-shell-mode/4291700e981a2105d55fa56382ba25046d3d268d/images/helm.png -------------------------------------------------------------------------------- /images/helm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/sage-shell-mode/4291700e981a2105d55fa56382ba25046d3d268d/images/helm1.png -------------------------------------------------------------------------------- /images/indent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/sage-shell-mode/4291700e981a2105d55fa56382ba25046d3d268d/images/indent.png -------------------------------------------------------------------------------- /sage-shell-blocks.el: -------------------------------------------------------------------------------- 1 | ;;; sage-shell-blocks.el --- Support for structuring Sage code in sheets 2 | 3 | ;; Copyright (C) 2013-2018 Johan Rosenkilde 4 | 5 | ;; Author: Johan Rosenkilde 6 | ;; URL: https://github.com/sagemath/sage-shell-mode 7 | ;; Keywords: Sage, math 8 | 9 | ;;; License 10 | ;; This program is free software; you can redistribute it and/or modify 11 | ;; it under the terms of the GNU General Public License as published by 12 | ;; the Free Software Foundation, either version 3 of the License, or 13 | ;; (at your option) any later version. 14 | 15 | ;; This program is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with this program. If not, see . 22 | 23 | ;;; Commentary: 24 | 25 | ;; This file adds functionality which supports structuring experimental Sage 26 | ;; code in "sheets", where the code is bundled in "blocks" akin to the boxes of 27 | ;; the Notebook. Functions are provided for convenient handling of such blocks. 28 | ;; The file injects a few keybindings into `sage-shell:sage-mode' as well as 29 | ;; `sage-shell-mode'. 30 | 31 | ;; A block is defined by a line beginning with `sage-shell-blocks:delimiter'. 32 | 33 | ;;; Code: 34 | (require 'sage-shell-mode) 35 | (defcustom sage-shell-blocks:delimiter "###" 36 | "Any line matching the regular expression `sage-shell-blocks:delimiter' at the 37 | beginning of the line is considered a start of a block. 38 | 39 | Note that '^' to match at the beginning of the line should not be added to 40 | `sage-shell-blocks:delimiter'. 41 | Strange behaviour might arise if `sage-shell-blocks:delimiter' matches 42 | multiple lines at a time." 43 | :type 'string 44 | :group 'sage-shell) 45 | 46 | (defcustom sage-shell-blocks:title-decorate " ---- " 47 | "When printing titles of blocks, put this decoration around the 48 | title for easy recognition" 49 | :type 'string 50 | :group 'sage-shell) 51 | 52 | ;; 53 | ;; Functionality for Sage source files 54 | ;; 55 | ;;;###autoload 56 | (defun sage-shell-blocks:backward (arg) 57 | "Move backwards to the last beginning of a block." 58 | (interactive "p") 59 | (if (< arg 0) 60 | (sage-shell-blocks:forward (- arg)) 61 | (while (and (> arg 0) 62 | (search-backward-regexp (concat "^" sage-shell-blocks:delimiter) nil 'move)) 63 | (setq arg (- arg 1))))) 64 | 65 | ;;;###autoload 66 | (defun sage-shell-blocks:forward (arg) 67 | "Move forwards to the next beginning of a block." 68 | (interactive "p") 69 | (if (< arg 0) 70 | (sage-shell-blocks:backward (- arg)) 71 | ;; If point is on a delimiter, we should skip this, so search from beginning of 72 | ;; next line (this will match immediately, if next line is a delimiter) 73 | (let ((re (concat "^" sage-shell-blocks:delimiter))) 74 | (when (looking-at re) 75 | (forward-line)) 76 | ;; search forward: if it worked, move to begin of delimiter, otherwise end of file 77 | (while (and (> arg 0) 78 | (search-forward-regexp re nil 'move)) 79 | (setq arg (- arg 1))) 80 | ;; We successfully found something so move to the beginning of the match 81 | (when (= arg 0) 82 | (goto-char (match-beginning 0)))))) 83 | 84 | ;;;###autoload 85 | (defun sage-shell-blocks:send-current () 86 | "Send the block that the point is currently in to the inferior shell. 87 | Move to end of block sent." 88 | (interactive) 89 | ;; Border-case: if we're standing on a delimiter, sage-shell-blocks:backward will go 90 | ;; to previous delimiter, but we should send from this delimiter and forwards. 91 | (sage-shell-blocks:forward 1) 92 | (let* ((this-buf (current-buffer)) 93 | (enddelim (point)) 94 | (backdelim (save-excursion 95 | (sage-shell-blocks:backward 1) 96 | (point))) 97 | title) 98 | ;; Copy the region to a temp buffer. 99 | ;; Possibly change the first line if it contains a title 100 | (with-temp-buffer 101 | (insert-buffer-substring this-buf backdelim enddelim) 102 | (goto-char (point-min)) 103 | (when (looking-at sage-shell-blocks:delimiter) 104 | (progn 105 | (goto-char (match-end 0)) 106 | (setq title (buffer-substring (point) 107 | (progn (end-of-line) (point)))) 108 | (when (string-match "^ *\\([^ ].*[^ ]\\) *$" title) 109 | (setq title (match-string 1 title))) 110 | (unless (equal title "") 111 | (insert (concat "\nprint(\"" sage-shell-blocks:title-decorate title sage-shell-blocks:title-decorate "\")"))))) 112 | (sage-shell-edit:send-region (point-min) (point-max))))) 113 | 114 | ;; Functionality for the inferior shell 115 | ;; 116 | ;;;###autoload 117 | (defun sage-shell-blocks:pull-next () 118 | "Evaluate the next block of the last visited file in Sage mode." 119 | (interactive) 120 | ;; Find the first buffer in buffer-list which is in sage-shell:sage-mode 121 | (let* ((lst (buffer-list)) 122 | (buf 123 | (catch 'break 124 | (while lst 125 | (if (with-current-buffer (car lst) (derived-mode-p 'sage-shell:sage-mode)) 126 | (throw 'break (car lst)) 127 | (setq lst (cdr lst))))))) 128 | (if buf 129 | (progn 130 | (switch-to-buffer-other-window buf) 131 | (sage-shell-blocks:send-current)) 132 | (error "No sage-shell:sage-mode buffer found")))) 133 | 134 | (provide 'sage-shell-blocks) 135 | 136 | ;;; sage-blocks.el ends here 137 | -------------------------------------------------------------------------------- /sage-shell-view.el: -------------------------------------------------------------------------------- 1 | ;;; sage-shell-view.el --- Typeset Sage output on the fly -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2008 ~ 2016 Matthias Meulien, Nick Alexander, 4 | ;; 2016 ~ 2018 Sho Takemori 5 | 6 | ;; Author: Sho Takemori 7 | ;; Keywords: sage, math, image 8 | ;; URL: https://github.com/sagemath/sage-shell-mode 9 | ;; Original authors: Matthias Meulien , Nick Alexander 10 | ;; 11 | 12 | ;;; License 13 | ;; This program is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation, either version 3 of the License, or 16 | ;; (at your option) any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | 28 | ;; This is a port of sage-view written by Matthias Meulien and Nick Alexander. 29 | 30 | ;; `sage-shell-view' typesets output in an `sage-shell-mode' buffer and displays 31 | ;; plots inline in an `sage-shell-mode'. Inline displays are context 32 | ;; sensitive; by default, right-clicking brings up a context-menu. 33 | 34 | ;; Use `sage-shell-view' to enable the minor mode, and then 35 | ;; `sage-shell-view-enable-inline-output', `sage-shell-view-disable-inline-output' and 36 | ;; `sage-shell-view-enable-inline-plots', `sage-shell-view-disable-inline-plots' enable 37 | ;; and disable the relevant features. You might add some of those functions 38 | ;; to `sage-shell-view-hook' to configure `sage-shell-view' to your liking. 39 | 40 | ;; You can customize `sage-shell-view' using the Emacs customize interface by M-x 41 | ;; customize-group RET sage-shell-view RET. In particular you can customize 42 | ;; magnification and margins 43 | 44 | ;; This mode was inspired by doc-view.el by Tassilo Horn, preview.el 45 | ;; by David Kastrup, and imath.el by Yasuaki Honda. 46 | 47 | ;; The LaTeX style used by preview.el is mandatory to use 48 | ;; sage-shell-view.el. It is shipped with AUCTeX. 49 | 50 | ;;; Code: 51 | 52 | ;;; Todo: 53 | ;; - Add a auto-reveal stuff to overlays 54 | ;; - Check that display is image capable 55 | ;; - Disabling sage-view mode should remove overlays 56 | ;; - Set color, center image, enlarge overlay to window full size 57 | ;; - Add zoom features to overlays 58 | ;; - Add horizontal scrolling 59 | 60 | ;; Bugs: 61 | ;; - Numpy output can be a text array... should not be inserted into 62 | ;; $$ signs (hum... example?) 63 | 64 | (require 'mouse) 65 | (require 'sage-shell-mode) 66 | (require 'deferred) 67 | 68 | (defgroup sage-shell-view nil "Typeset Sage output on the fly" 69 | :group 'sage-shell 70 | :prefix "sage-shell-view-") 71 | 72 | (defcustom sage-shell-view-dvipng-command "dvipng" 73 | "*dvipng command to convert from DVI to PNG." 74 | :type 'string 75 | :group 'sage-shell-view) 76 | 77 | (defcustom sage-shell-view-default-commands t 78 | "Determine what to enable when `sage-shell-view' is started. 79 | If equal to the symbol `plots' then will start inline plotting. 80 | If equal to the symbol `output' then will start typesetting output. 81 | Otherwise, if non-nil will start both. 82 | 83 | Each of these can be enabled or disabled later by calling 84 | `sage-shell-view-enable-inline-plots', `sage-shell-view-disable-inline-plots', 85 | `sage-shell-view-enable-inline-output', `sage-shell-view-disable-inline-output', 86 | `sage-shell-view-toggle-inline-plots' or 87 | `sage-shell-view-toggle-inline-output'." 88 | :type '(choice (const :tag "Inline Plots" plots) 89 | (const :tag "Typeset Output" output) 90 | (const :tag "Both" t)) 91 | :group 'sage-shell-view) 92 | 93 | (defcustom sage-shell-view-latex-preamble 94 | "\\usepackage{amstext} 95 | \\usepackage{amsmath} 96 | \\usepackage{amssymb} 97 | \\usepackage{amsfonts} 98 | \\usepackage{graphicx} 99 | \\usepackage{mathrsfs} 100 | \\usepackage[utf8]{inputenc} 101 | \\usepackage[T1]{fontenc} 102 | % we need preview 103 | \\usepackage[active, displaymath]{preview} 104 | % macros sage uses 105 | \\newcommand{\\ZZ}{\\Bold{Z}} 106 | \\newcommand{\\NN}{\\Bold{N}} 107 | \\newcommand{\\RR}{\\Bold{R}} 108 | \\newcommand{\\CC}{\\Bold{C}} 109 | \\newcommand{\\QQ}{\\Bold{Q}} 110 | \\newcommand{\\QQbar}{\\overline{\\QQ}} 111 | \\newcommand{\\GF}[1]{\\Bold{F}_{#1}} 112 | \\newcommand{\\Zp}[1]{\\ZZ_{#1}} 113 | \\newcommand{\\Qp}[1]{\\QQ_{#1}} 114 | \\newcommand{\\Zmod}[1]{\\ZZ/#1\\ZZ} 115 | \\newcommand{\\CDF}{\\Bold{C}} 116 | \\newcommand{\\CIF}{\\Bold{C}} 117 | \\newcommand{\\CLF}{\\Bold{C}} 118 | \\newcommand{\\RDF}{\\Bold{R}} 119 | \\newcommand{\\RIF}{\\Bold{I} \\Bold{R}} 120 | \\newcommand{\\RLF}{\\Bold{R}} 121 | \\newcommand{\\CFF}{\\Bold{CFF}} 122 | " 123 | "The default LaTeX preamble." 124 | :type 'string 125 | :group 'sage-shell-view) 126 | 127 | (defcustom sage-shell-view-latex-foreground-color nil 128 | "Foreground color used in LaTeX image as string. 129 | If the value is `nil', then this variable is ignored." 130 | :type '(choice (const :tag "Not Specified" nil) 131 | (string :tag "Color")) 132 | :group 'sage-shell-view) 133 | 134 | (defcustom sage-shell-view-latex-background-color nil 135 | "Background color used in LaTeX image as string. 136 | If the value is `nil', then this variable is ignored." 137 | :type '(choice (const :tag "Not Specified" nil) 138 | (string :tag "Color")) 139 | :group 'sage-shell-view) 140 | 141 | (defcustom sage-shell-view-dvipng-options nil 142 | "*Options for dvipng when converting from DVI to PNG." 143 | :type 'list 144 | :group 'sage-shell-view) 145 | 146 | (defcustom sage-shell-view-margin '(1 . 1) 147 | "*Margin (in pixels or (pixels-x . pixels-y)) added around displayed images." 148 | :type '(choice integer (cons integer integer)) 149 | :group 'sage-shell-view) 150 | 151 | (defcustom sage-shell-view-scale-factor 0.2 152 | "*Factor used when zooming." 153 | :type 'number 154 | :group 'sage-shell-view) 155 | 156 | (defcustom sage-shell-view-default-resolution 125 157 | "Resolution used when converting from DVI to PNG. 158 | This value is passed to the -D option of the command dvipng. 159 | If it is `nil', then the function `sage-shell-view-compute-resolution' 160 | computes the resolution automatically." 161 | :type 'number 162 | :group 'sage-shell-view) 163 | 164 | (defcustom sage-shell-view-lighter " sage-view" 165 | "Lighter for `sage-shell-view' minor mode." 166 | :group 'sage-shell-view 167 | :type 'string) 168 | 169 | (defcustom sage-shell-view-latex-documentclass "\\documentclass{article}" 170 | "documentclass for LaTeX" 171 | :group 'sage-shell-view 172 | :type 'string) 173 | 174 | (defcustom sage-shell-view-latex-math-environment "math" 175 | "Math environment for LaTeX." 176 | :group 'sage-shell-view 177 | :type 'string) 178 | 179 | (defvar sage-shell-view-scale 1.0 180 | "Scale used when converting from PDF/PS to PNG.") 181 | 182 | (defun sage-shell-view-color-to-rgb (str) 183 | "Convert color name STR to rgb values understood by TeX." 184 | (mapcar (lambda (x) (format "%g" (/ x 65535.0))) (color-values str))) 185 | 186 | (defun sage-shell-view-rgb (fg-or-bg) 187 | (let ((color-alst `((bg . ,sage-shell-view-latex-background-color) 188 | (fg . ,sage-shell-view-latex-foreground-color))) 189 | (sym-alst `((bg . background-color) 190 | (fg . foreground-color)))) 191 | (sage-shell-view-color-to-rgb 192 | (or (assoc-default fg-or-bg color-alst) 193 | (frame-parameter nil (assoc-default fg-or-bg sym-alst)))))) 194 | 195 | (defun sage-shell-view-latex-str (math-expr) 196 | "LaTeX string to be inserted a tmp file." 197 | (format 198 | "%s 199 | %s 200 | \\begin{document} 201 | \\begin{preview} 202 | \\begin{%s} 203 | %s 204 | \\end{%s} 205 | \\end{preview} 206 | \\end{document} 207 | " 208 | sage-shell-view-latex-documentclass 209 | sage-shell-view-latex-preamble 210 | sage-shell-view-latex-math-environment 211 | ;; math-expr 212 | (replace-regexp-in-string 213 | "^\$+" "" 214 | (replace-regexp-in-string "\$+$" "" math-expr)) 215 | sage-shell-view-latex-math-environment)) 216 | 217 | (defun sage-shell-view-dir-name () 218 | (sage-shell-edit--set-and-make-temp-dir) 219 | (buffer-local-value 'sage-shell-edit:temp-directory 220 | sage-shell:process-buffer)) 221 | 222 | (defun sage-shell-view-overlay-activep (ov) 223 | "Check whether there is a valid image associated with OV." 224 | (equal (car (overlay-get ov 'display)) 'image)) 225 | 226 | (defun sage-shell-view-regenerate (ov) 227 | "Return zoom to normal and regenerate the overlay." 228 | (overlay-put ov 'scale sage-shell-view-scale) 229 | (sage-shell-view-process-overlay ov)) 230 | 231 | (defun sage-shell-view-zoom-in (ov &optional multiplier) 232 | "Internal function to zoom in on an overlay." 233 | (unless (numberp multiplier) 234 | (setq multiplier 1)) 235 | (let* ((scale (or (overlay-get ov 'scale) sage-shell-view-scale)) 236 | (new-scale (+ scale (* multiplier sage-shell-view-scale-factor)))) 237 | (overlay-put ov 'scale new-scale) 238 | (message "Overlay's scale set to %s" new-scale) 239 | (sage-shell-view-process-overlay ov))) 240 | 241 | (defun sage-shell-view-zoom-out (ov &optional multiplier) 242 | "Internal function to zoom out on an overlay." 243 | (unless (numberp multiplier) 244 | (setq multiplier 1)) 245 | (let* ((scale (or (overlay-get ov 'scale) sage-shell-view-scale)) 246 | (new-scale (- scale (* multiplier sage-shell-view-scale-factor)))) 247 | ;; Ensure it's not too small (or negative) 248 | (when (< new-scale sage-shell-view-scale-factor) 249 | (setq new-scale sage-shell-view-scale-factor)) 250 | (overlay-put ov 'scale new-scale) 251 | (message "Overlay's scale set to %s" new-scale) 252 | (sage-shell-view-process-overlay ov))) 253 | 254 | (defmacro sage-shell-view--when-overlay-active (ov &rest body) 255 | (declare (indent 1)) 256 | `(if (sage-shell-view-overlay-activep ,ov) 257 | (progn ,@body) 258 | (error "There is no valid image associated with the overlay."))) 259 | 260 | (defun sage-shell-view-context-menu (ov ev) 261 | "Pop up a menu for OV at position EV." 262 | (popup-menu 263 | `("Sage View Mode" 264 | ["Regenerate" (lambda () (interactive) (sage-shell-view-regenerate ,ov))] 265 | ["Copy Text" (lambda () (interactive) (sage-shell-view-copy-text ,ov))] 266 | ["Copy LaTeX" (lambda () (interactive) (sage-shell-view-copy-latex ,ov))] 267 | ["Save As..." (lambda () (interactive) 268 | (sage-shell-view--when-overlay-active ,ov 269 | (sage-shell-view-save-image ,ov)))] 270 | ["Zoom in" (lambda (multiplier) (interactive "p") 271 | (sage-shell-view--when-overlay-active ,ov 272 | (sage-shell-view-zoom-in ,ov multiplier)))] 273 | ["Zoom out" (lambda (multiplier) 274 | (interactive "p") 275 | (sage-shell-view--when-overlay-active ,ov 276 | (sage-shell-view-zoom-out ,ov multiplier)))] 277 | "--" 278 | ["Customize Conversion Options" 279 | (lambda () (interactive) 280 | (sage-shell-view--when-overlay-active ,ov 281 | (customize-group 'sage-shell-view t)))]) 282 | ev)) 283 | 284 | (defun sage-shell-view-cleanup-copied-text (str) 285 | "Remove some boilerplate text added by Sage to all LaTeX output." 286 | (replace-regexp-in-string 287 | (regexp-quote "\\newcommand{\\Bold}[1]{\\mathbf{#1}}") 288 | "" str)) 289 | 290 | (defun sage-shell-view-copy-text (ov) 291 | "Copy text source of OV into the kill buffer." 292 | (let ((text (overlay-get ov 'text))) 293 | (if text 294 | (kill-new text) 295 | (message "No text available")))) 296 | 297 | (defun sage-shell-view-copy-latex (ov) 298 | "Copy LaTeX source of OV into the kill buffer." 299 | (let ((text (overlay-get ov 'math))) 300 | (if text 301 | (kill-new (sage-shell-view-cleanup-copied-text text)) 302 | (message "No LaTeX code available")))) 303 | 304 | (defvar sage-shell-view-plot-regex 305 | (rx "BEGIN_PNG:" (group (1+ nonl)) ":END_PNG") 306 | "Regular expression matching a plot output in Sage output.") 307 | 308 | (defun sage-shell-view-save-image (ov) 309 | "Copy image file associated to OV. 310 | 311 | Make sure that there is a valid image associated with OV with 312 | `sage-shell-view-overlay-activep'." 313 | (let* ((spec (cdr (overlay-get ov 'display))) 314 | (file (plist-get spec :file)) 315 | (name (when (and file (file-readable-p file)) 316 | (expand-file-name 317 | (read-file-name "Write image to file: " 318 | default-directory 319 | "sage-shell-view.png"))))) 320 | (if name 321 | (copy-file file name)))) 322 | 323 | (defun sage-shell-view-plot-context-menu (ov ev) 324 | "Pop up a menu for OV at position EV." 325 | (popup-menu 326 | (list 327 | "Sage View Mode" 328 | (vector 329 | "Save As..." 330 | (lambda () (interactive) 331 | (sage-shell-view--when-overlay-active ov 332 | (sage-shell-view-save-image ov))))) 333 | ev)) 334 | 335 | (defun sage-shell-view-compute-resolution (scale) 336 | (if (display-graphic-p) 337 | ;; In a terminal, display-mm-width returns nil and 338 | ;; display-pixel-width returns the number of characters. 339 | (let ((w (* scale (/ (* 25.4 1.4 (display-pixel-width)) 340 | (display-mm-width)))) 341 | (h (* scale (/ (* 25.4 1.4 (display-pixel-height)) 342 | (display-mm-height))))) 343 | (concat (int-to-string w) "x" (int-to-string h))) 344 | "72x72")) 345 | 346 | (defun sage-shell-view-process-overlay (ov) 347 | "Associate a LATEX document to OV and start conversion process 348 | from LATEX to PDF." 349 | (let* ((base (expand-file-name 350 | (make-temp-name "sage-shell-view_") (sage-shell-view-dir-name)))) 351 | (with-temp-file (concat base ".tex") 352 | (insert (sage-shell-view-latex-str 353 | (overlay-get ov 'math))) 354 | ;; The LaTeX created by Sage for MathJax (in some cases) isn't valid. 355 | ;; This is our attempt to work around it. 356 | (goto-char (point-min)) 357 | (while (search-forward-regexp "\\verb!\\([^!]*\\)!" nil t) 358 | (replace-match "\mathtt{\\1}"))) 359 | 360 | (overlay-put ov 'file-sans-extension base) 361 | 362 | (deferred:$ 363 | (apply #'deferred:process "latex" (sage-shell-view--latex-option base)) 364 | 365 | (deferred:nextc it 366 | (lambda (_) (sage-shell-view--dvi-to-png ov)))))) 367 | 368 | (defun sage-shell-view--dvi-to-png (ov) 369 | (unless (executable-find sage-shell-view-dvipng-command) 370 | (error "dvipng not found. To use sage-shell-view mode, please install dvipng.")) 371 | (let ((base (overlay-get ov 'file-sans-extension))) 372 | (deferred:$ 373 | (apply #'deferred:process 374 | sage-shell-view-dvipng-command 375 | (sage-shell-view--dvipng-option (overlay-get ov 'scale) base)) 376 | (deferred:nextc it 377 | (lambda (_) 378 | (overlay-put ov 'display 379 | (list 'image :type 'png :file (concat base ".png") 380 | :margin sage-shell-view-margin))))))) 381 | 382 | (defun sage-shell-view--latex-option (base) 383 | (list (concat "--output-directory=" (sage-shell-view-dir-name)) 384 | (concat "-interaction=" "nonstopmode") 385 | (concat base ".tex"))) 386 | 387 | (defun sage-shell-view--dvipng-option (scale base) 388 | (let ((png (shell-quote-argument (concat base ".png"))) 389 | (dvi (shell-quote-argument (concat base ".dvi"))) 390 | (scale (or scale sage-shell-view-scale))) 391 | (append sage-shell-view-dvipng-options 392 | (list 393 | "-T tight" 394 | (apply #'format "-fg rgb %s %s %s" (sage-shell-view-rgb 'fg)) 395 | (apply #'format "-bg rgb %s %s %s" (sage-shell-view-rgb 'bg)) 396 | "-D" (sage-shell-view--resultion scale) 397 | "-o" png 398 | dvi)))) 399 | 400 | (defun sage-shell-view--resultion (scale) 401 | (if sage-shell-view-default-resolution 402 | (sage-shell:->> 403 | (* scale sage-shell-view-default-resolution) 404 | round 405 | int-to-string) 406 | (sage-shell-view-compute-resolution 407 | scale))) 408 | 409 | (defvar sage-shell-view-inline-plots-enabled nil) 410 | (make-variable-buffer-local 'sage-shell-view-inline-plots-enabled) 411 | (defvar sage-shell-view-inline-output-enabled nil) 412 | (make-variable-buffer-local 'sage-shell-view-inline-output-enabled) 413 | 414 | (defun sage-shell-view-output-filter-process-inline-plots (_string) 415 | "Generate and place one overlay image for one inline plot, 416 | found by looking for a particular png file in directory 417 | `sage-shell-view-dir-name'. 418 | 419 | This function expects the buffer to be narrowed to just the 420 | current output; see `sage-shell-view-output-filter' for how to do 421 | that." 422 | (goto-char (point-min)) 423 | (while (re-search-forward 424 | sage-shell-view-plot-regex 425 | (point-max) t) 426 | (let* ((plot-beg (match-beginning 0)) 427 | (plot-end (match-end 0)) 428 | (pngname (match-string-no-properties 1)) 429 | (base (expand-file-name (make-temp-name "sage-shell-view-plot_") 430 | (sage-shell-view-dir-name))) 431 | (pngname2 (concat base ".png"))) 432 | (when (and pngname 433 | (file-exists-p pngname) 434 | (file-readable-p pngname)) 435 | ;; the found branch 436 | (rename-file pngname pngname2 t) 437 | (goto-char comint-last-input-end) 438 | (let ((im (create-image pngname2 'png)) 439 | (ov (make-overlay plot-beg plot-end 440 | nil nil nil)) 441 | (map (make-sparse-keymap))) 442 | (overlay-put ov 'display im) 443 | ;; help alignment as much as possible 444 | (overlay-put ov 'before-string "\n") 445 | (overlay-put ov 'sage-shell-view t) 446 | (define-key map [mouse-3] 447 | (lambda (event) (interactive "e") 448 | (sage-shell-view-plot-context-menu ov event))) 449 | (overlay-put ov 'keymap map)))))) 450 | 451 | (defvar sage-shell-view-output-regexp 452 | (rx "BEGIN_TEXT:" 453 | (group (minimal-match (0+ (or nonl "\n")))) 454 | ":END_TEXTBEGIN_LATEX:" 455 | (group (minimal-match (0+ (or nonl "\n")))) 456 | ":END_LATEX") 457 | "Regular expression matching typeset output from BackendEmacs.") 458 | 459 | (defun sage-shell-view-output-filter-process-inline-output (_string) 460 | "Substitute overlays to inline output. 461 | 462 | Each region delimited by `sage-shell-view-start-string' and 463 | `sage-shell-view-final-string' is replaced by an overlay. 464 | 465 | This function expects the buffer to be narrowed to the current 466 | output. And should be wrapped in a `save-excursion' and 467 | `save-restriction' call. 468 | 469 | See also `sage-shell-view-output-filter'." 470 | (goto-char (point-min)) 471 | (while (re-search-forward sage-shell-view-output-regexp (point-max) t) 472 | (let* ((full-beg (match-beginning 0)) 473 | (full-end (match-end 0)) 474 | (text-beg (match-beginning 1)) 475 | (text-end (match-end 1)) 476 | (latex-beg (match-beginning 2)) 477 | (latex-end (match-end 2)) 478 | (text (buffer-substring-no-properties text-beg text-end)) 479 | (latex (buffer-substring-no-properties latex-beg latex-end)) 480 | (ov (make-overlay full-beg full-end 481 | nil nil nil)) 482 | (map (make-sparse-keymap))) 483 | ;; Delete everything except the text 484 | (delete-region text-end full-end) 485 | (delete-region full-beg text-beg) 486 | ;; Populate the overlay 487 | (overlay-put ov 'help-echo "mouse-3: Open contextual menu") 488 | (overlay-put ov 'text text) 489 | (overlay-put ov 'math latex) 490 | (define-key map [mouse-3] 491 | `(lambda (event) (interactive "e") 492 | (sage-shell-view-context-menu ,ov event))) 493 | (overlay-put ov 'keymap map) 494 | (overlay-put ov 'sage-shell-view t) 495 | (sage-shell-view-process-overlay ov)))) 496 | 497 | ;;;###autoload 498 | (define-minor-mode sage-shell-view-mode 499 | "Toggle automatic typesetting of Sage output. 500 | 501 | Typesetting of math formulas is done by LATEX subprocesses and 502 | PDF to PNG conversions." 503 | :lighter sage-shell-view-lighter 504 | :global nil 505 | :init-value nil 506 | (cond (sage-shell-view-mode 507 | (add-hook 'comint-output-filter-functions 508 | 'sage-shell-view-output-filter nil t) 509 | (cond 510 | ((eq sage-shell-view-default-commands 'plots) 511 | (sage-shell-view-enable-inline-plots)) 512 | ((eq sage-shell-view-default-commands 'output) 513 | (sage-shell-view-enable-inline-output)) 514 | (sage-shell-view-default-commands 515 | (sage-shell-view-enable-inline-plots) 516 | (sage-shell-view-enable-inline-output)))) 517 | (t (remove-hook 'comint-output-filter-functions 518 | 'sage-shell-view-output-filter t) 519 | (sage-shell-view-set-backend nil nil)))) 520 | 521 | ;;;###autoload 522 | (defalias 'sage-shell-view 'sage-shell-view-mode) 523 | 524 | (defun sage-shell-view-output-filter (string) 525 | "Generate and place overlay images for inline output and inline plots. 526 | 527 | Function to be inserted in `comint-output-filter-functions'." 528 | (when sage-shell-view-mode 529 | (save-excursion 530 | (save-restriction 531 | (narrow-to-region comint-last-input-end 532 | (process-mark (get-buffer-process (current-buffer)))) 533 | (when sage-shell-view-inline-plots-enabled 534 | (sage-shell-view-output-filter-process-inline-plots string)) 535 | (when sage-shell-view-inline-output-enabled 536 | (sage-shell-view-output-filter-process-inline-output string)))))) 537 | 538 | (defun sage-shell-view-update-modeline () 539 | "Update modeline to include information about whether sage-shell-view 540 | is enabled." 541 | (when (eq major-mode 'sage-shell-mode) 542 | (let ((fmt (format "/%s%s" 543 | (if sage-shell-view-inline-plots-enabled "p" "") 544 | (if sage-shell-view-inline-output-enabled "t" ""))) 545 | (bare-mode-name (if (string-match "\\(^[^/]*\\)/" mode-name) 546 | (match-string 1 mode-name) 547 | mode-name))) 548 | (setq mode-name 549 | (if (> (length fmt) 1) 550 | (concat bare-mode-name fmt) 551 | bare-mode-name)) 552 | (force-mode-line-update)))) 553 | 554 | ;;;###autoload 555 | (defun sage-shell-view-enable-inline-output () 556 | "Enable inline output pretty-printing, i.e. typeset output from sage in the 557 | `sage-shell-mode' buffer. 558 | WARNING: this communicates with the sage process. Only use this when sage is 559 | running." 560 | (interactive) 561 | (sage-shell-view--set-inline-state 562 | 'text t)) 563 | 564 | (defun sage-shell-view-disable-inline-output () 565 | "Disable inline output pretty-printing, i.e. do not typeset output from sage 566 | in the `sage-shell-mode' buffer. 567 | WARNING: this communicates with the sage process. Only use this when sage is running." 568 | (interactive) 569 | (sage-shell-view--set-inline-state 570 | 'text nil)) 571 | 572 | ;;;###autoload 573 | (defun sage-shell-view-enable-inline-plots () 574 | "Enable inline plotting, i.e. display plots in the `sage-shell-mode' buffer 575 | and do not spawn an external viewer. 576 | WARNING: this communicates with the sage process. 577 | Only use this when sage is running." 578 | (interactive) 579 | (sage-shell-view--set-inline-state 580 | 'plot t)) 581 | 582 | (defun sage-shell-view-disable-inline-plots () 583 | "Disable inline plotting, i.e. do not display plots in the 584 | `sage-shell-mode' buffer and instead spawn an external viewer. 585 | WARNING: this communicates with the sage process. 586 | Only use this when sage is running." 587 | (interactive) 588 | (sage-shell-view--set-inline-state 589 | 'plot nil)) 590 | 591 | ;;;###autoload 592 | (cl-defun sage-shell-view-toggle-inline-output (&optional (verbose t)) 593 | "Toggle inline typesetting of outputs in `sage-shell-mode' buffer." 594 | (interactive) 595 | (sage-shell-edit:set-sage-proc-buf-internal nil) 596 | (sage-shell-view--set-inline-state 597 | 'text 598 | (not (buffer-local-value 'sage-shell-view-inline-output-enabled 599 | sage-shell:process-buffer)) 600 | verbose)) 601 | 602 | ;;;###autoload 603 | (cl-defun sage-shell-view-toggle-inline-plots (&optional (verbose t)) 604 | "Toggle inline plotting of graphs in `sage-shell-mode' buffer." 605 | (interactive) 606 | (sage-shell-edit:set-sage-proc-buf-internal nil) 607 | (sage-shell-view--set-inline-state 608 | 'plot 609 | (not (buffer-local-value 'sage-shell-view-inline-plots-enabled 610 | sage-shell:process-buffer)) 611 | verbose)) 612 | 613 | (defun sage-shell-view--set-inline-state (type enable-p &optional verbose) 614 | "Enable/disable inline outputs/plots." 615 | (sage-shell-edit:set-sage-proc-buf-internal nil) 616 | (with-current-buffer sage-shell:process-buffer 617 | (unless sage-shell-view-mode 618 | (let ((sage-shell-view-default-commands nil)) 619 | (sage-shell-view-mode 1))) 620 | (let ((current-state `((text . ,sage-shell-view-inline-output-enabled) 621 | (plot . ,sage-shell-view-inline-plots-enabled)))) 622 | (unless (assoc type current-state) 623 | (error "TYPE should be text or plot.")) 624 | (setcdr (assoc type current-state) enable-p) 625 | (let-alist current-state 626 | (sage-shell-view-set-backend 627 | .text .plot 628 | (lambda () (when verbose 629 | (message "Inline %s %s." 630 | (let ((l '((text . "typesetting") 631 | (plot . "plots")))) 632 | (assoc-default type l)) 633 | (if enable-p 634 | "enabled" 635 | "disabled"))))))))) 636 | 637 | (defconst sage-shell-view--mod-name "_emacs_sage_shell_view") 638 | 639 | (defun sage-shell-view--to-py-bool (a) 640 | (if a "True" "False")) 641 | 642 | (cl-defun sage-shell-view-set-backend (text plot &optional 643 | (success-callback #'ignore)) 644 | (cl-check-type success-callback function) 645 | (with-current-buffer sage-shell:process-buffer 646 | (sage-shell-view--init) 647 | (sage-shell:run-cell 648 | (format "%s.%s(text=%s, plot=%s)" 649 | sage-shell-view--mod-name 650 | "set_backend" 651 | (sage-shell-view--to-py-bool text) 652 | (sage-shell-view--to-py-bool plot)) 653 | :sync t 654 | :callback 655 | (lambda (res) 656 | (cond ((sage-shell:output-stct-success res) 657 | (funcall success-callback) 658 | (setq sage-shell-view-inline-plots-enabled plot 659 | sage-shell-view-inline-output-enabled text) 660 | (sage-shell-view-update-modeline)) 661 | (t (sage-shell--error-callback res))))))) 662 | 663 | (defvar sage-shell-view--init-completed-p nil) 664 | (make-variable-buffer-local 'sage-shell-view--init-completed-p) 665 | 666 | (defun sage-shell-view--init () 667 | (unless sage-shell-view--init-completed-p 668 | (sage-shell:run-cell 669 | "import emacs_sage_shell_view as _emacs_sage_shell_view" 670 | :sync t 671 | :callback #'sage-shell--error-callback) 672 | (with-current-buffer sage-shell:process-buffer 673 | (setq sage-shell-view--init-completed-p t)))) 674 | 675 | (provide 'sage-shell-view) 676 | ;;; sage-shell-view.el ends here 677 | -------------------------------------------------------------------------------- /test/sage-shell-mode-test.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t -*- 2 | 3 | (require 'ert) 4 | (require 'sage-shell-mode) 5 | 6 | (ert-deftest sage-shell:development-version-test () 7 | (should (or (string= (user-login-name) "travis") 8 | (string= 9 | (sage-shell:src-version 10 | (expand-file-name "local/lib/python2.7/site-packages/sage/rings/function_field/function_field.py" 11 | (sage-shell:sage-root))) 12 | (expand-file-name "src/sage/rings/function_field/function_field.py" 13 | (sage-shell:sage-root))))) 14 | (should (or 15 | (string= (user-login-name) "travis") 16 | (string= 17 | (sage-shell:src-version 18 | (expand-file-name "local/lib/python2.7/site-packages/sage/misc/cachefunc.so" 19 | (sage-shell:sage-root))) 20 | (expand-file-name "src/sage/misc/cachefunc.pyx" 21 | (sage-shell:sage-root))))) 22 | (should (or 23 | (string= (user-login-name) "travis") 24 | (string= 25 | (sage-shell:site-package-version 26 | (expand-file-name "src/sage/rings/function_field/function_field.py" 27 | (sage-shell:sage-root))) 28 | (expand-file-name "local/lib/python2.7/site-packages/sage/rings/function_field/function_field.py" 29 | (sage-shell:sage-root)))))) 30 | 31 | (defun sage-shell-test:temp-state 32 | (code int &optional pt) 33 | (with-temp-buffer 34 | (insert code) 35 | (goto-char (or pt (point-max))) 36 | (sage-shell-cpl:parse-current-state int))) 37 | 38 | (defun sage-shell-test:sage-mode-temp-state (code &optional pt) 39 | (with-temp-buffer 40 | (insert code) 41 | (goto-char (or pt (point-max))) 42 | (sage-shell-edit:parse-current-state))) 43 | 44 | (defmacro sage-shell-test:state-assert (state &rest args) 45 | (declare (indent 1) (debug t)) 46 | (let ((test-fns '((prefix . =) 47 | (var-base-name . equal) 48 | (types . equal) 49 | (interface . string=) 50 | (module-name . equal)))) 51 | (cons 'and 52 | (cl-loop for (a b) in (sage-shell:group args) 53 | collect 54 | (list (or (assoc-default a test-fns) #'equal) 55 | `(sage-shell-cpl:get ,state ',a) b))))) 56 | 57 | (ert-deftest sage-shell:parse-state-repl-attribute () 58 | (should (let ((state (sage-shell-test:temp-state 59 | "sage: abc.a[0].aaa" "sage"))) 60 | (sage-shell-test:state-assert state 61 | prefix 16 62 | var-base-name "abc.a[0]" 63 | types '("attributes") 64 | interface "sage")))) 65 | 66 | (ert-deftest sage-shell:parse-state-repl-funcall () 67 | (should (let ((state (sage-shell-test:temp-state "sage: f().foo" "sage"))) 68 | (sage-shell-test:state-assert state 69 | var-base-name nil 70 | types nil 71 | interface "sage")))) 72 | 73 | (ert-deftest sage-shell:parse-state-repl-intf () 74 | (should (let ((state (sage-shell-test:temp-state "sage: gap.ev" "sage"))) 75 | (sage-shell-test:state-assert state 76 | types '("interface" "attributes") 77 | interface "gap"))) 78 | 79 | (should (let ((state (sage-shell-test:temp-state 80 | "sage: gap.eval(\"Ab\")" "sage" 81 | 18))) 82 | (sage-shell-test:state-assert state 83 | prefix 17 84 | types '("interface") 85 | interface "gap")))) 86 | 87 | (ert-deftest sage-shell:parse-state-repl-block () 88 | (should (let ((state (sage-shell-test:temp-state "sage: def foo(x): 89 | ....: if abc" "sage"))) 90 | (sage-shell-test:state-assert state 91 | types '("interface") 92 | interface "sage" 93 | prefix 32)))) 94 | 95 | (ert-deftest sage-shell:parse-state-repl-other-int-gap () 96 | (should (let ((state (sage-shell-test:temp-state "gap: " "gap"))) 97 | (sage-shell-test:state-assert state 98 | types '("interface") 99 | interface "gap")))) 100 | 101 | (ert-deftest sage-shell:parse-state-repl-other-int-gp () 102 | (should (let ((state (sage-shell-test:temp-state "pari: " "gp"))) 103 | (sage-shell-test:state-assert state 104 | types '("interface") 105 | interface "gp")))) 106 | 107 | (ert-deftest sage-shell:parse-state-edit-from-top-level () 108 | (should (let ((state (sage-shell-test:sage-mode-temp-state 109 | "from foo"))) 110 | (sage-shell-test:state-assert state 111 | types '("modules") 112 | module-name nil)))) 113 | 114 | (ert-deftest sage-shell:parse-state-edit-from-sub-module () 115 | (should (let ((state (sage-shell-test:sage-mode-temp-state 116 | "from foo.bar.baz"))) 117 | (sage-shell-test:state-assert state 118 | types '("modules") 119 | module-name "foo.bar")))) 120 | 121 | (ert-deftest sage-shell:parse-state-edit-from-sub-module-in-block () 122 | (should (let ((state (sage-shell-test:sage-mode-temp-state 123 | "def foo(): 124 | from foo.bar.baz"))) 125 | (sage-shell-test:state-assert state 126 | types '("modules") 127 | module-name "foo.bar")))) 128 | 129 | (ert-deftest sage-shell:parse-state-edit-from-vars-in-module () 130 | (should (let ((state (sage-shell-test:sage-mode-temp-state 131 | "from foo.bar import (Foo, 132 | Bar, B"))) 133 | (sage-shell-test:state-assert state 134 | types '("vars-in-module") 135 | module-name "foo.bar")))) 136 | 137 | (ert-deftest sage-shell:parse-state-func-call () 138 | (should (let ((state (sage-shell-test:temp-state 139 | "sage: foo()" "sage" 11))) 140 | (sage-shell-test:state-assert state 141 | types '("in-function-call" "interface") 142 | in-function-call "foo" 143 | in-function-call-end 10 144 | in-function-call-base-name nil)))) 145 | 146 | (ert-deftest sage-shell:parse-state-func-call-1 () 147 | (should (let ((state (sage-shell-test:temp-state 148 | "sage: foo(1, 2, ((2, 3, 4, [5, 6])))" "sage" 33))) 149 | (sage-shell-test:state-assert state 150 | types '("in-function-call" "interface") 151 | in-function-call "foo" 152 | in-function-call-end 10)))) 153 | 154 | (ert-deftest sage-shell:parse-state-func-call-1 () 155 | (should (let ((state (sage-shell-test:temp-state 156 | "sage: foo(1, 2, ((2, 3, 4, [5, 6])))" "sage" 33))) 157 | (sage-shell-test:state-assert state 158 | types '("in-function-call" "interface") 159 | in-function-call "foo" 160 | in-function-call-end 10)))) 161 | 162 | (ert-deftest sage-shell:parse-state-edit-func-call () 163 | (should (let ((state (sage-shell-test:sage-mode-temp-state 164 | "foo(1, 2, 165 | ((2, 3, 4, [5, 6])))" 25))) 166 | (sage-shell-test:state-assert state 167 | types '("interface") 168 | in-function-call "foo" 169 | in-function-call-end 4)))) 170 | 171 | (ert-deftest sage-shell:parse-state-edit-func-call-1 () 172 | (should (let ((state (sage-shell-test:sage-mode-temp-state 173 | "foo(1, 2, 174 | ((2, 3, 4, [5, 6])))"))) 175 | (sage-shell-test:state-assert state 176 | types '("interface") 177 | in-function-call nil)))) 178 | 179 | (ert-deftest sage-shell:split-args () 180 | (should (= (length 181 | (sage-shell:-eldoc-split-buffer-args "1, 2, ((2, 3, 4, [5,")) 182 | 3))) 183 | 184 | (ert-deftest sage-shell:split-args-1 () 185 | (should (= (length 186 | (sage-shell:-eldoc-split-buffer-args 187 | "1, 2,3,4, 188 | foo=bar(1, 2),baz=(1, 2")) 189 | 6))) 190 | 191 | (ert-deftest sage-shell:split-args-2 () 192 | (should (= (length 193 | (sage-shell:-eldoc-split-buffer-args 194 | (concat "[foo(1, 2), ((3, 4), 5)], " 195 | " 196 | (((a, b), c))[0], 197 | foo=bar(1, 2), baz=(1, 2"))) 198 | 4))) 199 | 200 | 201 | (defvar sage-shell-test:eldoc-str 202 | "foo(a, b, bar=[0, (1, 2), (3, (5, 6)), '**kwds'], foo_bar='x, y, *args', **kwds)") 203 | 204 | (ert-deftest sage-shell:eldoc-highlight () 205 | (should (equal (sage-shell:-eldoc-highlight-beg-end 206 | "foo" sage-shell-test:eldoc-str "a" nil) 207 | (cons 4 5)))) 208 | 209 | (ert-deftest sage-shell:eldoc-highlight-1 () 210 | (should (equal (sage-shell:-eldoc-highlight-beg-end 211 | "foo" sage-shell-test:eldoc-str "c" nil) 212 | (cons 73 79)))) 213 | 214 | (ert-deftest sage-shell:eldoc-highlight-2 () 215 | (should (equal (sage-shell:-eldoc-highlight-beg-end 216 | "foo" sage-shell-test:eldoc-str "bar" nil) 217 | (cons 10 48)))) 218 | 219 | (ert-deftest sage-shell:eldoc-highlight-3 () 220 | (should (equal (sage-shell:-eldoc-highlight-beg-end 221 | "foo" "foo(a, b, *args, **kwds)" nil 5) 222 | (cons 10 15)))) 223 | 224 | (ert-deftest sage-shell:eldoc-highlight-4 () 225 | (should (equal (sage-shell:-eldoc-highlight-beg-end 226 | "foo" "foo(a, b, *args, **kwds)" "bar" nil) 227 | (cons 17 23)))) 228 | 229 | (defun sage-shell-test--start-sage-sync () 230 | (let ((proc-buf (sage-shell:run-sage "sage"))) 231 | (with-current-buffer proc-buf 232 | (while (null (sage-shell:output-finished-p)) 233 | (accept-process-output nil 0 100)) 234 | proc-buf))) 235 | 236 | (when (executable-find "sage") 237 | (setq sage-shell:use-prompt-toolkit t) 238 | (let ((proc-buf (sage-shell-test--start-sage-sync))) 239 | 240 | (let* ((rand-str (md5 (current-time-string))) 241 | (callback (sage-shell:send-command (format "print '%s'" rand-str) 242 | proc-buf))) 243 | (sage-shell:after-redirect-finished 244 | (ert-deftest sage-shell:test-send-command () 245 | (should (equal (funcall callback) (format "%s\n" rand-str)))))) 246 | 247 | 248 | (ert-deftest sage-shell:test-runcell-sync () 249 | (equal (sage-shell:run-cell-raw-output "10.factorial()" 250 | :to-string t 251 | :process-buffer proc-buf) 252 | "3628800\n")) 253 | 254 | (let ((rand-str (md5 (current-time-string)))) 255 | (sage-shell:run-cell 256 | (format "print '%s'" rand-str) 257 | :process-buffer proc-buf 258 | :callback (lambda (res) 259 | (ert-deftest sage-shell:test-run-cell-1 () 260 | (should (equal (sage-shell:output-stct-output res) 261 | (format "%s\n" rand-str))) 262 | (should (equal (sage-shell:output-stct-success res) t)))))) 263 | 264 | (sage-shell:run-cell 265 | "x/(x - x)" 266 | :process-buffer proc-buf 267 | :callback (lambda (res) 268 | (ert-deftest sage-shell:test-run-cell-2 () 269 | (should (equal (sage-shell:output-stct-success res) nil)) 270 | (should (string-match "ZeroDivisionError" 271 | (sage-shell:output-stct-output res)))))) 272 | 273 | ;; Wait for evaliation completes 274 | (with-current-buffer proc-buf 275 | (while (null (sage-shell:redirect-finished-p)) 276 | (accept-process-output nil 0 100))) 277 | 278 | (ert-deftest sage-shell:test-singular-vars () 279 | (with-current-buffer proc-buf 280 | (let* ((state '((types "interface" "attributes") 281 | (interface . "singular") 282 | (var-base-name . "singular"))) 283 | (cands (sage-shell-cpl:candidates 284 | :sexp 285 | (sage-shell-cpl:completion-init t :compl-state state) 286 | :state state 287 | :regexp (sage-shell-interfaces:get "singular" 'cmd-rxp)))) 288 | (should (member "zerodec" cands)) 289 | (should (member "groebner" cands))))))) 290 | 291 | (defun sage-shell-test:-insert-test-str1 () 292 | (sage-shell:-insert-str (cl-loop for c from ?a to (+ ?a 10) 293 | concat (char-to-string c))) 294 | (insert (cl-loop for c from ?A to (+ ?A 10) 295 | concat (char-to-string c))) 296 | (newline) 297 | (insert (cl-loop for c from ?1 to (+ ?1 10) 298 | concat (char-to-string c))) 299 | (newline) 300 | (insert "aaaaa")) 301 | 302 | (ert-deftest sage-shell:delete-display-test () 303 | (with-temp-buffer 304 | (sage-shell-test:-insert-test-str1) 305 | (goto-char 4) 306 | (sage-shell:-delete-display nil) 307 | (should (string= (buffer-string) 308 | "abcABCDEFGHIJK 309 | 123456789:; 310 | aaaaa"))) 311 | (with-temp-buffer 312 | (sage-shell-test:-insert-test-str1) 313 | (goto-char 4) 314 | (sage-shell:-delete-display nil 1) 315 | (should (string= (buffer-string) "defghijkABCDEFGHIJK 316 | 123456789:; 317 | aaaaa"))) 318 | (with-temp-buffer 319 | (sage-shell-test:-insert-test-str1) 320 | (goto-char 4) 321 | (sage-shell:-delete-display nil 2) 322 | (should (string= (buffer-string) 323 | "ABCDEFGHIJK 324 | 123456789:; 325 | aaaaa")))) 326 | 327 | (ert-deftest sage-shell:delete-line-test () 328 | (with-temp-buffer 329 | (sage-shell-test:-insert-test-str1) 330 | (goto-char 3) 331 | (sage-shell:-delete-line nil) 332 | (should (= (point) 3)) 333 | (should (string= (buffer-string) 334 | "abABCDEFGHIJK 335 | 123456789:; 336 | aaaaa"))) 337 | (with-temp-buffer 338 | (sage-shell-test:-insert-test-str1) 339 | (goto-char 3) 340 | (sage-shell:-delete-line nil 1) 341 | (should (= (point) 3)) 342 | (should (string= (buffer-string) 343 | "cdefghijkABCDEFGHIJK 344 | 123456789:; 345 | aaaaa"))) 346 | (with-temp-buffer 347 | (sage-shell-test:-insert-test-str1) 348 | (goto-char 3) 349 | (sage-shell:-delete-line nil 2) 350 | (should (= (point) 3)) 351 | (should (string= (buffer-string) 352 | "ABCDEFGHIJK 353 | 123456789:; 354 | aaaaa")))) 355 | 356 | (ert-deftest sage-shell:up-down-test () 357 | (with-temp-buffer 358 | (sage-shell-test:-insert-test-str1) 359 | (goto-char 27) 360 | (sage-shell:-cursor-down nil 1) 361 | (should (= (point) 39))) 362 | (with-temp-buffer 363 | (sage-shell-test:-insert-test-str1) 364 | (goto-char 27) 365 | (sage-shell:-cursor-up nil 1) 366 | (should (= (point) 4)))) 367 | 368 | (ert-deftest sage-shell:forward-back-test () 369 | (with-temp-buffer 370 | (sage-shell-test:-insert-test-str1) 371 | 372 | (goto-char 3) 373 | (sage-shell:-cursor-forward nil) 374 | (should (= (point) 4)) 375 | 376 | (goto-char 3) 377 | (sage-shell:-cursor-back nil) 378 | (should (= (point) 2)) 379 | 380 | (goto-char 3) 381 | (sage-shell:-cursor-forward nil 3) 382 | (should (= (point) 6)) 383 | 384 | (goto-char 26) 385 | (sage-shell:-cursor-back nil 100) 386 | (should (= (point) 24)) 387 | 388 | (goto-char 3) 389 | (sage-shell:-cursor-forward nil 100) 390 | (should (= (point) 23)))) 391 | 392 | 393 | (ert-deftest sage-shell:-insert-and-handle-char-test () 394 | (let ((default-str "abcdefghijkABCDEFGHIJK 395 | 123456789:; 396 | aaaaa")) 397 | (with-temp-buffer 398 | (sage-shell-test:-insert-test-str1) 399 | (goto-char 3) 400 | (sage-shell:-insert-and-handle-char " ") 401 | (should (string= (buffer-string) 402 | "ab fghijkABCDEFGHIJK 403 | 123456789:; 404 | aaaaa")) 405 | 406 | 407 | (erase-buffer) 408 | (sage-shell-test:-insert-test-str1) 409 | (goto-char 7) 410 | (sage-shell:-insert-and-handle-char "\n") 411 | (should (= (point) 30)) 412 | (should (string= (buffer-string) 413 | default-str)) 414 | 415 | (goto-char 30) 416 | (sage-shell:-insert-and-handle-char "\n") 417 | (should (= (point) 42)) 418 | (should (string= (buffer-string) 419 | "abcdefghijkABCDEFGHIJK 420 | 123456789:; 421 | aaaaa ")) 422 | 423 | (erase-buffer) 424 | (sage-shell-test:-insert-test-str1) 425 | (goto-char 12) 426 | (sage-shell:-insert-and-handle-char "abc") 427 | (should (string= (buffer-string) 428 | "abcdefghijkabcABCDEFGHIJK 429 | 123456789:; 430 | aaaaa")) 431 | 432 | (erase-buffer) 433 | (sage-shell-test:-insert-test-str1) 434 | (goto-char 12) 435 | (sage-shell:-insert-and-handle-char " ") 436 | (should (string= (buffer-string) default-str)) 437 | (should (= (point) 1)) 438 | 439 | (goto-char (point-max)) 440 | (sage-shell:-insert-and-handle-char "abc \n123") 441 | (should (string= (buffer-string) "abcdefghijkABCDEFGHIJK 442 | 123456789:; 443 | aaaaaabc 444 | 123"))))) 445 | 446 | (ert-deftest sage-shell:-insert-and-handle-ansi-escape-test () 447 | (with-temp-buffer 448 | (sage-shell:-insert-str "sage: ") 449 | (sage-shell:-insert-and-handle-ansi-escape nil "sage: 12 450 | ") 451 | (should (string= (buffer-string) "sage: 12\n"))) 452 | 453 | (with-temp-buffer 454 | (sage-shell:-insert-str "sage: for a in range(10):") 455 | (newline) 456 | (sage-shell:-insert-str "....: print a") 457 | (newline) 458 | (sage-shell:-insert-str "....: ") 459 | (sage-shell:-insert-and-handle-ansi-escape 460 | nil 461 | "sage: for a in range(10): 462 | ....: print a 463 | ....:  464 | ") 465 | (string= (buffer-string) 466 | "sage: for a in range(10): 467 | ....: print a 468 | ....: 469 | ")) 470 | 471 | (with-temp-buffer 472 | (sage-shell:-insert-str "sage: if 1:") 473 | (newline) 474 | (sage-shell:-insert-str "....: ") 475 | (sage-shell:-insert-and-handle-ansi-escape 476 | nil 477 | " 478 | ") 479 | (should (string= (sage-shell:trim-right (buffer-string)) 480 | "sage:")))) 481 | 482 | (ert-deftest sage-shell:-hook-test () 483 | (let ((hook (intern (symbol-name (sage-shell:gensym "sage-shell"))))) 484 | (should (not (boundp hook))) 485 | (should (equal (add-hook hook #'ignore) (list #'ignore))))) 486 | 487 | (ert-deftest sage-shell:current-line-test () 488 | (let ((sage-shell:use-prompt-toolkit nil)) 489 | (with-temp-buffer 490 | (sage-shell:-insert-str "sage: ") 491 | (insert "foo") 492 | (newline) 493 | (sage-shell:-insert-str "sage: ") 494 | (insert "def foo():\n") 495 | (sage-shell:-insert-str "....: ") 496 | (insert " pass\n") 497 | (goto-char (point-min)) 498 | (should (string= (sage-shell:-current-line 7) "foo")) 499 | (forward-line 1) 500 | (should (string= (sage-shell:-current-line 17) "def foo():")) 501 | (forward-line 1) 502 | (should (string= (sage-shell:-current-line 34) " pass")))) 503 | (let ((sage-shell:use-prompt-toolkit t)) 504 | (with-temp-buffer 505 | (sage-shell:-insert-str "sage: foo\n") 506 | (sage-shell:-insert-str "sage: 12343434\n") 507 | (sage-shell:-insert-str "....: 12343434\n") 508 | (sage-shell:-insert-str "sage: ") 509 | (insert "def fooo():") 510 | 511 | (goto-char (point-min)) 512 | (should (string= (sage-shell:-current-line 7) "foo")) 513 | (forward-line 1) 514 | (should (string= (sage-shell:-current-line 17) "1234343412343434")) 515 | (forward-line 2) 516 | (should (string= (sage-shell:-current-line 47) "def fooo():")) 517 | 518 | (goto-char (point-max)) 519 | (insert "\n") 520 | (sage-shell:-insert-str "....: ") 521 | (insert "pass") 522 | 523 | (should (string= (sage-shell:-current-line 69) "pass"))))) 524 | 525 | (ert-deftest sage-shell:nthcar-and-rest-test () 526 | (should (equal (sage-shell:nthcar-and-rest 2 '(1 2 3 4 5)) 527 | '((1 2) . (3 4 5)))) 528 | (should (equal (sage-shell:nthcar-and-rest 10 '(1 2 3 4 5)) 529 | '((1 2 3 4 5) . nil)))) 530 | --------------------------------------------------------------------------------