├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── images └── icon.png ├── interfaces ├── standard │ ├── check_dependencies.py │ ├── matlab_interface.py │ ├── ml_script.py │ ├── ml_selection.py │ └── ml_terminal.py └── unicode │ ├── check_dependencies.py │ ├── matlab_interface.py │ ├── ml_script.py │ ├── ml_selection.py │ └── ml_terminal.py ├── package-lock.json ├── package.json ├── src ├── extension.ts └── test │ ├── runTest.ts │ └── suite │ ├── extension.test.ts │ └── index.ts ├── tsconfig.json └── vsc-extension-quickstart.md /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "project": "tsconfig.json", 9 | "sourceType": "module" 10 | }, 11 | "plugins": [ 12 | "@typescript-eslint" 13 | ], 14 | "rules": { 15 | "@typescript-eslint/member-delimiter-style": [ 16 | "warn", 17 | { 18 | "multiline": { 19 | "delimiter": "semi", 20 | "requireLast": true 21 | }, 22 | "singleline": { 23 | "delimiter": "semi", 24 | "requireLast": false 25 | } 26 | } 27 | ], 28 | "@typescript-eslint/naming-convention": "warn", 29 | "@typescript-eslint/no-unused-expressions": "warn", 30 | "@typescript-eslint/semi": [ 31 | "warn", 32 | "always" 33 | ], 34 | "curly": "warn", 35 | "eqeqeq": [ 36 | "warn", 37 | "always" 38 | ], 39 | "no-redeclare": "warn", 40 | "no-throw-literal": "warn", 41 | "no-unused-expressions": "warn", 42 | "semi": "warn" 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.{cmd,[cC][mM][dD]} text eol=crlf 3 | *.{bat,[bB][aA][tT]} text eol=crlf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | __pycache__/ 6 | */__pycache__/ 7 | *.pyc -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "${defaultBuildTask}" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "${defaultBuildTask}" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "typescript.tsdk": "node_modules/typescript/lib" 12 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts 11 | __pycache__/ 12 | */__pycache__/ 13 | *.pyc -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the Matlab Interactive Terminal extension for Visual Studio Code will be documented in this file. 4 | 5 | ## Master 6 | 7 | ## [0.4.0] - 2021-08-08 8 | ### Added 9 | - Terminal profile support (SNDST00M #37) 10 | ### Improved 11 | - Boot experience and help messages (4086606 #34) 12 | 13 | ## [0.3.4] - 2020-12-18 14 | ### Fixed 15 | - Cannot find Python path caused by encoding errors (fixed by TheBrenny #27) 16 | 17 | ## [0.3.3] - 2020-10-11 18 | ### Added 19 | - Option to choose to switch focus to terminal or not (weihongliang233 #24) 20 | ### Fixed 21 | - Cases where run selection was running behind #12 (fixed by weihongliang233 #19) 22 | 23 | ## [0.3.2] - 2020-04-26 24 | ### Added 25 | - Added option to use unicode output, as if activated the output is not in real-time 26 | ### Fixed 27 | - Consolidated temp file managing for run selection 28 | - Corrected errors when spaces were present in the path to the path from which a selection was run 29 | 30 | ## [0.3.1] - 2020-04-18 31 | ### Fixed 32 | - Properly displays error messages with unicode 33 | 34 | ## [0.3.0] - 2020-04-18 35 | ### Added 36 | - Correct implementation and handling of unicode, including CJK, characters 37 | ### Fixed 38 | - Impossibility to launch from previous update 39 | 40 | ## [0.2.11] - 2020-04-18 41 | ### Fixed 42 | - Prompt locations in some cases #6 43 | - Relative path issues in run selection #8 44 | 45 | ## [0.2.10] - 2020-02-26 46 | ### Added 47 | - When a script is run, it is saved beforehand in order to be correctly updated 48 | ### Fixed 49 | - Fixed discrepancies in run selection by using temporary files 50 | - Updated readme 51 | 52 | ## [0.2.9] - 2020-02-13 53 | ### Added 54 | - The script calling is hidden at initialization 55 | ### Fixed 56 | - Implemented correctly Matlab `clc` command 57 | - The terminal is correctly shown in certain situations 58 | 59 | ## [0.2.8] - 2020-02-07 60 | ### Added 61 | - Added basic Python 2 compatibility 62 | - Added prompts in the Matlab terminal Issue #2 63 | 64 | ## [0.2.7] - 2020-02-03 65 | ### Added 66 | - Added option to specify the Python executable to use in case of several versions Issue #1 67 | 68 | ## [0.2.6] - 2020-01-19 69 | ### Fixed 70 | - Resolve encoding issues when running selection in new terminal 71 | 72 | ## [0.2.5] - 2020-01-16 73 | ### Fixed 74 | - The `Run selection` command should now handle properly the double quotes, by using a temporary file 75 | 76 | ## [0.2.4] - 2020-01-15 77 | ### Fixed 78 | - The exceptions are now properly handled when a file or a selection are run 79 | - Cleaned code with TSLint 80 | 81 | ## [0.2.3] - 2020-01-15 82 | ### Fixed 83 | - Fixed bug where terminal was launching when dependencies were not satisfied 84 | 85 | ## [0.2.2] - 2020-01-15 86 | ### Added 87 | - Reworked TypeScript code 88 | ### Fixed 89 | - Multi-lines selections when no terminal is open 90 | - Spaces in paths and instructions on all cases 91 | - Corrected bug in Python related to Matlab error handling 92 | 93 | ## [0.2.1] - 2020-01-14 94 | ### Fixed 95 | - Fixed a bug where the selected text could not be run in an empty terminal if it contained spaces 96 | 97 | ## [0.2.0] - 2020-01-14 98 | ### Added 99 | - Implemented better code structure for the Python scripts 100 | - Added command to launch current selection or current line in Matlab REPL 101 | 102 | ## [0.1.4] - 2020-01-14 103 | ### Fixed 104 | - The matlab scripts are now properly reloaded when a script is launched in an existing terminal 105 | 106 | ## [0.1.3] - 2020-01-13 107 | Initial release 108 | - Created extension structure 109 | - Implemented `Open a Matlab Terminal` command 110 | - Implemented `Run current Matlab Script` command 111 | - Added basic dependencies checks at activation to prevent errors -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Aurélien Pommel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Matlab Interactive Terminal for Visual Studio Code 2 | 3 | Matlab Interactive Terminal is an extension for Visual Studio Code that allows users to launch MATLAB scripts and have a working MATLAB REPL directly included in Visual Studio Code. This extension uses the MATLAB Engine for Python which must be correctly set up for the extension to work. It works equally on Windows, macOS and Linux. 4 | 5 | ## Requirements 6 | 7 | - **Python** x64 2.7, 3.7 or 3.8 (added to PATH), available [here](https://www.python.org/downloads/) or through other distributions such as [Anaconda](https://www.anaconda.com/distribution/) 8 | - **MATLAB** R2014b (Matlab 8.4) or higher 9 | - **MATLAB Engine API for Python**, installations instructions are available [here](https://www.mathworks.com/help/matlab/matlab_external/install-the-matlab-engine-for-python.html) 10 | 11 | N.B.: The Python requirements may vary depending on the installed MATLAB version. More information about MATLAB-Python compatibility is available on [MathWorks website](https://www.mathworks.com/help/matlab/matlab_external/system-requirements-for-matlab-engine-for-python.html). 12 | 13 | ## Features 14 | 15 | The extension adds three commands to Visual Studio Code, that can then be tied to key-bindings. These are: 16 | - `Open a MATLAB Terminal` which opens an interactive MATLAB terminal in the VS Code integrated terminal, similar to the MATLAB command line 17 | - `Run current MATLAB Script` which runs the currently opened MATLAB script and then allows the user to interact with it through the opened terminal 18 | - `Run current selection in MATLAB` which runs the currently selected text in a MATLAB terminal. If no text is selected, the current line is run instead 19 | 20 | ## Recommended VS Code Extensions 21 | 22 | - [Matlab](https://marketplace.visualstudio.com/items?itemName=Gimly81.matlab) (from Xavier Hahn) provides syntax coloration, snippets and linting for the Matlab language. Matlab Interactive Terminal is thought for working with it. 23 | - [Matlab Code Run](https://marketplace.visualstudio.com/items?itemName=bramvanbilsen.matlab-code-run) (from Bram Vanbilsen) is a good alternative to Matlab Interactive Terminal without the Python and Matlab Engine API dependencies. It should provide similar functionalities on both Mac OS and Linux. 24 | 25 | ## Known Issues 26 | 27 | See https://github.com/apommel/vscode-matlab-interactive-terminal/issues 28 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apommel/vscode-matlab-interactive-terminal/b349610b7c9c7c7fc2acb9eeb6c8acef2cf6b313/images/icon.png -------------------------------------------------------------------------------- /interfaces/standard/check_dependencies.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | # Checks if the necessary Python dependencies are present on the system at activation 4 | try: # Check if the Matlab Engine is installed 5 | import matlab.engine 6 | except ImportError: 7 | print('1') 8 | else: 9 | print('0') -------------------------------------------------------------------------------- /interfaces/standard/matlab_interface.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | # Trying to have a basic Python 2 compatibility 4 | from __future__ import print_function 5 | 6 | try: 7 | input = raw_input 8 | except NameError: 9 | pass 10 | 11 | import os 12 | from textwrap import dedent 13 | 14 | global import_fail 15 | try: # Check if the Matlab Engine is installed 16 | import matlab.engine 17 | from matlab.engine import RejectedExecutionError as MatlabTerminated 18 | except ImportError: 19 | print("MATLAB Engine for Python cannot be detected. Please install it for the extension to work.") 20 | import_fail = True 21 | else: 22 | import_fail = False 23 | 24 | class MatlabInterface: 25 | global import_fail 26 | 27 | def __init__(self): 28 | # OS checks related work 29 | if os.name == 'nt': 30 | self.cls_str = 'cls' 31 | else: 32 | self.cls_str = 'clear' 33 | 34 | self.clear() 35 | 36 | if not import_fail: 37 | try: 38 | self.eng = matlab.engine.start_matlab() 39 | intro = '''\ 40 | MATLAB Interactive Terminal (R{release}) 41 | 42 | To get started, type one of these commands: 43 | helpwin Provide access to help comments for all functions 44 | helpdesk Open help browser 45 | demo Access product examples in Help browser 46 | 47 | For product information, visit https://www.mathworks.com.\ 48 | '''.format(release=self.release()) 49 | print(dedent(intro)) 50 | 51 | except MatlabTerminated as e: 52 | self.clear() 53 | print(str(e)) 54 | print("MATLAB Engine for Python exited prematurely.") 55 | 56 | else: 57 | print("Launching MATLAB failed: Error starting MATLAB process in MATLAB Engine for Python.") 58 | 59 | def clear(self): 60 | os.system(self.cls_str) 61 | 62 | def release(self): 63 | release_str = "version('-release');" 64 | res = self.eng.eval(release_str) 65 | return res 66 | 67 | def run_script(self, script_path): 68 | if not import_fail: 69 | try: 70 | print("File: \"%s\""%script_path) 71 | self.eng.run(script_path, nargout=0) 72 | 73 | except MatlabTerminated: 74 | print("MATLAB process was terminated. Restarting the engine...") 75 | self.eng = matlab.engine.start_matlab() 76 | print("MATLAB process restarted.") 77 | 78 | except : # The other exceptions are handled by Matlab 79 | pass 80 | 81 | def run_selection(self, temp_path): 82 | if not import_fail: 83 | f = open(temp_path, 'r') 84 | print("Running current selection in Visual Studio Code:") 85 | 86 | try: # Print the content of the selection before running it, encoding issues can happen 87 | print('') 88 | for line in f: 89 | print(' ' + line, end='') 90 | 91 | except UnicodeDecodeError as err: 92 | print('') 93 | print(str(err)) 94 | 95 | print('\n') 96 | f.close() 97 | 98 | try: 99 | self.eng.run(temp_path, nargout=0) 100 | 101 | except MatlabTerminated: 102 | print("MATLAB process terminated.") 103 | print("Restarting MATLAB Engine for Python...") 104 | self.eng = matlab.engine.start_matlab() 105 | print("Restarted MATLAB process.") 106 | 107 | except : # The other exceptions are handled by Matlab 108 | pass 109 | 110 | finally: 111 | try: 112 | os.remove(temp_path) 113 | os.rmdir(os.path.dirname(temp_path)) 114 | 115 | except OSError: 116 | pass 117 | 118 | def interactive_loop(self): 119 | loop=True # Looping allows for an interactive terminal 120 | 121 | while loop and not import_fail: 122 | print('>>> ', end='') 123 | command = input() 124 | 125 | if command=="exit" or command=="exit()": # Keywords to leave the engine 126 | loop=False 127 | 128 | elif command=="clc" or command=="clc()": # matlab terminal clearing must be reimplemented 129 | self.clear() 130 | 131 | else: 132 | try: 133 | self.eng.eval(command, nargout=0) # Feed the instructions to Matlab eval 134 | 135 | except MatlabTerminated: 136 | print("MATLAB process terminated.") 137 | print("Restarting MATLAB Engine for Python...") 138 | self.eng = matlab.engine.start_matlab() 139 | print("Restarted MATLAB process.") 140 | 141 | except : # The other exceptions are handled by Matlab 142 | pass 143 | 144 | if not import_fail: self.eng.quit() 145 | -------------------------------------------------------------------------------- /interfaces/standard/ml_script.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | from matlab_interface import MatlabInterface 4 | import sys 5 | 6 | matlab = MatlabInterface() 7 | # Run the initial script given in arg 8 | matlab.run_script(sys.argv[1]) 9 | matlab.interactive_loop() 10 | -------------------------------------------------------------------------------- /interfaces/standard/ml_selection.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | from matlab_interface import MatlabInterface 4 | import sys 5 | 6 | matlab = MatlabInterface() 7 | # Run the initial selection given in arg 8 | matlab.run_selection(sys.argv[1]) 9 | matlab.interactive_loop() -------------------------------------------------------------------------------- /interfaces/standard/ml_terminal.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | from matlab_interface import MatlabInterface 4 | 5 | matlab = MatlabInterface() 6 | matlab.interactive_loop() -------------------------------------------------------------------------------- /interfaces/unicode/check_dependencies.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | # Checks if the necessary Python dependencies are present on the system at activation 4 | try: # Check if the Matlab Engine is installed 5 | import matlab.engine 6 | except ImportError: 7 | print('1') 8 | else: 9 | print('0') -------------------------------------------------------------------------------- /interfaces/unicode/matlab_interface.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | # Trying to have a basic Python 2 compatibility 4 | from __future__ import print_function 5 | try: 6 | input = raw_input 7 | except NameError: 8 | pass 9 | 10 | import os 11 | from io import StringIO 12 | from textwrap import dedent 13 | 14 | global import_fail 15 | try: # Check if the Matlab Engine is installed 16 | import matlab.engine 17 | from matlab.engine import RejectedExecutionError as MatlabTerminated 18 | except ImportError: 19 | print("MATLAB Engine for Python cannot be detected. Please install it for the extension to work.") 20 | import_fail = True 21 | else: 22 | import_fail = False 23 | 24 | class MatlabInterface: 25 | global import_fail 26 | 27 | def __init__(self): 28 | # OS checks related work 29 | if os.name == 'nt': 30 | self.cls_str = 'cls' 31 | else: 32 | self.cls_str = 'clear' 33 | self.clear() 34 | 35 | if not import_fail: 36 | try: 37 | self.eng = matlab.engine.start_matlab() 38 | intro = '''\ 39 | MATLAB Interactive Terminal (R{release}) 40 | 41 | To get started, type one of these commands: 42 | helpwin Provide access to help comments for all functions 43 | helpdesk Open help browser 44 | demo Access product examples in Help browser 45 | 46 | For product information, visit https://www.mathworks.com.\ 47 | '''.format(release=self.release()) 48 | print(dedent(intro)) 49 | 50 | except Exception as e: 51 | self.clear() 52 | print("MATLAB Engine for Python exited prematurely.") 53 | print(e) 54 | 55 | else: 56 | print("Launching MATLAB failed: Error starting MATLAB process in MATLAB Engine for Python.") 57 | 58 | def clear(self): 59 | os.system(self.cls_str) 60 | 61 | def release(self): 62 | release_str = "version('-release');" 63 | res = self.eng.eval(release_str) 64 | return res 65 | 66 | def run_script(self, script_path): 67 | if not import_fail: 68 | 69 | try: 70 | print("File: \"{}\"".format(script_path)) 71 | stream = StringIO() 72 | err_stream = StringIO() 73 | self.eng.run(script_path, nargout=0, stdout=stream, stderr=err_stream) 74 | print(stream.getvalue()) 75 | 76 | except MatlabTerminated: 77 | print(stream.getvalue(), err_stream.getvalue(), sep="\n") 78 | print("MATLAB process terminated.") 79 | print("Restarting MATLAB Engine for Python...") 80 | self.eng = matlab.engine.start_matlab() 81 | print("Restarted MATLAB process.") 82 | 83 | except : # The other exceptions are handled by Matlab 84 | print(stream.getvalue(), err_stream.getvalue(), sep="\n") 85 | 86 | def run_selection(self, temp_path): 87 | if not import_fail: 88 | f = open(temp_path, 'r') 89 | print("Running:") 90 | 91 | try: # Print the content of the selection before running it, encoding issues can happen 92 | for line in f: 93 | print(line, end='') 94 | 95 | except UnicodeDecodeError: 96 | print("current selection") 97 | 98 | print('\n') 99 | f.close() 100 | 101 | try: 102 | stream = StringIO() 103 | err_stream = StringIO() 104 | self.eng.run(temp_path, nargout=0, stdout=stream, stderr=err_stream) 105 | print(stream.getvalue()) 106 | 107 | except MatlabTerminated: 108 | print(stream.getvalue(), err_stream.getvalue(), sep="\n") 109 | print("MATLAB terminated. Restarting the engine...") 110 | self.eng = matlab.engine.start_matlab() 111 | print("MATLAB restarted") 112 | 113 | except : # The other exceptions are handled by Matlab 114 | print(stream.getvalue(), err_stream.getvalue(), sep="\n") 115 | 116 | finally: 117 | os.remove(temp_path) 118 | os.rmdir(os.path.dirname(temp_path)) 119 | 120 | def interactive_loop(self): 121 | loop=True # Looping allows for an interactive terminal 122 | 123 | while loop and not import_fail: 124 | print('>>> ', end='') 125 | command = input() 126 | 127 | if command=="exit" or command=="exit()": # Keywords to leave the engine 128 | loop=False 129 | 130 | elif command=="clc" or command=="clc()": # matlab terminal clearing must be reimplemented 131 | self.clear() 132 | 133 | else: 134 | try: 135 | stream = StringIO() 136 | err_stream = StringIO() 137 | self.eng.eval(command, nargout=0, stdout=stream, stderr=err_stream) # Feed the instructions to Matlab eval 138 | print(stream.getvalue()) 139 | 140 | except MatlabTerminated: 141 | print(stream.getvalue(), err_stream.getvalue(), sep="\n") 142 | print("MATLAB process terminated.") 143 | print("Restarting MATLAB Engine for Python...") 144 | self.eng = matlab.engine.start_matlab() 145 | print("Restarted MATLAB process.") 146 | 147 | except : # The other exceptions are handled by Matlab 148 | print(stream.getvalue(), err_stream.getvalue(), sep="\n") 149 | 150 | if not import_fail: self.eng.quit() 151 | -------------------------------------------------------------------------------- /interfaces/unicode/ml_script.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | from matlab_interface import MatlabInterface 4 | import sys 5 | 6 | matlab = MatlabInterface() 7 | # Run the initial script given in arg 8 | matlab.run_script(sys.argv[1]) 9 | matlab.interactive_loop() 10 | -------------------------------------------------------------------------------- /interfaces/unicode/ml_selection.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | from matlab_interface import MatlabInterface 4 | import sys 5 | 6 | matlab = MatlabInterface() 7 | # Run the initial selection given in arg 8 | matlab.run_selection(sys.argv[1]) 9 | matlab.interactive_loop() -------------------------------------------------------------------------------- /interfaces/unicode/ml_terminal.py: -------------------------------------------------------------------------------- 1 | # Developed by Aurelien Pommel and other contributors 2 | 3 | from matlab_interface import MatlabInterface 4 | 5 | matlab = MatlabInterface() 6 | matlab.interactive_loop() -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matlab-interactive-terminal", 3 | "displayName": "Matlab Interactive Terminal", 4 | "description": "Interactive MATLAB terminal for Visual Studio Code, compatible with Windows, Mac and Linux", 5 | "version": "0.4.0", 6 | "publisher": "apommel", 7 | "license": "MIT", 8 | "icon": "images/icon.png", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/apommel/vscode-matlab-interactive-terminal.git" 12 | }, 13 | "engines": { 14 | "vscode": "^1.59.0" 15 | }, 16 | "categories": [ 17 | "Programming Languages", 18 | "Other" 19 | ], 20 | "activationEvents": [ 21 | "onLanguage:matlab", 22 | "onCommand:matlab-interactive-terminal.openMatlabTerminal", 23 | "onCommand:matlab-interactive-terminal.runMatlabScript", 24 | "onCommand:matlab-interactive-terminal.runMatlabSelection", 25 | "onTerminalProfile:matlab-interactive-terminal.terminal-profile" 26 | ], 27 | "main": "./out/extension.js", 28 | "contributes": { 29 | "commands": [ 30 | { 31 | "command": "matlab-interactive-terminal.openMatlabTerminal", 32 | "title": "Open a MATLAB Terminal" 33 | }, 34 | { 35 | "command": "matlab-interactive-terminal.runMatlabScript", 36 | "title": "Run current MATLAB Script" 37 | }, 38 | { 39 | "command": "matlab-interactive-terminal.runMatlabSelection", 40 | "title": "Run current selection in MATLAB" 41 | } 42 | ], 43 | "configuration": { 44 | "title": "Matlab Interactive Terminal", 45 | "properties": { 46 | "matlab-interactive-terminal.pythonPath": { 47 | "type": "string", 48 | "default": null, 49 | "description": "Specifies the path to the Python executable to use (if leaved blank, Python in PATH is used)" 50 | }, 51 | "matlab-interactive-terminal.unicodeSwitch": { 52 | "type": "boolean", 53 | "default": "false", 54 | "description": "Displays Unicode characters (ex: CJK characters) in MATLAB output (N.B.: output will not be in real time)" 55 | }, 56 | "matlab-interactive-terminal.CursorBack": { 57 | "type": "boolean", 58 | "default": "true", 59 | "description": "Decides whether to make the cursor fall back onto your editor when you execute the command 'Run current selection' " 60 | } 61 | } 62 | }, 63 | "terminal": { 64 | "profiles": [ 65 | { 66 | "id": "matlab-interactive-terminal.terminal-profile", 67 | "icon": "variable-group", 68 | "title": "MATLAB" 69 | } 70 | ] 71 | } 72 | }, 73 | "scripts": { 74 | "vscode:prepublish": "npm run compile", 75 | "compile": "tsc -p ./", 76 | "watch": "tsc -watch -p ./", 77 | "pretest": "npm run compile", 78 | "test": "node ./out/test/runTest.js" 79 | }, 80 | "devDependencies": { 81 | "@types/glob": "^7.1.3", 82 | "@types/mocha": "^5.2.7", 83 | "@types/node": "^12.12.67", 84 | "@types/vscode": "^1.59.0", 85 | "@typescript-eslint/eslint-plugin": "^4.28.0", 86 | "@typescript-eslint/parser": "^4.28.0", 87 | "eslint": "^7.30.0", 88 | "glob": "^7.1.5", 89 | "mocha": "^6.2.3", 90 | "typescript": "^4.3.5", 91 | "vscode-test": "^1.4.0" 92 | }, 93 | "dependencies": {} 94 | } 95 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | // Developed by Aurelien Pommel and other contributors 2 | 3 | // The module 'vscode' contains the VS Code extensibility API 4 | // Import the module and reference it with the alias vscode in your code below 5 | import * as vscode from "vscode"; 6 | import * as path from "path"; 7 | import * as util from "util"; 8 | import * as fs from "fs"; 9 | 10 | // this method is called when your extension is activated 11 | // your extension is activated the very first time the command is executed 12 | export function activate(context: vscode.ExtensionContext) { 13 | 14 | // Get basic directory information 15 | const extDir = context.asAbsolutePath(""); 16 | let scriptDir = path.join(extDir, "/interfaces/standard"); 17 | 18 | // Get configuration parameters 19 | const getConfigMap = () => { 20 | return vscode.workspace.getConfiguration("matlab-interactive-terminal"); 21 | }; 22 | 23 | // Get Python interpreter path 24 | const getPythonPath = () => { 25 | let extConfig = getConfigMap(); 26 | let pythonPath: string; 27 | let pythonPathSetting: string | undefined; 28 | pythonPathSetting = extConfig.get("pythonPath"); 29 | 30 | pythonPath = pythonPathSetting 31 | ? path.normalize(pythonPathSetting) 32 | : "python"; 33 | 34 | return pythonPath; 35 | }; 36 | let pythonPath: string | undefined = getPythonPath(); 37 | 38 | // Set up common terminal options 39 | let terminalLaunchOptions: vscode.TerminalOptions = { 40 | name: "MATLAB", 41 | shellPath: pythonPath 42 | }; 43 | 44 | const getUnicodeOption = () => { 45 | let extConfig = getConfigMap(); 46 | let unicodeOption: boolean | undefined; 47 | unicodeOption = extConfig.get("unicodeSwitch"); 48 | if (unicodeOption === undefined) { 49 | unicodeOption = false; 50 | } 51 | if (unicodeOption) { 52 | scriptDir = path.join(extDir, "/interfaces/unicode"); 53 | } 54 | else { 55 | scriptDir = path.join(extDir, "/interfaces/standard"); 56 | } 57 | }; 58 | getUnicodeOption(); 59 | 60 | // Check the dependencies and inform the user 61 | let errMessage = ""; 62 | let correctSetup: boolean; 63 | 64 | const checkSetup = () => { 65 | let scriptPath = path.join(scriptDir, "check_dependencies.py"); 66 | const { execFileSync } = require("child_process"); 67 | if (!pythonPath) { 68 | return false; 69 | } 70 | try { 71 | let stdout = execFileSync(pythonPath, [scriptPath]); 72 | if (stdout.toString() === "1") { 73 | errMessage = "MATLAB Engine for Python seems to not be installed correctly."; 74 | return false; 75 | } 76 | } 77 | catch (error) { // If an error is caught, it means Python cannot be called 78 | errMessage = error.message; 79 | return false; 80 | } 81 | return true; 82 | }; 83 | 84 | const openMatlabTerminal = () => { 85 | getUnicodeOption(); 86 | let scriptPath = path.join(scriptDir, "ml_terminal.py"); 87 | pythonPath = getPythonPath(); 88 | correctSetup = checkSetup(); 89 | if (correctSetup) { 90 | const opts: vscode.TerminalOptions = { ...terminalLaunchOptions }; 91 | opts.shellPath = pythonPath; 92 | opts.shellArgs = [scriptPath]; 93 | vscode.window.createTerminal(opts).show(false); 94 | } 95 | else { 96 | console.log(errMessage); 97 | vscode.window.showErrorMessage(errMessage); 98 | } 99 | }; 100 | context.subscriptions.push(vscode.commands.registerCommand("matlab-interactive-terminal.openMatlabTerminal", openMatlabTerminal)); 101 | 102 | 103 | const terminalFallback = (activeTerminal: vscode.Terminal | undefined) => { 104 | // The terminal is not opened if there is already a current one 105 | if (activeTerminal === undefined || (activeTerminal && activeTerminal.name !== "MATLAB")) { 106 | openMatlabTerminal(); 107 | } 108 | }; 109 | 110 | 111 | const runMatlabScript = () => { 112 | let activeTextEditor = vscode.window.activeTextEditor; 113 | let activeTerminal = vscode.window.activeTerminal; 114 | if (activeTextEditor) { 115 | activeTextEditor.document.save(); 116 | let currentFile = activeTextEditor.document.fileName; 117 | getUnicodeOption(); 118 | let scriptPath = path.join(scriptDir, "ml_script.py"); 119 | if (activeTerminal && activeTerminal.name === "MATLAB") // If already a Matlab Engine started, the file is run in it 120 | { 121 | activeTerminal.sendText("clear functions"); // Force Matlab to reload the scripts 122 | activeTerminal.sendText(util.format("run(\"%s\")", currentFile)); 123 | activeTerminal.show(false); 124 | } 125 | else { 126 | pythonPath = getPythonPath(); 127 | correctSetup = checkSetup(); 128 | if (correctSetup) { 129 | const opts: vscode.TerminalOptions = { ...terminalLaunchOptions }; 130 | opts.shellPath = pythonPath; 131 | opts.shellArgs = [scriptPath, currentFile]; 132 | vscode.window.createTerminal(opts).show(false); 133 | } 134 | else { 135 | console.log(errMessage); 136 | vscode.window.showErrorMessage(errMessage); 137 | } 138 | } 139 | } 140 | else { // If not any file is opened, a Matlab terminal is simply opened 141 | terminalFallback(activeTerminal); 142 | } 143 | }; 144 | context.subscriptions.push(vscode.commands.registerCommand("matlab-interactive-terminal.runMatlabScript", runMatlabScript)); 145 | 146 | 147 | const runMatlabSelection = () => { 148 | let activeTextEditor = vscode.window.activeTextEditor; 149 | let activeTerminal = vscode.window.activeTerminal; 150 | if (activeTextEditor) { 151 | var currentSelection = null; 152 | var cwd = path.dirname(activeTextEditor.document.uri.fsPath); // Get current file directory 153 | if (cwd.charAt(1) === ":") { // Hack to have drive letter in uppercase 154 | cwd = cwd.charAt(0).toUpperCase() + cwd.slice(1); 155 | } 156 | if (activeTextEditor.selection.isEmpty) { // Run current line if selection is empty 157 | currentSelection = activeTextEditor.document.lineAt(activeTextEditor.selection.active).text; 158 | } 159 | else { 160 | currentSelection = `cd \'${cwd}\'\n`.concat(activeTextEditor.document.getText(activeTextEditor.selection)); 161 | } 162 | getUnicodeOption(); 163 | let scriptPath = path.join(scriptDir, "ml_selection.py"); 164 | let tempDir = path.join(extDir, "temp"); // A temp file and directory are created in the ext dir 165 | if (!fs.existsSync(tempDir)) { 166 | fs.mkdirSync(tempDir); 167 | } 168 | else { 169 | var fileList = fs.readdirSync(tempDir); 170 | for (const file of fileList) { 171 | fs.unlinkSync(path.join(tempDir, file)); 172 | } 173 | } 174 | let tempPath = path.join(tempDir, "temp.m"); 175 | fs.writeFileSync(tempPath, currentSelection); 176 | if (activeTerminal && activeTerminal.name === "MATLAB") { // If already a Matlab Engine started, the selection is run in it 177 | activeTerminal.sendText(util.format("clear(\"%s\")", tempPath)); // Force Matlab to reload the scripts 178 | activeTerminal.sendText(util.format("run(\"%s\")", tempPath)); 179 | if (getConfigMap().get("CursorBack") === false) { 180 | activeTerminal.show(false); 181 | } 182 | } 183 | else { 184 | pythonPath = getPythonPath(); 185 | correctSetup = checkSetup(); 186 | if (correctSetup) { 187 | const opts: vscode.TerminalOptions = { ...terminalLaunchOptions }; 188 | opts.shellPath = pythonPath; 189 | opts.shellArgs = [scriptPath, tempPath]; 190 | vscode.window.createTerminal(opts).show(false); 191 | } 192 | else { 193 | console.log(errMessage); 194 | vscode.window.showErrorMessage(errMessage); 195 | } 196 | } 197 | } 198 | else { 199 | terminalFallback(activeTerminal); 200 | } 201 | }; 202 | context.subscriptions.push(vscode.commands.registerCommand("matlab-interactive-terminal.runMatlabSelection", runMatlabSelection)); 203 | 204 | const resetPythonPath = () => { 205 | pythonPath = getPythonPath(); 206 | }; 207 | context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(resetPythonPath)); 208 | 209 | const matlabTerminalProfile: vscode.TerminalProfileProvider = { 210 | provideTerminalProfile() { 211 | let terminalPath = path.join(scriptDir, "ml_terminal.py"); 212 | return { 213 | options: { 214 | ...terminalLaunchOptions, 215 | shellArgs: [terminalPath] 216 | } 217 | }; 218 | } 219 | }; 220 | context.subscriptions.push(vscode.window.registerTerminalProfileProvider('matlab-interactive-terminal.terminal-profile', matlabTerminalProfile)); 221 | 222 | } 223 | 224 | // this method is called when your extension is deactivated 225 | export function deactivate() { } 226 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.equal(-1, [1, 2, 3].indexOf(5)); 13 | assert.equal(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | }); 10 | mocha.useColors(true); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | --------------------------------------------------------------------------------