diff --git a/packages/debug/src/browser/debug-session-manager.ts b/packages/debug/src/browser/debug-session-manager.ts index 1b0bd4b9f474a..d5decd2125dc5 100644 --- a/packages/debug/src/browser/debug-session-manager.ts +++ b/packages/debug/src/browser/debug-session-manager.ts @@ -27,7 +27,7 @@ import { DebugConfiguration } from '../common/debug-common'; import { DebugError, DebugService } from '../common/debug-service'; import { BreakpointManager } from './breakpoint/breakpoint-manager'; import { DebugConfigurationManager } from './debug-configuration-manager'; -import { DebugSession, DebugState } from './debug-session'; +import { DebugSession, DebugState, debugStateContextValue } from './debug-session'; import { DebugSessionContributionRegistry, DebugSessionFactory } from './debug-session-contribution'; import { DebugCompoundRoot, DebugCompoundSessionOptions, DebugConfigurationSessionOptions, DebugSessionOptions, InternalDebugSessionOptions } from './debug-session-options'; import { DebugStackFrame } from './model/debug-stack-frame'; @@ -106,7 +106,9 @@ export class DebugSessionManager { protected readonly onDidChangeEmitter = new Emitter(); readonly onDidChange: Event = this.onDidChangeEmitter.event; protected fireDidChange(current: DebugSession | undefined): void { + this.debugTypeKey.set(current?.configuration.type); this.inDebugModeKey.set(this.inDebugMode); + this.debugStateKey.set(debugStateContextValue(this.state)); this.onDidChangeEmitter.fire(current); } @@ -154,11 +156,13 @@ export class DebugSessionManager { protected debugTypeKey: ContextKey; protected inDebugModeKey: ContextKey; + protected debugStateKey: ContextKey; @postConstruct() protected init(): void { this.debugTypeKey = this.contextKeyService.createKey('debugType', undefined); this.inDebugModeKey = this.contextKeyService.createKey('inDebugMode', this.inDebugMode); + this.debugStateKey = this.contextKeyService.createKey('debugState', debugStateContextValue(this.state)); this.breakpoints.onDidChangeMarkers(uri => this.fireDidChangeBreakpoints({ uri })); this.labelProvider.onDidChange(event => { for (const uriString of this.breakpoints.getUris()) { diff --git a/packages/debug/src/browser/debug-session.tsx b/packages/debug/src/browser/debug-session.tsx index 130885be8d19b..c8825227121e2 100644 --- a/packages/debug/src/browser/debug-session.tsx +++ b/packages/debug/src/browser/debug-session.tsx @@ -50,6 +50,18 @@ export enum DebugState { Running, Stopped } +/** + * The mapped string values must not change as they are used for the `debugState` when context closure. + * For more details see the `Debugger contexts` section of the [official doc](https://code.visualstudio.com/api/references/when-clause-contexts#available-contexts). + */ +export function debugStateContextValue(state: DebugState): string { + switch (state) { + case DebugState.Initializing: return 'initializing'; + case DebugState.Stopped: return 'stopped'; + case DebugState.Running: return 'running'; + default: return 'inactive'; + } +} // FIXME: make injectable to allow easily inject services export class DebugSession implements CompositeTreeElement { @@ -74,7 +86,7 @@ export class DebugSession implements CompositeTreeElement { protected readonly childSessions = new Map(); protected readonly toDispose = new DisposableCollection(); - private isStopping: boolean = false; + protected isStopping: boolean = false; constructor( readonly id: string, @@ -89,6 +101,10 @@ export class DebugSession implements CompositeTreeElement { protected readonly fileService: FileService, protected readonly debugContributionProvider: ContributionProvider, protected readonly workspaceService: WorkspaceService, + /** + * Number of millis after a `stop` request times out. It's 5 seconds by default. + */ + protected readonly stopTimeout = 5_000, ) { this.connection.onRequest('runInTerminal', (request: DebugProtocol.RunInTerminalRequest) => this.runInTerminal(request)); this.connection.onDidClose(() => { @@ -274,19 +290,25 @@ export class DebugSession implements CompositeTreeElement { } protected async initialize(): Promise { - const response = await this.connection.sendRequest('initialize', { - clientID: 'Theia', - clientName: 'Theia IDE', - adapterID: this.configuration.type, - locale: 'en-US', - linesStartAt1: true, - columnsStartAt1: true, - pathFormat: 'path', - supportsVariableType: false, - supportsVariablePaging: false, - supportsRunInTerminalRequest: true - }); - this.updateCapabilities(response?.body || {}); + try { + const response = await this.connection.sendRequest('initialize', { + clientID: 'Theia', + clientName: 'Theia IDE', + adapterID: this.configuration.type, + locale: 'en-US', + linesStartAt1: true, + columnsStartAt1: true, + pathFormat: 'path', + supportsVariableType: false, + supportsVariablePaging: false, + supportsRunInTerminalRequest: true + }); + this.updateCapabilities(response?.body || {}); + this.didReceiveCapabilities.resolve(); + } catch (err) { + this.didReceiveCapabilities.reject(err); + throw err; + } } protected async launchOrAttach(): Promise { @@ -304,8 +326,17 @@ export class DebugSession implements CompositeTreeElement { } } + /** + * The `send('initialize')` request could resolve later than `on('initialized')` emits the event. + * Hence, the `configure` would use the empty object `capabilities`. + * Using the empty `capabilities` could result in missing exception breakpoint filters, as + * always `capabilities.exceptionBreakpointFilters` is falsy. This deferred promise works + * around this timing issue. https://github.com/eclipse-theia/theia/issues/11886 + */ + protected didReceiveCapabilities = new Deferred(); protected initialized = false; protected async configure(): Promise { + await this.didReceiveCapabilities.promise; if (this.capabilities.exceptionBreakpointFilters) { const exceptionBreakpoints = []; for (const filter of this.capabilities.exceptionBreakpointFilters) { @@ -340,24 +371,39 @@ export class DebugSession implements CompositeTreeElement { if (!this.isStopping) { this.isStopping = true; if (this.canTerminate()) { - const terminated = this.waitFor('terminated', 5000); + const terminated = this.waitFor('terminated', this.stopTimeout); try { - await this.connection.sendRequest('terminate', { restart: isRestart }, 5000); + await this.connection.sendRequest('terminate', { restart: isRestart }, this.stopTimeout); await terminated; } catch (e) { - console.error('Did not receive terminated event in time', e); + this.handleTerminateError(e); } } else { + const terminateDebuggee = this.initialized && this.capabilities.supportTerminateDebuggee; try { - await this.sendRequest('disconnect', { restart: isRestart }, 5000); + await this.sendRequest('disconnect', { restart: isRestart, terminateDebuggee }, this.stopTimeout); } catch (e) { - console.error('Error on disconnect', e); + this.handleDisconnectError(e); } } callback(); } } + /** + * Invoked when sending the `terminate` request to the debugger is rejected or timed out. + */ + protected handleTerminateError(err: unknown): void { + console.error('Did not receive terminated event in time', err); + } + + /** + * Invoked when sending the `disconnect` request to the debugger is rejected or timed out. + */ + protected handleDisconnectError(err: unknown): void { + console.error('Error on disconnect', err); + } + async disconnect(isRestart: boolean, callback: () => void): Promise { if (!this.isStopping) { this.isStopping = true; @@ -665,12 +711,17 @@ export class DebugSession implements CompositeTreeElement { const response = await this.sendRequest('setFunctionBreakpoints', { breakpoints: enabled.map(b => b.origin.raw) }); - response.body.breakpoints.forEach((raw, index) => { - // node debug adapter returns more breakpoints sometimes - if (enabled[index]) { - enabled[index].update({ raw }); - } - }); + // Apparently, `body` and `breakpoints` can be missing. + // https://github.com/eclipse-theia/theia/issues/11885 + // https://github.com/microsoft/vscode/blob/80004351ccf0884b58359f7c8c801c91bb827d83/src/vs/workbench/contrib/debug/browser/debugSession.ts#L448-L449 + if (response && response.body) { + response.body.breakpoints.forEach((raw, index) => { + // node debug adapter returns more breakpoints sometimes + if (enabled[index]) { + enabled[index].update({ raw }); + } + }); + } } catch (error) { // could be error or promise rejection of DebugProtocol.SetFunctionBreakpoints if (error instanceof Error) { @@ -699,10 +750,12 @@ export class DebugSession implements CompositeTreeElement { ); const enabled = all.filter(b => b.enabled); try { + const breakpoints = enabled.map(({ origin }) => origin.raw); const response = await this.sendRequest('setBreakpoints', { source: source.raw, sourceModified, - breakpoints: enabled.map(({ origin }) => origin.raw) + breakpoints, + lines: breakpoints.map(({ line }) => line) }); response.body.breakpoints.forEach((raw, index) => { // node debug adapter returns more breakpoints sometimes diff --git a/packages/debug/src/browser/model/debug-stack-frame.tsx b/packages/debug/src/browser/model/debug-stack-frame.tsx index 9e70411c4337b..67b3c14ced513 100644 --- a/packages/debug/src/browser/model/debug-stack-frame.tsx +++ b/packages/debug/src/browser/model/debug-stack-frame.tsx @@ -64,28 +64,36 @@ export class DebugStackFrame extends DebugStackFrameData implements TreeElement })); } - async open(options: WidgetOpenerOptions = { - mode: 'reveal' - }): Promise { + async open(options?: WidgetOpenerOptions): Promise { if (!this.source) { return undefined; } const { line, column, endLine, endColumn } = this.raw; const selection: RecursivePartial = { - start: Position.create(line - 1, column - 1) + start: Position.create(this.clampPositive(line - 1), this.clampPositive(column - 1)) }; if (typeof endLine === 'number') { selection.end = { - line: endLine - 1, - character: typeof endColumn === 'number' ? endColumn - 1 : undefined + line: this.clampPositive(endLine - 1), + character: typeof endColumn === 'number' ? this.clampPositive(endColumn - 1) : undefined }; } this.source.open({ + mode: 'reveal', ...options, selection }); } + /** + * Debugger can send `column: 0` value despite of initializing the debug session with `columnsStartAt1: true`. + * This method can be used to ensure that neither `column` nor `column` are negative numbers. + * See https://github.com/microsoft/vscode-mock-debug/issues/85. + */ + protected clampPositive(value: number): number { + return Math.max(value, 0); + } + protected scopes: Promise | undefined; getScopes(): Promise { return this.scopes || (this.scopes = this.doGetScopes()); diff --git a/packages/debug/src/browser/style/index.css b/packages/debug/src/browser/style/index.css index 68a694ee7ed0e..df1f8be4c3fef 100644 --- a/packages/debug/src/browser/style/index.css +++ b/packages/debug/src/browser/style/index.css @@ -146,6 +146,17 @@ opacity: 1; } +.debug-toolbar .debug-action>div { + font-family: var(--theia-ui-font-family); + font-size: var(--theia-ui-font-size0); + display: flex; + align-items: center; + align-self: center; + justify-content: center; + text-align: center; + min-height: inherit; +} + /** Console */ #debug-console .theia-console-info { diff --git a/packages/debug/src/browser/view/debug-action.tsx b/packages/debug/src/browser/view/debug-action.tsx index 4be5ec9d93956..f7ef0a3c19b5b 100644 --- a/packages/debug/src/browser/view/debug-action.tsx +++ b/packages/debug/src/browser/view/debug-action.tsx @@ -21,7 +21,10 @@ export class DebugAction extends React.Component { override render(): React.ReactNode { const { enabled, label, iconClass } = this.props; - const classNames = ['debug-action', ...codiconArray(iconClass, true)]; + const classNames = ['debug-action']; + if (iconClass) { + classNames.push(...codiconArray(iconClass, true)); + } if (enabled === false) { classNames.push(DISABLED_CLASS); } @@ -29,7 +32,9 @@ export class DebugAction extends React.Component { className={classNames.join(' ')} title={label} onClick={this.props.run} - ref={this.setRef} />; + ref={this.setRef} > + {!iconClass &&
{label}
} + ; } focus(): void { diff --git a/packages/debug/src/browser/view/debug-toolbar-widget.tsx b/packages/debug/src/browser/view/debug-toolbar-widget.tsx index 705c29f6c19bc..66c3d0c08d8db 100644 --- a/packages/debug/src/browser/view/debug-toolbar-widget.tsx +++ b/packages/debug/src/browser/view/debug-toolbar-widget.tsx @@ -16,7 +16,8 @@ import * as React from '@theia/core/shared/react'; import { inject, postConstruct, injectable } from '@theia/core/shared/inversify'; -import { Disposable, DisposableCollection, MenuPath } from '@theia/core'; +import { CommandMenuNode, CommandRegistry, CompoundMenuNode, Disposable, DisposableCollection, MenuModelRegistry, MenuPath } from '@theia/core'; +import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; import { ReactWidget } from '@theia/core/lib/browser/widgets'; import { DebugViewModel } from './debug-view-model'; import { DebugState } from '../debug-session'; @@ -28,8 +29,10 @@ export class DebugToolBar extends ReactWidget { static readonly MENU: MenuPath = ['debug-toolbar-menu']; - @inject(DebugViewModel) - protected readonly model: DebugViewModel; + @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; + @inject(MenuModelRegistry) protected readonly menuModelRegistry: MenuModelRegistry; + @inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService; + @inject(DebugViewModel) protected readonly model: DebugViewModel; protected readonly onRender = new DisposableCollection(); @@ -65,6 +68,7 @@ export class DebugToolBar extends ReactWidget { protected render(): React.ReactNode { const { state } = this.model; return + {this.renderContributedCommands()} {this.renderContinue()} @@ -77,6 +81,43 @@ export class DebugToolBar extends ReactWidget { {this.renderStart()} ; } + + protected renderContributedCommands(): React.ReactNode { + const debugActions: React.ReactNode[] = []; + // first, search for CompoundMenuNodes: + this.menuModelRegistry.getMenu(DebugToolBar.MENU).children.forEach(compoundMenuNode => { + if (CompoundMenuNode.is(compoundMenuNode) && this.matchContext(compoundMenuNode.when)) { + // second, search for nested CommandMenuNodes: + compoundMenuNode.children.forEach(commandMenuNode => { + if (CommandMenuNode.is(commandMenuNode) && this.matchContext(commandMenuNode.when)) { + debugActions.push(this.debugAction(commandMenuNode)); + } + }); + } + }); + return debugActions; + } + + protected matchContext(when?: string): boolean { + return !when || this.contextKeyService.match(when); + } + + protected debugAction(commandMenuNode: CommandMenuNode): React.ReactNode { + const { command, icon = '', label = '' } = commandMenuNode; + if (!label && !icon) { + const { when } = commandMenuNode; + console.warn(`Neither 'label' nor 'icon' properties were defined for the command menu node. (${JSON.stringify({ command, when })}}. Skipping.`); + return; + } + const run = () => this.commandRegistry.executeCommand(command); + return ; + } + protected renderStart(): React.ReactNode { const { state } = this.model; if (state === DebugState.Inactive && this.model.sessionCount === 1) { diff --git a/packages/editor/src/browser/editor-manager.ts b/packages/editor/src/browser/editor-manager.ts index b9243d3256a6b..a30e6ea4f5ca3 100644 --- a/packages/editor/src/browser/editor-manager.ts +++ b/packages/editor/src/browser/editor-manager.ts @@ -253,6 +253,12 @@ export class EditorManager extends NavigatableWidgetOpenHandler { protected getSelection(widget: EditorWidget, selection: RecursivePartial): Range | Position | undefined { const { start, end } = selection; + if (Position.is(start)) { + if (Position.is(end)) { + return widget.editor.document.toValidRange({ start, end }); + } + return widget.editor.document.toValidPosition(start); + } const line = start && start.line !== undefined && start.line >= 0 ? start.line : undefined; if (line === undefined) { return undefined; diff --git a/packages/editor/src/browser/editor.ts b/packages/editor/src/browser/editor.ts index 2326e52b583b3..97367f57f6935 100644 --- a/packages/editor/src/browser/editor.ts +++ b/packages/editor/src/browser/editor.ts @@ -33,6 +33,17 @@ export interface TextEditorDocument extends lsp.TextDocument, Saveable, Disposab * @since 1.8.0 */ findMatches?(options: FindMatchesOptions): FindMatch[]; + /** + * Creates a valid position. If the position is outside of the backing document, this method will return a position that is ensured to be inside the document and valid. + * For example, when the `position` is `{ line: 1, character: 0 }` and the document is empty, this method will return with `{ line: 0, character: 0 }`. + */ + toValidPosition(position: Position): Position; + /** + * Creates a valid range. If the `range` argument is outside of the document, this method will return with a new range that does not exceed the boundaries of the document. + * For example, if the argument is `{ start: { line: 1, character: 0 }, end: { line: 1, character: 0 } }` and the document is empty, the return value is + * `{ start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }`. + */ + toValidRange(range: Range): Range; } // Refactoring diff --git a/packages/monaco/src/browser/monaco-editor-model.ts b/packages/monaco/src/browser/monaco-editor-model.ts index 105c1fc2d473f..cd0f4b36e0e48 100644 --- a/packages/monaco/src/browser/monaco-editor-model.ts +++ b/packages/monaco/src/browser/monaco-editor-model.ts @@ -293,6 +293,15 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo return this.model.getLineMaxColumn(lineNumber); } + toValidPosition(position: Position): Position { + const { lineNumber, column } = this.model.validatePosition(this.p2m.asPosition(position)); + return this.m2p.asPosition(lineNumber, column); + } + + toValidRange(range: Range): Range { + return this.m2p.asRange(this.model.validateRange(this.p2m.asRange(range))); + } + get readOnly(): boolean { return this.resource.saveContents === undefined; } diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index d803933c75dc5..e31f1085bffa4 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1800,6 +1800,7 @@ export interface DebugExt { $onSessionCustomEvent(sessionId: string, event: string, body?: any): void; $breakpointsDidChange(added: Breakpoint[], removed: string[], changed: Breakpoint[]): void; $sessionDidCreate(sessionId: string): void; + $sessionDidStart(sessionId: string): void; $sessionDidDestroy(sessionId: string): void; $sessionDidChange(sessionId: string | undefined): void; $provideDebugConfigurationsByHandle(handle: number, workspaceFolder: string | undefined): Promise; diff --git a/packages/plugin-ext/src/main/browser/debug/debug-main.ts b/packages/plugin-ext/src/main/browser/debug/debug-main.ts index cbeb416440475..4221473312236 100644 --- a/packages/plugin-ext/src/main/browser/debug/debug-main.ts +++ b/packages/plugin-ext/src/main/browser/debug/debug-main.ts @@ -114,6 +114,7 @@ export class DebugMainImpl implements DebugMain, Disposable { this.breakpointsManager.onDidChangeBreakpoints(fireDidChangeBreakpoints), this.breakpointsManager.onDidChangeFunctionBreakpoints(fireDidChangeBreakpoints), this.sessionManager.onDidCreateDebugSession(debugSession => this.debugExt.$sessionDidCreate(debugSession.id)), + this.sessionManager.onDidStartDebugSession(debugSession => this.debugExt.$sessionDidStart(debugSession.id)), this.sessionManager.onDidDestroyDebugSession(debugSession => this.debugExt.$sessionDidDestroy(debugSession.id)), this.sessionManager.onDidChangeActiveDebugSession(event => this.debugExt.$sessionDidChange(event.current && event.current.id)), this.sessionManager.onDidReceiveDebugSessionCustomEvent(event => this.debugExt.$onSessionCustomEvent(event.session.id, event.event, event.body)) diff --git a/packages/plugin-ext/src/main/browser/menus/plugin-menu-command-adapter.ts b/packages/plugin-ext/src/main/browser/menus/plugin-menu-command-adapter.ts index a76881060108c..bd82d054dbb38 100644 --- a/packages/plugin-ext/src/main/browser/menus/plugin-menu-command-adapter.ts +++ b/packages/plugin-ext/src/main/browser/menus/plugin-menu-command-adapter.ts @@ -89,6 +89,7 @@ export class PluginMenuCommandAdapter implements MenuCommandAdapter { ['comments/comment/title', toCommentArgs], ['comments/commentThread/context', toCommentArgs], ['debug/callstack/context', firstArgOnly], + ['debug/variables/context', firstArgOnly], ['debug/toolBar', noArgs], ['editor/context', selectedResource], ['editor/title', widgetURI], diff --git a/packages/plugin-ext/src/main/browser/menus/vscode-theia-menu-mappings.ts b/packages/plugin-ext/src/main/browser/menus/vscode-theia-menu-mappings.ts index 4b7c47f157871..97c04d2bb387c 100644 --- a/packages/plugin-ext/src/main/browser/menus/vscode-theia-menu-mappings.ts +++ b/packages/plugin-ext/src/main/browser/menus/vscode-theia-menu-mappings.ts @@ -21,6 +21,8 @@ import { injectable } from '@theia/core/shared/inversify'; import { URI as CodeUri } from '@theia/core/shared/vscode-uri'; import { DebugStackFramesWidget } from '@theia/debug/lib/browser/view/debug-stack-frames-widget'; import { DebugThreadsWidget } from '@theia/debug/lib/browser/view/debug-threads-widget'; +import { DebugToolBar } from '@theia/debug/lib/browser/view/debug-toolbar-widget'; +import { DebugVariablesWidget } from '@theia/debug/lib/browser/view/debug-variables-widget'; import { EditorWidget, EDITOR_CONTEXT_MENU } from '@theia/editor/lib/browser'; import { NAVIGATOR_CONTEXT_MENU } from '@theia/navigator/lib/browser/navigator-contribution'; import { ScmTreeWidget } from '@theia/scm/lib/browser/scm-tree-widget'; @@ -38,6 +40,8 @@ export const implementedVSCodeContributionPoints = [ 'comments/comment/title', 'comments/commentThread/context', 'debug/callstack/context', + 'debug/variables/context', + 'debug/toolBar', 'editor/context', 'editor/title', 'editor/title/context', @@ -59,6 +63,8 @@ export const codeToTheiaMappings = new Map([ ['comments/comment/title', [COMMENT_TITLE]], ['comments/commentThread/context', [COMMENT_THREAD_CONTEXT]], ['debug/callstack/context', [DebugStackFramesWidget.CONTEXT_MENU, DebugThreadsWidget.CONTEXT_MENU]], + ['debug/variables/context', [DebugVariablesWidget.CONTEXT_MENU]], + ['debug/toolBar', [DebugToolBar.MENU]], ['editor/context', [EDITOR_CONTEXT_MENU]], ['editor/title', [PLUGIN_EDITOR_TITLE_MENU]], ['editor/title/context', [SHELL_TABBAR_CONTEXT_MENU]], diff --git a/packages/plugin-ext/src/plugin/debug/debug-ext.ts b/packages/plugin-ext/src/plugin/debug/debug-ext.ts index 146d4d3b65d71..4bf42e7129a50 100644 --- a/packages/plugin-ext/src/plugin/debug/debug-ext.ts +++ b/packages/plugin-ext/src/plugin/debug/debug-ext.ts @@ -67,6 +67,7 @@ export class DebugExtImpl implements DebugExt { private readonly onDidChangeBreakpointsEmitter = new Emitter(); private readonly onDidChangeActiveDebugSessionEmitter = new Emitter(); private readonly onDidTerminateDebugSessionEmitter = new Emitter(); + private readonly onDidCreateDebugSessionEmitter = new Emitter(); private readonly onDidStartDebugSessionEmitter = new Emitter(); private readonly onDidReceiveDebugSessionCustomEmitter = new Emitter(); @@ -131,6 +132,10 @@ export class DebugExtImpl implements DebugExt { return this.onDidTerminateDebugSessionEmitter.event; } + get onDidCreateDebugSession(): theia.Event { + return this.onDidCreateDebugSessionEmitter.event; + } + get onDidStartDebugSession(): theia.Event { return this.onDidStartDebugSessionEmitter.event; } @@ -253,6 +258,13 @@ export class DebugExtImpl implements DebugExt { } async $sessionDidCreate(sessionId: string): Promise { + const session = this.sessions.get(sessionId); + if (session) { + this.onDidCreateDebugSessionEmitter.fire(session); + } + } + + async $sessionDidStart(sessionId: string): Promise { const session = this.sessions.get(sessionId); if (session) { this.onDidStartDebugSessionEmitter.fire(session);