@@ -11,10 +11,9 @@ import { tryGetFunctionProjectRoot } from '../commands/createNewProject/verifyIs
1111import { localSettingsFileName } from '../constants' ;
1212import { getLocalSettingsJson } from '../funcConfig/local.settings' ;
1313import { localize } from '../localize' ;
14- import { stripAnsiControlCharacters } from '../utils/ansiUtils' ;
1514import { cpUtils } from '../utils/cpUtils' ;
1615import { getWorkspaceSetting } from '../vsCodeConfig/settings' ;
17- import { isFuncHostErrorLog } from './funcHostErrorUtils' ;
16+ import { addErrorLinesFromChunk } from './funcHostErrorUtils' ;
1817
1918export interface IRunningFuncTask {
2019 taskExecution : vscode . TaskExecution ;
@@ -40,26 +39,6 @@ export interface IRunningFuncTask {
4039 streamAbortController ?: AbortController ;
4140}
4241
43- function addErrorLog ( task : IRunningFuncTask , rawChunk : string ) : void {
44- const plain = stripAnsiControlCharacters ( rawChunk ) . trim ( ) ;
45- if ( ! plain ) {
46- return ;
47- }
48-
49- const arr = task . errorLogs ?? ( task . errorLogs = [ ] ) ;
50- if ( arr [ arr . length - 1 ] === plain ) {
51- return ;
52- }
53-
54- arr . push ( plain ) ;
55-
56- // Keep the most recent few to avoid unbounded memory usage.
57- const maxErrors = 10 ;
58- if ( arr . length > maxErrors ) {
59- task . errorLogs = arr . slice ( arr . length - maxErrors ) ;
60- }
61- }
62-
6342export interface IRunningFuncTaskWithScope {
6443 scope : vscode . WorkspaceFolder | vscode . TaskScope ;
6544 task : IRunningFuncTask ;
@@ -90,8 +69,14 @@ class RunningFunctionTaskMap {
9069 const taskExecution = t . taskExecution . task . execution as vscode . ShellExecution ;
9170 // the cwd will include ${workspaceFolder} from our tasks.json so we need to replace it with the actual path
9271 const taskDirectory = taskExecution . options ?. cwd ?. replace ( '${workspaceFolder}' , ( t . taskExecution . task ?. scope as vscode . WorkspaceFolder ) . uri ?. path ) ;
93- buildPath = buildPath ?. replace ( '${workspaceFolder}' , ( t . taskExecution . task ?. scope as vscode . WorkspaceFolder ) . uri ?. path ) ;
94- return taskDirectory && buildPath && normalizePath ( taskDirectory ) === normalizePath ( buildPath ) ;
72+ const resolvedBuildPath = buildPath ?. replace ( '${workspaceFolder}' , ( t . taskExecution . task ?. scope as vscode . WorkspaceFolder ) . uri ?. path ) ;
73+
74+ // When neither cwd is set, both tasks use the default working directory — treat as a match
75+ if ( ! taskDirectory && ! resolvedBuildPath ) {
76+ return true ;
77+ }
78+
79+ return taskDirectory && resolvedBuildPath && normalizePath ( taskDirectory ) === normalizePath ( resolvedBuildPath ) ;
9580 } ) ;
9681 }
9782
@@ -157,8 +142,25 @@ const defaultFuncPort: string = '7071';
157142
158143const funcCommandRegex : RegExp = / ( f u n c (?: \. e x e ) ? ) \s + h o s t \s + s t a r t / i;
159144export function isFuncHostTask ( task : vscode . Task ) : boolean {
160- const commandLine : string | undefined = task . execution && ( < vscode . ShellExecution > task . execution ) . commandLine ;
161- return funcCommandRegex . test ( commandLine || '' ) ;
145+ const execution = task . execution as vscode . ShellExecution | undefined ;
146+ if ( ! execution ) {
147+ return false ;
148+ }
149+
150+ // String-based ShellExecution: `commandLine` contains the full command
151+ if ( execution . commandLine ) {
152+ return funcCommandRegex . test ( execution . commandLine ) ;
153+ }
154+
155+ // Args-based ShellExecution: `command` + `args` are separate
156+ // Reconstruct the command string to test against the regex
157+ const command = typeof execution . command === 'string' ? execution . command : execution . command ?. value ;
158+ if ( command && execution . args ) {
159+ const argsStr = execution . args . map ( a => typeof a === 'string' ? a : a . value ) . join ( ' ' ) ;
160+ return funcCommandRegex . test ( `${ command } ${ argsStr } ` ) ;
161+ }
162+
163+ return false ;
162164}
163165
164166export function isFuncShellEvent ( event : vscode . TerminalShellExecutionStartEvent ) : boolean {
@@ -219,12 +221,12 @@ export function registerFuncHostTaskEvents(): void {
219221 context . telemetry . suppressIfSuccessful = true ;
220222 if ( e . execution . task . scope !== undefined && isFuncHostTask ( e . execution . task ) ) {
221223 const task = runningFuncTaskMap . get ( e . execution . task . scope , ( e . execution . task . execution as vscode . ShellExecution ) . options ?. cwd ) ;
222-
224+
223225 // Abort the stream iteration to prevent it from hanging indefinitely
224226 if ( task ?. streamAbortController ) {
225227 task . streamAbortController . abort ( ) ;
226228 }
227-
229+
228230 runningFuncTaskMap . delete ( e . execution . task . scope , ( e . execution . task . execution as vscode . ShellExecution ) . options ?. cwd ) ;
229231
230232 runningFuncTasksChangedEmitter . fire ( ) ;
@@ -260,14 +262,11 @@ export function registerFuncHostTaskEvents(): void {
260262 task . logs . splice ( 0 , task . logs . length - maxLogEntries ) ;
261263 }
262264
263- // Keep track of errors for the Debug view.
264- if ( isFuncHostErrorLog ( chunk ) ) {
265- const beforeCount = task . errorLogs ?. length ?? 0 ;
266- addErrorLog ( task , chunk ) ;
267- const afterCount = task . errorLogs ?. length ?? 0 ;
268- if ( afterCount > beforeCount ) {
269- runningFuncTasksChangedEmitter . fire ( ) ;
270- }
265+ // Split chunk into log entries by timestamp, check each for red
266+ // ANSI, and deduplicate against existing errors.
267+ const errorArr = task . errorLogs ?? ( task . errorLogs = [ ] ) ;
268+ if ( addErrorLinesFromChunk ( errorArr , chunk ) ) {
269+ runningFuncTasksChangedEmitter . fire ( ) ;
271270 }
272271 }
273272 } catch ( error ) {
@@ -319,7 +318,7 @@ export async function stopFuncTaskIfRunning(workspaceFolder: vscode.WorkspaceFol
319318
320319 if ( runningFuncTask !== undefined && runningFuncTask . length > 0 ) {
321320 for ( const runningFuncTaskItem of runningFuncTask ) {
322- if ( ! runningFuncTaskItem ) { break ; }
321+ if ( ! runningFuncTaskItem ) { break ; }
323322 if ( terminate ) {
324323 runningFuncTaskItem . taskExecution . terminate ( ) ;
325324 } else {
0 commit comments