Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.3.5] - 2025-09-04

### Added
- Support for prewarming graphics to improve the performance of first-time graphics rendering
- Support for using Visual Studio Code as the default editor when using the MATLAB `edit` and `open` commands
- Support for highlighting all references to a selected function, variable, class, or class property

### Fixed
- Resolves issue where newly saved document contents are ignored during execution
- Resolves issue where section breaks are not displayed and the `Run Section` command does not work until a file is modified for the first time.
- Applied patch for CVE-2025-54798

## [1.3.4] - 2025-07-31

### Added
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ You also can use this extension along with the Jupyter Extension for Visual Stud
## Configuration
To configure the extension, go to the extension settings and select from the available options.

### MATLAB Default Editor Setting
By default, the extension uses the editor specified in the MATLAB Editor/Debugger settings to open files with the MATLAB `edit` and `open` commands. To make Visual Studio Code the default editor for these commands, set the `MATLAB.defaultEditor` setting to `true`. To revert to using the editor specified in the MATLAB Editor/Debugger settings, set `MATLAB.defaultEditor` to `false`.
**Note:** Certain file types always open in MATLAB by default — for example, live scripts saved in the binary Live Code file format (.mlx) and MATLAB app files (.mlapp).

### MATLAB Install Path Setting
If you have MATLAB installed on your system, the extension automatically checks the system path for the location of the MATLAB executable. If the MATLAB executable is not on the system path, you may need to manually set the `MATLAB.installPath` setting to the full path of your MATLAB installation. For example, `C:\Program Files\MATLAB\R2022b` (Windows®), `/Applications/MATLAB_R2022b.app` (macOS), or `/usr/local/MATLAB/R2022b` (Linux®).

Expand Down Expand Up @@ -98,6 +102,9 @@ You can help improve the extension by sending user experience information to Mat

For more information, see the [MathWorks Privacy Policy](https://www.mathworks.com/company/aboutus/policies_statements.html).

### MATLAB Prewarm Graphics Setting
By default, MATLAB services are started early to improve the first-time performance of MATLAB figure rendering. To disable this behavior, set the `MATLAB.prewarmGraphics` setting to `false`.
This setting is supported with MATLAB R2025a and later. For earlier releases, this setting is ignored.


## Troubleshooting
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Edit MATLAB code with syntax highlighting, linting, navigation support, and more",
"icon": "public/L-Membrane_RGB_128x128.png",
"license": "MIT",
"version": "1.3.4",
"version": "1.3.5",
"engines": {
"vscode": "^1.67.0"
},
Expand Down Expand Up @@ -269,6 +269,18 @@
"default": 0,
"markdownDescription": "The maximum number of characters a file can contain for features such as linting, code navigation, and symbol renaming to be enabled. Use `0` for no limit.",
"scope": "window"
},
"MATLAB.prewarmGraphics": {
"type": "boolean",
"default": true,
"description": "Prewarm graphics at MATLAB startup to improve performance of first-time graphics rendering.",
"scope": "window"
},
"MATLAB.defaultEditor": {
"type": "boolean",
"default": false,
"markdownDescription": "Use Visual Studio Code instead of the MATLAB Editor to open and edit files using the `open` and `edit` commands. Certain file types always open in MATLAB by default — for example, live scripts saved in the binary Live Code file format (.mlx) and MATLAB app files (.mlapp).",
"scope": "window"
}
}
},
Expand Down
155 changes: 155 additions & 0 deletions src/DefaultEditorService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright 2025 The MathWorks, Inc.

import * as fs from 'fs';
import * as os from 'os';
import { exec } from 'child_process';
import * as path from 'path'
import * as vscode from 'vscode'
import { LanguageClient } from 'vscode-languageclient/node';
import { MatlabState, MVM } from './commandwindow/MVM'
import Notification from './Notifications'

export default class DefaultEditorService {
private initialized = false;

constructor (private readonly context: vscode.ExtensionContext, private readonly client: LanguageClient, private readonly mvm: MVM) {
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(() => {
void this.handleConfigChanged()
})
)

mvm.on(MVM.Events.stateChanged, (oldState: MatlabState, newState: MatlabState) => {
if (oldState === newState) {
return;
}

if (newState === MatlabState.READY || newState === MatlabState.BUSY) {
if (!this.initialized) {
this.initialized = true
void this.handleConfigChanged()
}
} else {
this.initialized = false
}
});
}

/** Helper function: checks if a specific path from array of path strings exists and returns true if a path string is found in file system else returns false
* @param paths array of string paths to be checked in file system
* @returns path string if it exists in file system else null
*/
private checkPath (paths: string[]): string | null {
for (const p of paths) {
if (fs.existsSync(p)) {
return p;
}
}
return null;
}

private getFallbackExecutablePaths (): string[] {
const appRoot = vscode.env.appRoot;
const platform = process.platform;
const fallbackPaths: string[] = [];

if (platform === 'win32') {
// appRoot: C:\Program Files\Microsoft VS Code\resources\app
// Executable: C:\Program Files\Microsoft VS Code\Code.exe
fallbackPaths.push(
path.join(path.dirname(path.dirname(appRoot)), 'Code.exe')
);
} else if (platform === 'darwin') {
// appRoot: /Applications/Visual Studio Code.app/Contents/Resources/app
// Executable: /Applications/Visual Studio Code.app/Contents/MacOS/Electron
// or /Applications/Visual Studio Code.app/Contents/MacOS/Visual Studio Code
// Try both
const appDir = path.dirname(path.dirname(appRoot)); // .../Visual Studio Code.app/Contents
fallbackPaths.push(
path.join(appDir, 'MacOS', 'Visual Studio Code'),
path.join(appDir, 'MacOS', 'Electron')
);
} else {
// Linux
// appRoot: /usr/share/code/resources/app
// Executable: /usr/share/code/code
fallbackPaths.push(
path.join(path.dirname(path.dirname(appRoot)), 'code')
);
}
return fallbackPaths
}

/** Look into most probable installation paths based on OS to find VS Code executable path
* @returns path string found for VS Code executable
*/
public getVSCodePath (): Promise<string> {
return new Promise((resolve, reject) => {
const fallbackPaths = this.getFallbackExecutablePaths()

const platform = os.platform();

const command = platform === 'win32' ? 'where code' : 'which code';

exec(command, (error, stdout, stderr) => {
if ((error == null) && typeof stdout === 'string' && stdout !== '') {
resolve(stdout.trim().split('\n').filter(Boolean)[0].trim());
} else {
const fallback = this.checkPath(fallbackPaths);
if (typeof fallback === 'string' && fallback.length > 0) {
resolve(fallback);
} else {
reject(new Error('MATLAB Default Editor: Error getting VS Code executable path'));
}
}
});
});
}

/**
* Handles the notification that MATLAB failed to set VS Code as default editor successfully. This most likely indicates that
* either VS Code is not installed in a default location and 'code' is not added to PATH.
* @param matlabConfig VsCode Workspace object for MATLAB extension configuration
*/
public async handleVsCodePathError (matlabConfig: vscode.WorkspaceConfiguration): Promise<void> {
const message = 'Unable to set MATLAB default editor to Visual Studio Code. Check that VS Code is installed in a default location or add it to the system PATH.'
const availableCmds = await vscode.commands.getCommands()
const options = ['Add VS Code to PATH']

if (availableCmds.includes('workbench.action.installCommandLine')) {
vscode.window.showErrorMessage(message, ...options).then(choice => {
switch (choice) {
case options[0]:
void vscode.commands.executeCommand('workbench.action.installCommandLine')
break
}
}, reject => console.error(reject))
} else {
vscode.window.showErrorMessage(message).then(choice => { /* empty */ }, reject => console.error(reject))
}
void matlabConfig.update('defaultEditor', false, true);
}

/**
* Handles config state management. Finds VS Code executable path when defaultEditor config is enabled and displays an error message if path not found.
*/
public handleConfigChanged (): void {
const configuration = vscode.workspace.getConfiguration('MATLAB')
if ((configuration.get<boolean>('defaultEditor') ?? true)) {
this.getVSCodePath().then(validPath => {
void this.sendExecutablePathNotification(validPath)
}).catch(err => {
console.error(err)
void this.handleVsCodePathError(configuration)
});
}
}

/**
* Sends notification to language server to update default editor to VS Code.
* @param executablePath The path to VS Code
*/
public sendExecutablePathNotification (executablePath: string): void {
void this.client.sendNotification(Notification.EditorExecutablePath, executablePath)
}
}
5 changes: 4 additions & 1 deletion src/Notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ enum Notification {
LicensingServerUrl = 'licensing/server/url',
LicensingData = 'licensing/data',
LicensingDelete = 'licensing/delete',
LicensingError = 'licensing/error'
LicensingError = 'licensing/error',

// Default Editor
EditorExecutablePath = 'matlab/otherEditor'
}

export default Notification
3 changes: 1 addition & 2 deletions src/commandwindow/CommandWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const ACTION_KEYS = {

MOVE_TO_POSITION_IN_LINE: (n: number) => ESC + '[' + n.toString() + 'G',
CLEAR_AND_MOVE_TO_BEGINNING: ESC + '[0G' + ESC + '[0J',
CLEAR_COMPLETELY: ESC + '[2J' + ESC + '[1;1H',
CLEAR_COMPLETELY: ESC + '[2J' + ESC + '[3J' + ESC + '[1;1H',

QUERY_CURSOR: ESC + '[6n',
SET_CURSOR_STYLE_TO_BAR: ESC + '[5 q'
Expand Down Expand Up @@ -707,7 +707,6 @@ export default class CommandWindow implements vscode.Pseudoterminal {
*/
clear (): void {
this._writeEmitter.fire(ACTION_KEYS.CLEAR_COMPLETELY)
void vscode.commands.executeCommand('workbench.action.terminal.clear');
this._setToEmptyPrompt();
}

Expand Down
5 changes: 5 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import DeprecationPopupService from './DeprecationPopupService'
import { SectionModel } from './model/SectionModel'
import SectionStylingService from './styling/SectionStylingService'
import MatlabDebugger from './debug/MatlabDebugger'
import DefaultEditorService from './DefaultEditorService'

let client: LanguageClient
const OPEN_SETTINGS_ACTION = 'workbench.action.openSettings'
Expand All @@ -37,6 +38,8 @@ let telemetryLogger: TelemetryLogger

let deprecationPopupService: DeprecationPopupService

let defaultEditorService: DefaultEditorService

let sectionModel: SectionModel;
let sectionStylingService: SectionStylingService;

Expand Down Expand Up @@ -144,6 +147,8 @@ export async function activate (context: vscode.ExtensionContext): Promise<void>
deprecationPopupService = new DeprecationPopupService(context)
deprecationPopupService.initialize(client)

defaultEditorService = new DefaultEditorService(context, client, mvm)

sectionModel = new SectionModel()
sectionModel.initialize(client as Notifier);
context.subscriptions.push(sectionModel)
Expand Down
5 changes: 1 addition & 4 deletions src/test/tools/tester/TestSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ export class TestSuite {
private readonly releaseQuality: ReleaseQuality

public constructor () {
this.storageFolder = path.join(__dirname, '..', '..', '..', '..', '.vscode-test', 'test-resources')
if (os.platform() === 'darwin') {
this.storageFolder = os.tmpdir()
}
this.storageFolder = path.join(__dirname, '..', '..', '..', '..', 's')
this.mochaConfig = path.join(__dirname, '..', 'config', '.mocharc.js')
const pjson = require(path.resolve('package.json')) // eslint-disable-line
this.vsixPath = path.resolve(`${pjson.name}-${pjson.version}.vsix`) // eslint-disable-line
Expand Down
23 changes: 22 additions & 1 deletion src/test/tools/tester/VSCodeTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,25 @@ export class VSCodeTester {
return await new vet.EditorView().closeEditor('Settings')
}

public async assertDecorationOnLine (n: number, message = ''): Promise<void> {
return await this.poll(this.lineHasDecoration.bind(this, n), true, `Expected line ${n} to have decoration. ${message}`)
}

private async lineHasDecoration (n: number): Promise<boolean> {
const editor = new vet.TextEditor()
const lines = await editor.findElements(vet.By.css('div.view-lines > div'));
if (n < 1 || n > lines.length) return false;

const spans = await lines[n - 1].findElements(vet.By.css('span'));
for (const span of spans) {
const classAttr = await span.getAttribute('class');
if (classAttr?.includes('TextEditorDecoration')) {
return true;
}
}
return false;
}

/**
* Poll for a function return the expected value. Default timeout is 30s
*/
Expand All @@ -175,7 +194,9 @@ export class VSCodeTester {
}

if (result !== value) {
await this.browser.takeScreenshot(result)
const filename = `test_failure_${new Date().toISOString().replace(/[:.]/g, '-')}`
await this.browser.takeScreenshot(filename)
console.log(`Screenshot saved as ${filename}.png`)
} else {
if (message !== '') {
console.log(`Assertion passed: ${message}`)
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/debugging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { VSCodeTester } from '../tools/tester/VSCodeTester'
import { before, afterEach, after } from 'mocha';

suite('Debugging Smoke Tests', () => {
suite('Debugging UI Tests', () => {
let vs: VSCodeTester

before(async () => {
Expand Down
Loading