44 *--------------------------------------------------------------------------------------------*/
55
66import { localize } from '../../../../nls.js' ;
7- import { isString } from '../../../../base/common/types.js' ;
7+ import { URI } from '../../../../base/common/uri.js' ;
8+ import { isString , assertType } from '../../../../base/common/types.js' ;
89import { Codicon } from '../../../../base/common/codicons.js' ;
910import { ITextModel } from '../../../../editor/common/model.js' ;
1011import { IRange } from '../../../../editor/common/core/range.js' ;
1112import { IEditor } from '../../../../editor/common/editorCommon.js' ;
1213import { ILogService } from '../../../../platform/log/common/log.js' ;
13- import { Position } from '../../../../editor/common/core/position.js' ;
14+ import { IModelService } from '../../../../editor/common/services/model.js' ;
15+ import { IPosition , Position } from '../../../../editor/common/core/position.js' ;
1416import { CancellationToken } from '../../../../base/common/cancellation.js' ;
1517import { KeyChord , KeyCode , KeyMod } from '../../../../base/common/keyCodes.js' ;
1618import { ILocalizedString } from '../../../../platform/action/common/action.js' ;
@@ -32,7 +34,7 @@ import { IPositronModalDialogsService } from '../../../services/positronModalDia
3234import { IPositronConsoleService , POSITRON_CONSOLE_VIEW_ID } from '../../../services/positronConsole/browser/interfaces/positronConsoleService.js' ;
3335import { IExecutionHistoryService } from '../../../services/positronHistory/common/executionHistoryService.js' ;
3436import { CodeAttributionSource , IConsoleCodeAttribution } from '../../../services/positronConsole/common/positronConsoleCodeExecution.js' ;
35- import { ICommandService } from '../../../../platform/commands/common/commands.js' ;
37+ import { CommandsRegistry , ICommandService } from '../../../../platform/commands/common/commands.js' ;
3638import { POSITRON_NOTEBOOK_CELL_EDITOR_FOCUSED } from '../../positronNotebook/browser/ContextKeysManager.js' ;
3739import { getContextFromActiveEditor } from '../../notebook/browser/controller/coreActions.js' ;
3840
@@ -315,6 +317,8 @@ export function registerPositronConsoleActions() {
315317 * - advance: Optionally, if the cursor should be advanced to the next statement. If `undefined`, fallbacks to `true`.
316318 * - mode: Optionally, the code execution mode for a language runtime. If `undefined` fallbacks to `Interactive`.
317319 * - errorBehavior: Optionally, the error behavior for a language runtime. If `undefined` fallbacks to `Continue`.
320+ * - uri: The URI of the document to execute code from. Must be provided together with `position`.
321+ * - position: The position in the document to execute code from. Must be provided together with `uri`.
318322 */
319323 async run (
320324 accessor : ServicesAccessor ,
@@ -324,13 +328,17 @@ export function registerPositronConsoleActions() {
324328 advance ?: boolean ;
325329 mode ?: RuntimeCodeExecutionMode ;
326330 errorBehavior ?: RuntimeErrorBehavior ;
327- } = { }
328- ) {
331+ } & (
332+ | { uri : URI ; position : Position }
333+ | { uri ?: never ; position ?: never }
334+ ) = { }
335+ ) : Promise < Position | undefined > {
329336 // Access services.
330337 const editorService = accessor . get ( IEditorService ) ;
331338 const languageFeaturesService = accessor . get ( ILanguageFeaturesService ) ;
332339 const languageService = accessor . get ( ILanguageService ) ;
333340 const logService = accessor . get ( ILogService ) ;
341+ const modelService = accessor . get ( IModelService ) ;
334342 const notificationService = accessor . get ( INotificationService ) ;
335343 const positronConsoleService = accessor . get ( IPositronConsoleService ) ;
336344
@@ -340,15 +348,44 @@ export function registerPositronConsoleActions() {
340348 // The code to execute.
341349 let code : string | undefined = undefined ;
342350
343- // If there is no active editor, there is nothing to execute.
344- const editor = editorService . activeTextEditorControl as IEditor ;
345- if ( ! editor ) {
346- return ;
351+ // Determine if we're using a provided URI or the active editor
352+ let editor : IEditor | undefined ;
353+ let model : ITextModel | undefined ;
354+ let position : Position ;
355+ let nextPosition : Position | undefined ;
356+
357+ if ( opts . uri ) {
358+ // Use the provided URI to get the model
359+ const foundModel = modelService . getModel ( opts . uri ) ;
360+ if ( ! foundModel ) {
361+ notificationService . notify ( {
362+ severity : Severity . Info ,
363+ message : localize ( 'positron.executeCode.noModel' , "Cannot execute code. Unable to find document at {0}." , opts . uri . toString ( ) ) ,
364+ sticky : false
365+ } ) ;
366+ return ;
367+ }
368+ model = foundModel ;
369+ // Use the provided position (guaranteed to exist when uri is provided)
370+ position = opts . position ;
371+ // No editor context when URI is provided
372+ editor = undefined ;
373+ } else {
374+ // Use the active editor
375+ editor = editorService . activeTextEditorControl as IEditor ;
376+ if ( ! editor ) {
377+ return ;
378+ }
379+ model = editor . getModel ( ) as ITextModel ;
380+ const editorPosition = editor . getPosition ( ) ;
381+ if ( ! editorPosition ) {
382+ return ;
383+ }
384+ position = editorPosition ;
347385 }
348386
349387 // Get the code to execute.
350- const selection = editor . getSelection ( ) ;
351- const model = editor . getModel ( ) as ITextModel ;
388+ const selection = editor ?. getSelection ( ) ;
352389
353390 // If we have a selection and it isn't empty, then we use its contents (even if it
354391 // only contains whitespace or comments) and also retain the user's selection location.
@@ -367,13 +404,6 @@ export function registerPositronConsoleActions() {
367404 // HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
368405 }
369406
370- // Get the position of the cursor. If we don't have a selection, we'll use this to
371- // determine the code to execute.
372- const position = editor . getPosition ( ) ;
373- if ( ! position ) {
374- return ;
375- }
376-
377407 // Get all the statement range providers for the active document.
378408 const statementRangeProviders =
379409 languageFeaturesService . statementRangeProvider . all ( model ) ;
@@ -401,7 +431,7 @@ export function registerPositronConsoleActions() {
401431 code = isString ( statementRange . code ) ? statementRange . code : model . getValueInRange ( statementRange . range ) ;
402432
403433 if ( advance ) {
404- await this . advanceStatement ( model , editor , statementRange , statementRangeProviders [ 0 ] , logService ) ;
434+ nextPosition = await this . advanceStatement ( model , editor , statementRange , statementRangeProviders [ 0 ] , logService ) ;
405435 }
406436 } else {
407437 // The statement range provider didn't return a range. This
@@ -412,8 +442,7 @@ export function registerPositronConsoleActions() {
412442 // If no selection was found, use the contents of the line containing the cursor
413443 // position.
414444 if ( ! isString ( code ) ) {
415- const position = editor . getPosition ( ) ;
416- let lineNumber = position ?. lineNumber ?? 0 ;
445+ let lineNumber = position . lineNumber ;
417446
418447 if ( lineNumber > 0 ) {
419448 // Find the first non-empty line after the cursor position and read the
@@ -431,21 +460,23 @@ export function registerPositronConsoleActions() {
431460
432461 // If we have code and a position move the cursor to the next line with code on it,
433462 // or just to the next line if all additional lines are blank.
434- if ( advance && isString ( code ) && position ) {
435- this . advanceLine ( model , editor , position , lineNumber , code , editorService ) ;
463+ if ( advance && isString ( code ) ) {
464+ nextPosition = this . advanceLine ( model , editor , position , lineNumber , code , editorService ) ;
436465 }
437466
438- if ( ! isString ( code ) && position && lineNumber === model . getLineCount ( ) ) {
467+ if ( ! isString ( code ) && lineNumber === model . getLineCount ( ) ) {
439468 // If we still don't have code and we are at the end of the document, add a
440469 // newline to the end of the document.
441470 this . amendNewlineToEnd ( model ) ;
442471
443472 // We don't move to that new line to avoid adding a bunch of empty
444473 // lines to the end. The edit operation typically moves us to the new line,
445474 // so we have to undo that.
446- const newPosition = new Position ( lineNumber , 1 ) ;
447- editor . setPosition ( newPosition ) ;
448- editor . revealPositionInCenterIfOutsideViewport ( newPosition ) ;
475+ if ( editor ) {
476+ const newPosition = new Position ( lineNumber , 1 ) ;
477+ editor . setPosition ( newPosition ) ;
478+ editor . revealPositionInCenterIfOutsideViewport ( newPosition ) ;
479+ }
449480 }
450481
451482 // If we still don't have code after looking at the cursor position,
@@ -472,18 +503,18 @@ export function registerPositronConsoleActions() {
472503 mode : opts . mode ,
473504 errorBehavior : opts . errorBehavior
474505 } ) ;
506+ return nextPosition ;
475507 }
476508
477509 async advanceStatement (
478510 model : ITextModel ,
479- editor : IEditor ,
511+ editor : IEditor | undefined ,
480512 statementRange : IStatementRange ,
481513 provider : StatementRangeProvider ,
482514 logService : ILogService ,
483- ) {
515+ ) : Promise < Position > {
484516
485- // Move the cursor to the next
486- // statement by creating a position on the line
517+ // Calculate the next position by creating a position on the line
487518 // following the statement and then invoking the
488519 // statement range provider again to find the appropriate
489520 // boundary of the next statement.
@@ -505,8 +536,6 @@ export function registerPositronConsoleActions() {
505536 model . getLineCount ( ) ,
506537 1
507538 ) ;
508- editor . setPosition ( newPosition ) ;
509- editor . revealPositionInCenterIfOutsideViewport ( newPosition ) ;
510539 } else {
511540 // Invoke the statement range provider again to
512541 // find the appropriate boundary of the next statement.
@@ -548,20 +577,24 @@ export function registerPositronConsoleActions() {
548577 ) ;
549578 }
550579 }
580+ }
551581
582+ // Only move the cursor if we have an editor
583+ if ( editor ) {
552584 editor . setPosition ( newPosition ) ;
553585 editor . revealPositionInCenterIfOutsideViewport ( newPosition ) ;
554586 }
587+ return newPosition ;
555588 }
556589
557590 advanceLine (
558591 model : ITextModel ,
559- editor : IEditor ,
592+ editor : IEditor | undefined ,
560593 position : Position ,
561594 lineNumber : number ,
562595 code : string ,
563596 editorService : IEditorService ,
564- ) {
597+ ) : Position {
565598 // HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
566599 // This attempts to address https://github.com/posit-dev/positron/issues/1177
567600 // by tacking a newline onto indented Python code fragments that end at an empty
@@ -598,8 +631,12 @@ export function registerPositronConsoleActions() {
598631 }
599632
600633 const newPosition = position . with ( lineNumber , 0 ) ;
601- editor . setPosition ( newPosition ) ;
602- editor . revealPositionInCenterIfOutsideViewport ( newPosition ) ;
634+ // Only move the cursor if we have an editor
635+ if ( editor ) {
636+ editor . setPosition ( newPosition ) ;
637+ editor . revealPositionInCenterIfOutsideViewport ( newPosition ) ;
638+ }
639+ return newPosition ;
603640 }
604641
605642 amendNewlineToEnd ( model : ITextModel ) {
@@ -1062,3 +1099,22 @@ export function registerPositronConsoleActions() {
10621099 }
10631100 } ) ;
10641101}
1102+
1103+
1104+ /**
1105+ * Register the internal command for executing code in console from the extension API.
1106+ * This command is called by the positron.executeCodeInConsole API command.
1107+ */
1108+ CommandsRegistry . registerCommand ( '_executeCodeInConsole' , async ( accessor , ...args : [ string , URI , IPosition ] ) => {
1109+ const [ languageId , uri , position ] = args ;
1110+ assertType ( typeof languageId === 'string' ) ;
1111+ assertType ( URI . isUri ( uri ) ) ;
1112+ assertType ( Position . isIPosition ( position ) ) ;
1113+
1114+ const commandService = accessor . get ( ICommandService ) ;
1115+ return await commandService . executeCommand ( 'workbench.action.positronConsole.executeCode' , {
1116+ languageId,
1117+ uri,
1118+ position : Position . lift ( position )
1119+ } ) ;
1120+ } ) ;
0 commit comments