Skip to content

Commit

Permalink
feat: skip files while debugging (#874)
Browse files Browse the repository at this point in the history
* Add support for justMyCode where ignored files are stepInto-ed.
Small refactor of log point evaluation code.

* Changelog and Readme.

* Renamed justMyCode to skipFiles same as javascript debugger.

* Add support for pause and stepping with the same command that started the skipping process.

* Extract matching function and tests.
  • Loading branch information
zobo authored Dec 29, 2022
1 parent 622c3e4 commit af38cb8
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.30.0]

- Add skipFiles launch setting to skip over specified file patterns.

## [1.29.1]

- Fix for env configuration check that sometimes causes an error.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ More general information on debugging with VS Code can be found on https://code.
- `pathMappings`: A list of server paths mapping to the local source paths on your machine, see "Remote Host Debugging" below
- `log`: Whether to log all communication between VS Code and the adapter to the debug console. See _Troubleshooting_ further down.
- `ignore`: An optional array of glob patterns that errors should be ignored from (for example `**/vendor/**/*.php`)
- `skipFiles`: An array of glob patterns, to skip when debugging. Star patterns and negations are allowed, for example, `**/vendor/**` or `!**/vendor/my-module/**`.
- `maxConnections`: Accept only this number of parallel debugging sessions. Additional connections will be dropped and their execution will continue without debugging.
- `proxy`: DBGp Proxy settings
- `enable`: To enable proxy registration set to `true` (default is `false).
Expand Down
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,14 @@
"**/vendor/**/*.php"
]
},
"skipFiles": {
"type": "array",
"items": "string",
"description": "An array of glob patterns, to skip when debugging. Star patterns and negations are allowed, for example, `[\"**/vendor/**\", \"!**/vendor/my-module/**\"]`",
"default": [
"**/vendor/**"
]
},
"log": {
"type": "boolean",
"description": "If true, will log all communication between VS Code and the adapter"
Expand Down
6 changes: 6 additions & 0 deletions src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as url from 'url'
import * as path from 'path'
import { decode } from 'urlencode'
import RelateUrl from 'relateurl'
import minimatch from 'minimatch'

/**
* Options to make sure that RelateUrl only outputs relative URLs and performs not other "smart" modifications.
Expand Down Expand Up @@ -153,3 +154,8 @@ export function isSameUri(clientUri: string, debuggerUri: string): boolean {
return debuggerUri === clientUri
}
}

export function isPositiveMatchInGlobs(path: string, globs: string[]): boolean {
const f = globs.find(glob => minimatch(path, glob.charAt(0) == '!' ? glob.substring(1) : glob))
return f !== undefined && f.charAt(0) !== '!'
}
77 changes: 61 additions & 16 deletions src/phpDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as path from 'path'
import * as util from 'util'
import * as fs from 'fs'
import { Terminal } from './terminal'
import { convertClientPathToDebugger, convertDebuggerPathToClient } from './paths'
import { convertClientPathToDebugger, convertDebuggerPathToClient, isPositiveMatchInGlobs } from './paths'
import minimatch from 'minimatch'
import { BreakpointManager, BreakpointAdapter } from './breakpoints'
import * as semver from 'semver'
Expand Down Expand Up @@ -76,6 +76,8 @@ export interface LaunchRequestArguments extends VSCodeDebugProtocol.LaunchReques
log?: boolean
/** Array of glob patterns that errors should be ignored from */
ignore?: string[]
/** Array of glob patterns that debugger should not step in */
skipFiles?: string[]
/** Xdebug configuration */
xdebugSettings?: { [featureName: string]: string | number }
/** proxy connection configuration */
Expand Down Expand Up @@ -165,6 +167,9 @@ class PhpDebugSession extends vscode.DebugSession {
/** A flag to indicate that the adapter has already processed the stopOnEntry step request */
private _hasStoppedOnEntry = false

/** A map from Xdebug connection id to state of skipping files */
private _skippingFiles = new Map<number, boolean>()

/** Breakpoint Manager to map VS Code to Xdebug breakpoints */
private _breakpointManager = new BreakpointManager()

Expand Down Expand Up @@ -566,6 +571,7 @@ class PhpDebugSession extends vscode.DebugSession {
this._connections.delete(connection.id)
this._statuses.delete(connection)
this._breakpointAdapters.delete(connection)
this._skippingFiles.delete(connection.id)
}
}

Expand Down Expand Up @@ -676,27 +682,43 @@ class PhpDebugSession extends vscode.DebugSession {
stoppedEventReason = 'entry'
this._hasStoppedOnEntry = true
} else if (response.command.startsWith('step')) {
await this._processLogPoints(response)
// check just my code
if (
this._args.skipFiles &&
isPositiveMatchInGlobs(
convertDebuggerPathToClient(response.fileUri).replace(/\\/g, '/'),
this._args.skipFiles
)
) {
if (!this._skippingFiles.has(connection.id)) {
this._skippingFiles.set(connection.id, true)
}
if (this._skippingFiles.get(connection.id)) {
let stepResponse
switch (response.command) {
case 'step_out':
stepResponse = await connection.sendStepOutCommand()
break
case 'step_over':
stepResponse = await connection.sendStepOverCommand()
break
default:
stepResponse = await connection.sendStepIntoCommand()
}
await this._checkStatus(stepResponse)
return
}
this._skippingFiles.delete(connection.id)
}
stoppedEventReason = 'step'
} else {
stoppedEventReason = 'breakpoint'
}
// Check for log points
if (this._logPointManager.hasLogPoint(response.fileUri, response.line)) {
const logMessage = await this._logPointManager.resolveExpressions(
response.fileUri,
response.line,
async (expr: string): Promise<string> => {
const evaluated = await connection.sendEvalCommand(expr)
return formatPropertyValue(evaluated.result)
}
)

this.sendEvent(new vscode.OutputEvent(logMessage + '\n', 'console'))
if (stoppedEventReason === 'breakpoint') {
if (await this._processLogPoints(response)) {
const responseCommand = await connection.sendRunCommand()
await this._checkStatus(responseCommand)
return
}
stoppedEventReason = 'breakpoint'
}
const event: VSCodeDebugProtocol.StoppedEvent = new vscode.StoppedEvent(
stoppedEventReason,
Expand All @@ -708,6 +730,24 @@ class PhpDebugSession extends vscode.DebugSession {
}
}

private async _processLogPoints(response: xdebug.StatusResponse): Promise<boolean> {
const connection = response.connection
if (this._logPointManager.hasLogPoint(response.fileUri, response.line)) {
const logMessage = await this._logPointManager.resolveExpressions(
response.fileUri,
response.line,
async (expr: string): Promise<string> => {
const evaluated = await connection.sendEvalCommand(expr)
return formatPropertyValue(evaluated.result)
}
)

this.sendEvent(new vscode.OutputEvent(logMessage + '\n', 'console'))
return true
}
return false
}

/** Logs all requests before dispatching */
protected dispatchRequest(request: VSCodeDebugProtocol.Request): void {
if (this._args?.log) {
Expand Down Expand Up @@ -1298,6 +1338,11 @@ class PhpDebugSession extends vscode.DebugSession {
response: VSCodeDebugProtocol.PauseResponse,
args: VSCodeDebugProtocol.PauseArguments
): void {
if (this._skippingFiles.has(args.threadId)) {
this._skippingFiles.set(args.threadId, false)
this.sendResponse(response)
return
}
this.sendErrorResponse(response, new Error('Pausing the execution is not supported by Xdebug'))
}

Expand Down
19 changes: 18 additions & 1 deletion src/test/paths.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isSameUri, convertClientPathToDebugger, convertDebuggerPathToClient } from '../paths'
import { isSameUri, convertClientPathToDebugger, convertDebuggerPathToClient, isPositiveMatchInGlobs } from '../paths'
import * as assert from 'assert'
import { describe, it } from 'mocha'

Expand Down Expand Up @@ -308,4 +308,21 @@ describe('paths', () => {
})
})
})
describe('isPositiveMatchInGlobs', () => {
it('should not match empty globs', () => {
assert.equal(isPositiveMatchInGlobs('/test/test.php', []), false)
})
it('should match positive globs', () => {
assert.equal(isPositiveMatchInGlobs('/test/test.php', ['**/test/**']), true)
})
it('should not match positive globs', () => {
assert.equal(isPositiveMatchInGlobs('/test/test.php', ['**/not_test/**']), false)
})
it('should match negative globs', () => {
assert.equal(isPositiveMatchInGlobs('/test/test.php', ['!**/test.php', '**/test/**']), false)
})
it('should not match negative globs', () => {
assert.equal(isPositiveMatchInGlobs('/test/test.php', ['!**/not_test/test.php', '**/test/**']), true)
})
})
})

0 comments on commit af38cb8

Please sign in to comment.