From d32a2a6d58ddaee1b541d791d2b35fdcb370eb53 Mon Sep 17 00:00:00 2001 From: Divyansh Choudhary Date: Wed, 8 Nov 2023 18:09:23 +0530 Subject: [PATCH 001/147] Open files from errors (#13390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create renderer to render errors * WIP tests scaffold * Implement opening files from paths in error renderer * Finish sentence in a comment * Apply suggestions from code review Co-authored-by: Frédéric Collonval * Improve RFC 5147 adherence for ranges * Switch to 0-based line numbers based on RFC5147 2.2.3: "Line position counting starts with zero, so the line position before the first line of a text/plain MIME entity has the line position zero" * Fix undefined check after adding `parseInt` --------- Co-authored-by: krassowski <5832902+krassowski@users.noreply.github.com> Co-authored-by: Frédéric Collonval --- packages/debugger-extension/src/index.ts | 246 ++++++---- packages/debugger/src/debugger.ts | 2 + packages/debugger/src/index.ts | 3 +- packages/debugger/src/tokens.ts | 18 + packages/fileeditor/src/widget.ts | 39 +- packages/fileeditor/test/widget.spec.ts | 55 ++- packages/rendermime-extension/src/index.ts | 26 ++ packages/rendermime-interfaces/src/index.ts | 37 ++ packages/rendermime/src/factories.ts | 17 +- packages/rendermime/src/registry.ts | 49 +- packages/rendermime/src/renderers.ts | 479 ++++++++++++++++---- packages/rendermime/src/widgets.ts | 18 + packages/rendermime/style/base.css | 6 +- packages/rendermime/test/factories.spec.ts | 165 ++++++- 14 files changed, 972 insertions(+), 188 deletions(-) diff --git a/packages/debugger-extension/src/index.ts b/packages/debugger-extension/src/index.ts index 0bcddcc6d37e..bd84ab6f057b 100644 --- a/packages/debugger-extension/src/index.ts +++ b/packages/debugger-extension/src/index.ts @@ -13,12 +13,14 @@ import { } from '@jupyterlab/application'; import { Clipboard, + Dialog, ICommandPalette, InputDialog, ISessionContextDialogs, IThemeManager, MainAreaWidget, SessionContextDialogs, + showDialog, WidgetTracker } from '@jupyterlab/apputils'; import { CodeCell } from '@jupyterlab/cells'; @@ -31,7 +33,8 @@ import { IDebuggerConfig, IDebuggerHandler, IDebuggerSidebar, - IDebuggerSources + IDebuggerSources, + IDebuggerSourceViewer } from '@jupyterlab/debugger'; import { DocumentWidget } from '@jupyterlab/docregistry'; import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor'; @@ -615,6 +618,147 @@ const sidebar: JupyterFrontEndPlugin = { } }; +/** + * The source viewer UI plugin. + */ +const sourceViewer: JupyterFrontEndPlugin = { + id: '@jupyterlab/debugger-extension:source-viewer', + description: 'Initialize the debugger sources viewer.', + requires: [IDebugger, IEditorServices, IDebuggerSources, ITranslator], + provides: IDebuggerSourceViewer, + autoStart: true, + activate: async ( + app: JupyterFrontEnd, + service: IDebugger, + editorServices: IEditorServices, + debuggerSources: IDebugger.ISources, + translator: ITranslator + ): Promise => { + const readOnlyEditorFactory = new Debugger.ReadOnlyEditorFactory({ + editorServices + }); + const { model } = service; + + const onCurrentFrameChanged = ( + _: IDebugger.Model.ICallstack, + frame: IDebugger.IStackFrame + ): void => { + debuggerSources + .find({ + focus: true, + kernel: service.session?.connection?.kernel?.name ?? '', + path: service.session?.connection?.path ?? '', + source: frame?.source?.path ?? '' + }) + .forEach(editor => { + requestAnimationFrame(() => { + void editor.reveal().then(() => { + const edit = editor.get(); + if (edit) { + Debugger.EditorHandler.showCurrentLine(edit, frame.line); + } + }); + }); + }); + }; + model.callstack.currentFrameChanged.connect(onCurrentFrameChanged); + + const openSource = ( + source: IDebugger.Source, + breakpoint?: IDebugger.IBreakpoint + ): void => { + if (!source) { + return; + } + const { content, mimeType, path } = source; + const results = debuggerSources.find({ + focus: true, + kernel: service.session?.connection?.kernel?.name ?? '', + path: service.session?.connection?.path ?? '', + source: path + }); + if (results.length > 0) { + if (breakpoint && typeof breakpoint.line !== 'undefined') { + results.forEach(editor => { + void editor.reveal().then(() => { + editor.get()?.revealPosition({ + line: (breakpoint.line as number) - 1, + column: breakpoint.column || 0 + }); + }); + }); + } + return; + } + const editorWrapper = readOnlyEditorFactory.createNewEditor({ + content, + mimeType, + path + }); + const editor = editorWrapper.editor; + const editorHandler = new Debugger.EditorHandler({ + debuggerService: service, + editorReady: () => Promise.resolve(editor), + getEditor: () => editor, + path, + src: editor.model.sharedModel + }); + editorWrapper.disposed.connect(() => editorHandler.dispose()); + + debuggerSources.open({ + label: PathExt.basename(path), + caption: path, + editorWrapper + }); + + const frame = service.model.callstack.frame; + if (frame) { + Debugger.EditorHandler.showCurrentLine(editor, frame.line); + } + }; + + const trans = translator.load('jupyterlab'); + + app.commands.addCommand(Debugger.CommandIDs.openSource, { + label: trans.__('Open Source'), + caption: trans.__('Open Source'), + isEnabled: () => !!sourceViewer, + execute: async args => { + const path = (args.path as string) || ''; + if (!path) { + throw Error('Path to open is needed'); + } + if (!service.isStarted) { + const choice = await showDialog({ + title: trans.__('Start debugger?'), + body: trans.__( + 'The debugger service is needed to open the source %1', + path + ), + buttons: [ + Dialog.cancelButton({ label: trans.__('Cancel') }), + Dialog.okButton({ label: trans.__('Start debugger') }) + ] + }); + if (choice.button.accept) { + await service.start(); + } else { + return; + } + } + const source = await service.getSource({ + path + }); + return openSource(source); + } + }); + + return Object.freeze({ + open: openSource + }); + } +}; + /** * The main debugger UI plugin. */ @@ -624,7 +768,7 @@ const main: JupyterFrontEndPlugin = { requires: [IDebugger, IDebuggerSidebar, IEditorServices, ITranslator], optional: [ ICommandPalette, - IDebuggerSources, + IDebuggerSourceViewer, ILabShell, ILayoutRestorer, ILoggerRegistry, @@ -638,7 +782,7 @@ const main: JupyterFrontEndPlugin = { editorServices: IEditorServices, translator: ITranslator, palette: ICommandPalette | null, - debuggerSources: IDebugger.ISources | null, + sourceViewer: IDebugger.ISourceViewer | null, labShell: ILabShell | null, restorer: ILayoutRestorer | null, loggerRegistry: ILoggerRegistry | null, @@ -884,90 +1028,8 @@ const main: JupyterFrontEndPlugin = { }); } - if (debuggerSources) { + if (sourceViewer) { const { model } = service; - const readOnlyEditorFactory = new Debugger.ReadOnlyEditorFactory({ - editorServices - }); - - const onCurrentFrameChanged = ( - _: IDebugger.Model.ICallstack, - frame: IDebugger.IStackFrame - ): void => { - debuggerSources - .find({ - focus: true, - kernel: service.session?.connection?.kernel?.name ?? '', - path: service.session?.connection?.path ?? '', - source: frame?.source?.path ?? '' - }) - .forEach(editor => { - requestAnimationFrame(() => { - void editor.reveal().then(() => { - const edit = editor.get(); - if (edit) { - Debugger.EditorHandler.showCurrentLine(edit, frame.line); - } - }); - }); - }); - }; - - const onSourceOpened = ( - _: IDebugger.Model.ISources | null, - source: IDebugger.Source, - breakpoint?: IDebugger.IBreakpoint - ): void => { - if (!source) { - return; - } - const { content, mimeType, path } = source; - const results = debuggerSources.find({ - focus: true, - kernel: service.session?.connection?.kernel?.name ?? '', - path: service.session?.connection?.path ?? '', - source: path - }); - if (results.length > 0) { - if (breakpoint && typeof breakpoint.line !== 'undefined') { - results.forEach(editor => { - void editor.reveal().then(() => { - editor.get()?.revealPosition({ - line: (breakpoint.line as number) - 1, - column: breakpoint.column || 0 - }); - }); - }); - } - return; - } - const editorWrapper = readOnlyEditorFactory.createNewEditor({ - content, - mimeType, - path - }); - const editor = editorWrapper.editor; - const editorHandler = new Debugger.EditorHandler({ - debuggerService: service, - editorReady: () => Promise.resolve(editor), - getEditor: () => editor, - path, - src: editor.model.sharedModel - }); - editorWrapper.disposed.connect(() => editorHandler.dispose()); - - debuggerSources.open({ - label: PathExt.basename(path), - caption: path, - editorWrapper - }); - - const frame = service.model.callstack.frame; - if (frame) { - Debugger.EditorHandler.showCurrentLine(editor, frame.line); - } - }; - const onKernelSourceOpened = ( _: IDebugger.Model.IKernelSources | null, source: IDebugger.Source, @@ -976,11 +1038,14 @@ const main: JupyterFrontEndPlugin = { if (!source) { return; } - onSourceOpened(null, source, breakpoint); + sourceViewer.open(source, breakpoint); }; - model.callstack.currentFrameChanged.connect(onCurrentFrameChanged); - model.sources.currentSourceOpened.connect(onSourceOpened); + model.sources.currentSourceOpened.connect( + (_: IDebugger.Model.ISources | null, source: IDebugger.Source) => { + sourceViewer.open(source); + } + ); model.kernelSources.kernelSourceOpened.connect(onKernelSourceOpened); model.breakpoints.clicked.connect(async (_, breakpoint) => { const path = breakpoint.source?.path; @@ -988,7 +1053,7 @@ const main: JupyterFrontEndPlugin = { sourceReference: 0, path }); - onSourceOpened(null, source, breakpoint); + sourceViewer.open(source, breakpoint); }); } } @@ -1006,6 +1071,7 @@ const plugins: JupyterFrontEndPlugin[] = [ sidebar, main, sources, + sourceViewer, configuration ]; diff --git a/packages/debugger/src/debugger.ts b/packages/debugger/src/debugger.ts index a19285b1610e..23a03d869716 100644 --- a/packages/debugger/src/debugger.ts +++ b/packages/debugger/src/debugger.ts @@ -125,6 +125,8 @@ export namespace Debugger { export const copyToClipboard = 'debugger:copy-to-clipboard'; export const copyToGlobals = 'debugger:copy-to-globals'; + + export const openSource = 'debugger:open-source'; } /** diff --git a/packages/debugger/src/index.ts b/packages/debugger/src/index.ts index 2f4238da5dd1..b363c57b8544 100644 --- a/packages/debugger/src/index.ts +++ b/packages/debugger/src/index.ts @@ -12,5 +12,6 @@ export { IDebuggerConfig, IDebuggerSources, IDebuggerSidebar, - IDebuggerHandler + IDebuggerHandler, + IDebuggerSourceViewer } from './tokens'; diff --git a/packages/debugger/src/tokens.ts b/packages/debugger/src/tokens.ts index 9db5d2e0efa6..26ecd4cc9de5 100644 --- a/packages/debugger/src/tokens.ts +++ b/packages/debugger/src/tokens.ts @@ -329,6 +329,16 @@ export namespace IDebugger { */ export interface IHandler extends DebuggerHandler.IHandler {} + /** + * Interface for interacting with source viewer. + */ + export interface ISourceViewer { + /** + * Open read-only editor for given source and optionally set a breakpoint. + */ + open(source: IDebugger.Source, breakpoint?: IDebugger.IBreakpoint): void; + } + /** * An interface for a scope. */ @@ -1106,3 +1116,11 @@ export const IDebuggerHandler = new Token( '@jupyterlab/debugger:IDebuggerHandler', 'A service for handling notebook debugger.' ); + +/** + * The source viwer token. + */ +export const IDebuggerSourceViewer = new Token( + '@jupyterlab/debugger:IDebuggerSourceViewer', + 'A debugger source viewer.' +); diff --git a/packages/fileeditor/src/widget.ts b/packages/fileeditor/src/widget.ts index 263a0bb639b7..914d713a0f72 100644 --- a/packages/fileeditor/src/widget.ts +++ b/packages/fileeditor/src/widget.ts @@ -204,6 +204,43 @@ export namespace FileEditor { }; } +/** + * A document widget for file editor widgets. + */ +export class FileEditorWidget extends DocumentWidget { + /** + * Set URI fragment identifier for text files + */ + async setFragment(fragment: string): Promise { + const parsedFragments = fragment.split('='); + + // TODO: expand to allow more schemes of Fragment Identification Syntax + // reference: https://datatracker.ietf.org/doc/html/rfc5147#section-3 + if (parsedFragments[0] !== '#line') { + return; + } + + const positionOrRange = parsedFragments[1]; + let firstLine: string; + if (positionOrRange.includes(',')) { + // Only respect range start for now. + firstLine = positionOrRange.split(',')[0] || '0'; + } else { + firstLine = positionOrRange; + } + + // Reveal the line + return this.context.ready.then(() => { + const position = { + line: parseInt(firstLine, 10), + column: 0 + }; + this.content.editor.setCursorPosition(position); + this.content.editor.revealPosition(position); + }); + } +} + /** * A widget factory for editors. */ @@ -237,7 +274,7 @@ export class FileEditorFactory extends ABCWidgetFactory< }); content.title.icon = textEditorIcon; - const widget = new DocumentWidget({ content, context }); + const widget = new FileEditorWidget({ content, context }); return widget; } diff --git a/packages/fileeditor/test/widget.spec.ts b/packages/fileeditor/test/widget.spec.ts index 27c7b76034ee..10a3193e4ad7 100644 --- a/packages/fileeditor/test/widget.spec.ts +++ b/packages/fileeditor/test/widget.spec.ts @@ -12,7 +12,11 @@ import { DocumentWidget, TextModelFactory } from '@jupyterlab/docregistry'; -import { FileEditor, FileEditorFactory } from '@jupyterlab/fileeditor'; +import { + FileEditor, + FileEditorFactory, + FileEditorWidget +} from '@jupyterlab/fileeditor'; import { ServiceManager } from '@jupyterlab/services'; import { framePromise } from '@jupyterlab/testing'; import { ServiceManagerMock } from '@jupyterlab/services/lib/testutils'; @@ -178,6 +182,55 @@ describe('fileeditorcodewrapper', () => { }); }); + describe('FileEditorWidget', () => { + let documentWidget: FileEditorWidget; + + beforeEach(() => { + const path = UUID.uuid4() + '.py'; + context = new Context({ manager, factory: modelFactory, path }); + context.model.sharedModel.setSource('a\nb\nc'); + const content = new FileEditor({ + factory: options => factoryService.newDocumentEditor(options), + mimeTypeService, + context + }); + documentWidget = new FileEditorWidget({ content, context }); + }); + + afterEach(() => { + documentWidget.dispose(); + }); + + describe('#setFragment', () => { + it('should set fragment for a single line', async () => { + await context.initialize(true); + await context.ready; + const widget = documentWidget.content; + + await documentWidget.setFragment('#line=2'); + let cursor = widget.editor.getCursorPosition(); + expect(cursor.line).toBe(2); + + await documentWidget.setFragment('#line=0'); + cursor = widget.editor.getCursorPosition(); + expect(cursor.line).toBe(0); + }); + it('should set fragment for a range', async () => { + await context.initialize(true); + await context.ready; + const widget = documentWidget.content; + + await documentWidget.setFragment('#line=,3'); + let cursor = widget.editor.getCursorPosition(); + expect(cursor.line).toBe(0); + + await documentWidget.setFragment('#line=2,3'); + cursor = widget.editor.getCursorPosition(); + expect(cursor.line).toBe(2); + }); + }); + }); + describe('FileEditorFactory', () => { const widgetFactory = new FileEditorFactory({ editorServices: { diff --git a/packages/rendermime-extension/src/index.ts b/packages/rendermime-extension/src/index.ts index 7b654657191a..b1e8a0332861 100644 --- a/packages/rendermime-extension/src/index.ts +++ b/packages/rendermime-extension/src/index.ts @@ -50,6 +50,8 @@ const plugin: JupyterFrontEndPlugin = { */ export default plugin; +const DEBUGGER_OPEN_SOURCE = 'debugger:open-source'; + /** * Activate the rendermine plugin. */ @@ -68,9 +70,21 @@ function activate( execute: args => { const path = args['path'] as string | undefined | null; const id = args['id'] as string | undefined | null; + const scope = (args['scope'] as string | undefined | null) || 'server'; if (!path) { return; } + if (scope === 'kernel') { + // Note: using a command instead of requiring + // `IDebuggerSourceViewer` to avoid a dependency cycle. + if (!app.commands.hasCommand(DEBUGGER_OPEN_SOURCE)) { + console.warn( + 'Cannot open kernel file: debugger sources provider not available' + ); + return; + } + return app.commands.execute(DEBUGGER_OPEN_SOURCE, { path }); + } // First check if the path exists on the server. return docManager.services.contents .get(path, { content: false }) @@ -104,6 +118,18 @@ function activate( path, id }); + }, + handlePath: ( + node: HTMLElement, + path: string, + scope: 'kernel' | 'server', + id?: string + ) => { + app.commandLinker.connectNode(node, CommandIDs.handleLink, { + path, + id, + scope + }); } }, latexTypesetter: latexTypesetter ?? undefined, diff --git a/packages/rendermime-interfaces/src/index.ts b/packages/rendermime-interfaces/src/index.ts index e2558b1bce4a..05f325e08f08 100644 --- a/packages/rendermime-interfaces/src/index.ts +++ b/packages/rendermime-interfaces/src/index.ts @@ -437,6 +437,34 @@ export namespace IRenderMime { * @param id: an optional element id to scroll to when the path is opened. */ handleLink(node: HTMLElement, path: string, id?: string): void; + /** + * Add the path handler to the node. + * + * @param node: the anchor node for which to handle the link. + * + * @param path: the path to open when the link is clicked. + * + * @param scope: the scope to which the path is bound. + * + * @param id: an optional element id to scroll to when the path is opened. + */ + handlePath?( + node: HTMLElement, + path: string, + scope: 'kernel' | 'server', + id?: string + ): void; + } + + export interface IResolvedLocation { + /** + * Location scope. + */ + scope: 'kernel' | 'server'; + /** + * Resolved path. + */ + path: string; } /** @@ -466,6 +494,15 @@ export namespace IRenderMime { * resolver should handle a given URL. */ isLocal?: (url: string) => boolean; + + /** + * Resolve a path from Jupyter kernel to a path: + * - relative to `root_dir` (preferrably) this is in jupyter-server scope, + * - path understood and known by kernel (if such a path exists). + * Returns `null` if there is no file matching provided path in neither + * kernel nor jupyter-server contents manager. + */ + resolvePath?: (path: string) => Promise; } /** diff --git a/packages/rendermime/src/factories.ts b/packages/rendermime/src/factories.ts index eaa36808cfaa..525218f03747 100644 --- a/packages/rendermime/src/factories.ts +++ b/packages/rendermime/src/factories.ts @@ -61,16 +61,22 @@ export const svgRendererFactory: IRenderMime.IRendererFactory = { createRenderer: options => new widgets.RenderedSVG(options) }; +/** + * A mime renderer factory for rendering stderr outputs + */ +export const errorRendererFactory: IRenderMime.IRendererFactory = { + safe: true, + mimeTypes: ['application/vnd.jupyter.stderr'], + defaultRank: 110, + createRenderer: options => new widgets.RenderedError(options) +}; + /** * A mime renderer factory for plain and jupyter console text data. */ export const textRendererFactory: IRenderMime.IRendererFactory = { safe: true, - mimeTypes: [ - 'text/plain', - 'application/vnd.jupyter.stdout', - 'application/vnd.jupyter.stderr' - ], + mimeTypes: ['text/plain', 'application/vnd.jupyter.stdout'], defaultRank: 120, createRenderer: options => new widgets.RenderedText(options) }; @@ -96,5 +102,6 @@ export const standardRendererFactories: ReadonlyArray { + // TODO: a clean implementation would be server-side and depends on: + // https://github.com/jupyter-server/jupyter_server/issues/1280 + + const rootDir = PageConfig.getOption('rootUri').replace('file://', ''); + // Workaround: expand `~` path using root dir (if it matches). + if (path.startsWith('~/') && rootDir.startsWith('/home/')) { + // For now we assume that kernel is in root dir. + path = rootDir.split('/').slice(0, 3).join('/') + path.substring(1); + } + if (path.startsWith(rootDir) || path.startsWith('./')) { + try { + const relativePath = path.replace(rootDir, ''); + // If file exists on the server we have guessed right + const response = await this._contents.get(relativePath, { + content: false + }); + return { + path: response.path, + scope: 'server' + }; + } catch (error) { + // The file seems like should be on the server but is not. + console.warn(`Could not resolve location of ${path} on server`); + return null; + } + } + // The file is not accessible from jupyter-server but maybe it is + // available from DAP `source`; we assume the path is available + // from kernel because currently we have no way of checking this + // without introducing a cycle (unless we were to set the debugger + // service instance on the resolver later). + return { + path: path, + scope: 'kernel' + }; + } + /** * Whether the URL can be decoded using `decodeURI`. */ diff --git a/packages/rendermime/src/renderers.ts b/packages/rendermime/src/renderers.ts index e418ce5407b6..a503b037f9b8 100644 --- a/packages/rendermime/src/renderers.ts +++ b/packages/rendermime/src/renderers.ts @@ -491,54 +491,178 @@ export namespace renderSVG { } /** - * Replace URLs with links. - * - * @param content - The text content of a node. - * - * @returns A list of text nodes and anchor elements. + * Options for auto linker. */ -function autolink(content: string): Array { +interface IAutoLinkOptions { + /** + * Whether to look for web URLs e.g. indicated by http schema or www prefix. + */ + checkWeb: boolean; + /** + * Whether to look for path URIs. + */ + checkPaths: boolean; +} + +interface ILinker { + /** + * Regular expression capturing links in the group named `path`. + * + * Full match extend will be used as label for the link. + * Additional named groups represent locator fragments. + */ + regex: RegExp; + /** + * Create the anchor element. + */ + createAnchor: ( + text: string, + label: string, + attributes?: Record + ) => HTMLAnchorElement; + /** + * Modify the path value if needed. + */ + processPath?: (text: string) => string; + /** + * Modify the label if needed. + */ + processLabel?: (text: string) => string; +} + +namespace ILinker { // Taken from Visual Studio Code: // https://github.com/microsoft/vscode/blob/9f709d170b06e991502153f281ec3c012add2e42/src/vs/workbench/contrib/debug/browser/linkDetector.ts#L17-L18 const controlCodes = '\\u0000-\\u0020\\u007f-\\u009f'; - const webLinkRegex = new RegExp( - '(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + + export const webLinkRegex = new RegExp( + '(?(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + controlCodes + '"]{2,}[^\\s' + controlCodes + - '"\'(){}\\[\\],:;.!?]', + '"\'(){}\\[\\],:;.!?])', 'ug' ); + // Taken from Visual Studio Code: + // https://github.com/microsoft/vscode/blob/3e407526a1e2ff22cacb69c7e353e81a12f41029/extensions/notebook-renderers/src/linkify.ts#L9 + const winAbsPathRegex = /(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/; + const winRelPathRegex = /(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/; + const winPathRegex = new RegExp( + `(${winAbsPathRegex.source}|${winRelPathRegex.source})` + ); + const posixPathRegex = /((?:\~|\.)?(?:\/[\w\.-]*)+)/; + const lineColumnRegex = + /(?:(?:\:|", line )(?[\d]+))?(?:\:(?[\d]+))?/; + // TODO: this ought to come from kernel (browser may be on a different OS). + const isWindows = navigator.userAgent.indexOf('Windows') >= 0; + export const pathLinkRegex = new RegExp( + `(?${isWindows ? winPathRegex.source : posixPathRegex.source})${ + lineColumnRegex.source + }`, + 'g' + ); +} - const nodes = []; - let lastIndex = 0; - - let match: RegExpExecArray | null; - while (null != (match = webLinkRegex.exec(content))) { - if (match.index !== lastIndex) { - nodes.push( - document.createTextNode(content.slice(lastIndex, match.index)) - ); - } - let url = match[0]; +/** + * Linker for web URLs. + */ +class WebLinker implements ILinker { + regex = ILinker.webLinkRegex; + createAnchor(url: string, label: string) { + const anchor = document.createElement('a'); + anchor.href = url.startsWith('www.') ? 'https://' + url : url; + anchor.rel = 'noopener'; + anchor.target = '_blank'; + anchor.appendChild(document.createTextNode(label)); + return anchor; + } + processPath(url: string) { // Special case when the URL ends with ">" or "<" const lastChars = url.slice(-1); const endsWithGtLt = ['>', '<'].indexOf(lastChars) !== -1; const len = endsWithGtLt ? url.length - 1 : url.length; - const anchor = document.createElement('a'); url = url.slice(0, len); - anchor.href = url.startsWith('www.') ? 'https://' + url : url; - anchor.rel = 'noopener'; - anchor.target = '_blank'; - anchor.appendChild(document.createTextNode(url.slice(0, len))); - nodes.push(anchor); - lastIndex = match.index + len; + return url; } - if (lastIndex !== content.length) { - nodes.push( - document.createTextNode(content.slice(lastIndex, content.length)) - ); + processLabel(url: string) { + return this.processPath(url); } +} + +/** + * Linker for path URIs. + */ +class PathLinker implements ILinker { + regex = ILinker.pathLinkRegex; + createAnchor(path: string, label: string, locators: Record) { + const anchor = document.createElement('a'); + + // Store the path in dataset. + // Do not set `href` - at this point we do not know if the path is valid and + // accessible for application (and we want rendering those as links). + anchor.dataset.path = path; + + // Store line using RFC 5147 fragment locator for text/plain files. + // It could be expanded to other formats, e.g. based on file extension. + const line = parseInt(locators['line'], 10); + let locator: string = !isNaN(line) ? `line=${line - 1}` : ''; + anchor.dataset.locator = locator; + + anchor.appendChild(document.createTextNode(label)); + return anchor; + } +} + +function autolink( + content: string, + options: IAutoLinkOptions +): Array { + const linkers: ILinker[] = []; + if (options.checkWeb) { + linkers.push(new WebLinker()); + } + if (options.checkPaths) { + linkers.push(new PathLinker()); + } + const nodes: Array = []; + + // There are two ways to implement competitive regexes: + // - two heads (which would need to resolve overlaps), or + // - (simpler) divide and recurse (implemented below) + const linkify = (content: string, regexIndex: number) => { + if (regexIndex >= linkers.length) { + nodes.push(document.createTextNode(content)); + return; + } + + const linker = linkers[regexIndex]; + + let match: RegExpExecArray | null; + let currentIndex = 0; + const regex = linker.regex; + // Reset regex + regex.lastIndex = 0; + + while (null != (match = regex.exec(content))) { + const stringBeforeMatch = content.substring(currentIndex, match.index); + if (stringBeforeMatch) { + linkify(stringBeforeMatch, regexIndex + 1); + } + + const { path, ...locators } = match.groups!; + const value = linker.processPath ? linker.processPath(path) : path; + const label = linker.processLabel + ? linker.processLabel(match[0]) + : match[0]; + nodes.push(linker.createAnchor(value, label, locators)); + currentIndex = match.index + label.length; + } + const stringAfterMatches = content.substring(currentIndex); + if (stringAfterMatches) { + linkify(stringAfterMatches, regexIndex + 1); + } + }; + + linkify(content, 0); return nodes; } @@ -672,68 +796,26 @@ export function renderText(options: renderText.IRenderOptions): Promise { }); // Set the sanitized content for the host node. - const ret = document.createElement('pre'); const pre = document.createElement('pre'); pre.innerHTML = content; const preTextContent = pre.textContent; + let ret: HTMLPreElement; if (preTextContent) { // Note: only text nodes and span elements should be present after sanitization in the `
` element.
     const linkedNodes =
       sanitizer.getAutolink?.() ?? true
-        ? autolink(preTextContent)
+        ? autolink(preTextContent, {
+            checkWeb: true,
+            checkPaths: false
+          })
         : [document.createTextNode(content)];
-    let inAnchorElement = false;
 
-    const combinedNodes: (HTMLAnchorElement | Text | HTMLSpanElement)[] = [];
     const preNodes = Array.from(pre.childNodes) as (Text | HTMLSpanElement)[];
-
-    for (let nodes of alignedNodes(preNodes, linkedNodes)) {
-      if (!nodes[0]) {
-        combinedNodes.push(nodes[1]);
-        inAnchorElement = nodes[1].nodeType !== Node.TEXT_NODE;
-        continue;
-      } else if (!nodes[1]) {
-        combinedNodes.push(nodes[0]);
-        inAnchorElement = false;
-        continue;
-      }
-      let [preNode, linkNode] = nodes;
-
-      const lastCombined = combinedNodes[combinedNodes.length - 1];
-
-      // If we are already in an anchor element and the anchor element did not change,
-      // we should insert the node from 
 which is either Text node or coloured span Element
-      // into the anchor content as a child
-      if (
-        inAnchorElement &&
-        (linkNode as HTMLAnchorElement).href ===
-          (lastCombined as HTMLAnchorElement).href
-      ) {
-        lastCombined.appendChild(preNode);
-      } else {
-        // the `linkNode` is either Text or AnchorElement;
-        const isAnchor = linkNode.nodeType !== Node.TEXT_NODE;
-        // if we are NOT about to start an anchor element, just add the pre Node
-        if (!isAnchor) {
-          combinedNodes.push(preNode);
-          inAnchorElement = false;
-        } else {
-          // otherwise start a new anchor; the contents of the `linkNode` and `preNode` should be the same,
-          // so we just put the neatly formatted `preNode` inside the anchor node (`linkNode`)
-          // and append that to combined nodes.
-          linkNode.textContent = '';
-          linkNode.appendChild(preNode);
-          combinedNodes.push(linkNode);
-          inAnchorElement = true;
-        }
-      }
-    }
-    // Do not reuse `pre` element. Clearing out previous children is too slow...
-    for (const child of combinedNodes) {
-      ret.appendChild(child);
-    }
+    ret = mergeNodes(preNodes, linkedNodes);
+  } else {
+    ret = document.createElement('pre');
   }
 
   host.appendChild(ret);
@@ -772,6 +854,160 @@ export namespace renderText {
   }
 }
 
+/**
+ * Render error into a host node.
+ *
+ * @param options - The options for rendering.
+ *
+ * @returns A promise which resolves when rendering is complete.
+ */
+export function renderError(
+  options: renderError.IRenderOptions
+): Promise {
+  // Unpack the options.
+  const { host, linkHandler, sanitizer, resolver, source } = options;
+
+  // Create the HTML content.
+  const content = sanitizer.sanitize(Private.ansiSpan(source), {
+    allowedTags: ['span']
+  });
+
+  // Set the sanitized content for the host node.
+  const pre = document.createElement('pre');
+  pre.innerHTML = content;
+
+  const preTextContent = pre.textContent;
+
+  let ret: HTMLPreElement;
+  if (preTextContent) {
+    // Note: only text nodes and span elements should be present after sanitization in the `
` element.
+    const linkedNodes =
+      sanitizer.getAutolink?.() ?? true
+        ? autolink(preTextContent, {
+            checkWeb: true,
+            checkPaths: true
+          })
+        : [document.createTextNode(content)];
+
+    const preNodes = Array.from(pre.childNodes) as (Text | HTMLSpanElement)[];
+    ret = mergeNodes(preNodes, linkedNodes);
+  } else {
+    ret = document.createElement('pre');
+  }
+  host.appendChild(ret);
+
+  // Patch the paths if a resolver is available.
+  let promise: Promise;
+  if (resolver) {
+    promise = Private.handlePaths(host, resolver, linkHandler);
+  } else {
+    promise = Promise.resolve(undefined);
+  }
+
+  // Return the rendered promise.
+  return promise;
+}
+
+/**
+ * Merge `` nodes from a `
` element with `` nodes from linker.
+ */
+function mergeNodes(
+  preNodes: (Text | HTMLSpanElement)[],
+  linkedNodes: (Text | HTMLAnchorElement)[]
+): HTMLPreElement {
+  const ret = document.createElement('pre');
+  let inAnchorElement = false;
+
+  const combinedNodes: (HTMLAnchorElement | Text | HTMLSpanElement)[] = [];
+
+  for (let nodes of alignedNodes(preNodes, linkedNodes)) {
+    if (!nodes[0]) {
+      combinedNodes.push(nodes[1]);
+      inAnchorElement = nodes[1].nodeType !== Node.TEXT_NODE;
+      continue;
+    } else if (!nodes[1]) {
+      combinedNodes.push(nodes[0]);
+      inAnchorElement = false;
+      continue;
+    }
+    let [preNode, linkNode] = nodes;
+
+    const lastCombined = combinedNodes[combinedNodes.length - 1];
+
+    // If we are already in an anchor element and the anchor element did not change,
+    // we should insert the node from 
 which is either Text node or coloured span Element
+    // into the anchor content as a child
+    if (
+      inAnchorElement &&
+      (linkNode as HTMLAnchorElement).href ===
+        (lastCombined as HTMLAnchorElement).href
+    ) {
+      lastCombined.appendChild(preNode);
+    } else {
+      // the `linkNode` is either Text or AnchorElement;
+      const isAnchor = linkNode.nodeType !== Node.TEXT_NODE;
+      // if we are NOT about to start an anchor element, just add the pre Node
+      if (!isAnchor) {
+        combinedNodes.push(preNode);
+        inAnchorElement = false;
+      } else {
+        // otherwise start a new anchor; the contents of the `linkNode` and `preNode` should be the same,
+        // so we just put the neatly formatted `preNode` inside the anchor node (`linkNode`)
+        // and append that to combined nodes.
+        linkNode.textContent = '';
+        linkNode.appendChild(preNode);
+        combinedNodes.push(linkNode);
+        inAnchorElement = true;
+      }
+    }
+  }
+  // Do not reuse `pre` element. Clearing out previous children is too slow...
+  for (const child of combinedNodes) {
+    ret.appendChild(child);
+  }
+  return ret;
+}
+
+/**
+ * The namespace for the `renderError` function statics.
+ */
+export namespace renderError {
+  /**
+   * The options for the `renderError` function.
+   */
+  export interface IRenderOptions {
+    /**
+     * The host node for the error content.
+     */
+    host: HTMLElement;
+
+    /**
+     * The html sanitizer for untrusted source.
+     */
+    sanitizer: IRenderMime.ISanitizer;
+
+    /**
+     * The source error to render.
+     */
+    source: string;
+
+    /**
+     * An optional url resolver.
+     */
+    resolver: IRenderMime.IResolver | null;
+
+    /**
+     * An optional link handler.
+     */
+    linkHandler: IRenderMime.ILinkHandler | null;
+
+    /**
+     * The application language translator.
+     */
+    translator?: ITranslator;
+  }
+}
+
 /**
  * The namespace for module implementation details.
  */
@@ -894,6 +1130,29 @@ namespace Private {
     return Promise.all(promises).then(() => undefined);
   }
 
+  /**
+   * Resolve the paths in `` elements `data` attributes.
+   *
+   * @param node - The head html element.
+   *
+   * @param resolver - A url resolver.
+   *
+   * @param linkHandler - An optional link handler for nodes.
+   *
+   * @returns a promise fulfilled when the relative urls have been resolved.
+   */
+  export async function handlePaths(
+    node: HTMLElement,
+    resolver: IRenderMime.IResolver,
+    linkHandler: IRenderMime.ILinkHandler | null
+  ): Promise {
+    // Handle anchor elements.
+    const anchors = node.getElementsByTagName('a');
+    for (let i = 0; i < anchors.length; i++) {
+      await handlePathAnchor(anchors[i], resolver, linkHandler);
+    }
+  }
+
   /**
    * Apply ids to headers.
    */
@@ -999,6 +1258,64 @@ namespace Private {
       });
   }
 
+  /**
+   * Handle an anchor node.
+   */
+  async function handlePathAnchor(
+    anchor: HTMLAnchorElement,
+    resolver: IRenderMime.IResolver,
+    linkHandler: IRenderMime.ILinkHandler | null
+  ): Promise {
+    let path = anchor.dataset.path || '';
+    let locator = anchor.dataset.locator ? '#' + anchor.dataset.locator : '';
+    delete anchor.dataset.path;
+    delete anchor.dataset.locator;
+
+    const isLocal = resolver.isLocal
+      ? resolver.isLocal(path)
+      : URLExt.isLocal(path);
+
+    // Bail if:
+    // - it is not a file-like url,
+    // - the resolver does not support paths
+    // - there is no link handler, or if it does not support paths
+    if (
+      !path ||
+      !isLocal ||
+      !resolver.resolvePath ||
+      !linkHandler ||
+      !linkHandler.handlePath
+    ) {
+      anchor.replaceWith(...anchor.childNodes);
+      return Promise.resolve(undefined);
+    }
+    try {
+      // Find given path
+      const resolution = await resolver.resolvePath(path);
+
+      if (!resolution) {
+        // Bail if the file does not exist
+        console.log('Path resolution bailing: does not exist');
+        return Promise.resolve(undefined);
+      }
+
+      // Handle the click override.
+      linkHandler.handlePath(
+        anchor,
+        resolution.path,
+        resolution.scope,
+        locator
+      );
+
+      // Set the visible anchor.
+      anchor.href = resolution.path + locator;
+    } catch (err) {
+      // If there was an error getting the url,
+      // just make it an empty link.
+      console.warn('Path anchor error:', err);
+      anchor.href = '#linking-failed-see-console';
+    }
+  }
   const ANSI_COLORS = [
     'ansi-black',
     'ansi-red',
diff --git a/packages/rendermime/src/widgets.ts b/packages/rendermime/src/widgets.ts
index d058cdaf1456..879dcd94af46 100644
--- a/packages/rendermime/src/widgets.ts
+++ b/packages/rendermime/src/widgets.ts
@@ -416,6 +416,24 @@ export class RenderedText extends RenderedCommon {
   }
 }
 
+export class RenderedError extends RenderedCommon {
+  constructor(options: IRenderMime.IRendererOptions) {
+    super(options);
+    this.addClass('jp-RenderedText');
+  }
+
+  render(model: IRenderMime.IMimeModel): Promise {
+    return renderers.renderError({
+      host: this.node,
+      sanitizer: this.sanitizer,
+      source: String(model.data[this.mimeType]),
+      linkHandler: this.linkHandler,
+      resolver: this.resolver,
+      translator: this.translator
+    });
+  }
+}
+
 /**
  * A widget for displaying JavaScript output.
  */
diff --git a/packages/rendermime/style/base.css b/packages/rendermime/style/base.css
index 9910229f8fa9..af777208fc21 100644
--- a/packages/rendermime/style/base.css
+++ b/packages/rendermime/style/base.css
@@ -31,17 +31,17 @@
   padding: 0;
 }
 
-.jp-RenderedText pre a:link {
+.jp-RenderedText pre a[href]:link {
   text-decoration: none;
   color: var(--jp-content-link-color);
 }
 
-.jp-RenderedText pre a:hover {
+.jp-RenderedText pre a[href]:hover {
   text-decoration: underline;
   color: var(--jp-content-link-hover-color, var(--jp-content-link-color));
 }
 
-.jp-RenderedText pre a:visited {
+.jp-RenderedText pre a[href]:visited {
   text-decoration: none;
   color: var(--jp-content-link-visited-color, var(--jp-content-link-color));
 }
diff --git a/packages/rendermime/test/factories.spec.ts b/packages/rendermime/test/factories.spec.ts
index 9a34eed42456..f03c9fc8ccf8 100644
--- a/packages/rendermime/test/factories.spec.ts
+++ b/packages/rendermime/test/factories.spec.ts
@@ -3,6 +3,7 @@
 
 import { Sanitizer } from '@jupyterlab/apputils';
 import {
+  errorRendererFactory,
   htmlRendererFactory,
   imageRendererFactory,
   IMarkdownParser,
@@ -41,11 +42,7 @@ describe('rendermime/factories', () => {
   describe('textRendererFactory', () => {
     describe('#mimeTypes', () => {
       it('should have text related mimeTypes', () => {
-        const mimeTypes = [
-          'text/plain',
-          'application/vnd.jupyter.stdout',
-          'application/vnd.jupyter.stderr'
-        ];
+        const mimeTypes = ['text/plain', 'application/vnd.jupyter.stdout'];
         expect(textRendererFactory.mimeTypes).toEqual(mimeTypes);
       });
     });
@@ -496,4 +493,162 @@ describe('rendermime/factories', () => {
       });
     });
   });
+
+  describe('errorRendererFactory', () => {
+    describe('#mimeTypes', () => {
+      it('should support application/vnd.jupyter.stderr mime types', () => {
+        expect(errorRendererFactory.mimeTypes).toEqual([
+          'application/vnd.jupyter.stderr'
+        ]);
+      });
+    });
+
+    const knownPaths = [
+      '/usr/local/lib/message.py',
+      '/tmp/ipykernel_361344/2220647380.py',
+      '~/jupyterlab/a_file.py',
+      '~/jupyterlab/b_file.py',
+      '/home/user/jupyterlab/a_file.py'
+    ];
+    const options = {
+      ...defaultOptions,
+      resolver: {
+        resolvePath: (url: string) => {
+          if (knownPaths.includes(url)) {
+            return Promise.resolve({
+              path: url,
+              scope: 'server'
+            });
+          }
+          return Promise.resolve(null);
+        },
+        isLocal: (url: string) => {
+          return knownPaths.includes(url);
+        },
+        exists: () => {
+          return false;
+        }
+      },
+      linkHandler: {
+        handlePath: (...args: any[]) => {
+          // no-op
+          return null;
+        }
+      }
+    };
+
+    describe('#createRenderer()', () => {
+      it('should output the correct HTML', async () => {
+        const f = errorRendererFactory;
+        const mimeType = 'application/vnd.jupyter.stderr';
+        const model = createModel(mimeType, 'x = 2 ** a');
+        const w = f.createRenderer({ mimeType, ...options });
+        await w.renderModel(model);
+        expect(w.node.innerHTML).toBe('
x = 2 ** a
'); + }); + + it('should be re-renderable', async () => { + const f = errorRendererFactory; + const mimeType = 'application/vnd.jupyter.stderr'; + const model = createModel(mimeType, 'x = 2 ** a'); + const w = f.createRenderer({ mimeType, ...options }); + await w.renderModel(model); + await w.renderModel(model); + expect(w.node.innerHTML).toBe('
x = 2 ** a
'); + }); + + it('should escape inline html', async () => { + const f = errorRendererFactory; + const source = + 'There is no text but \x1b[01;41;32mtext\x1b[00m.\nWoo.'; + const mimeType = 'application/vnd.jupyter.stderr'; + const model = createModel(mimeType, source); + const w = f.createRenderer({ mimeType, ...options }); + await w.renderModel(model); + expect(w.node.innerHTML).toBe( + '
There is no text <script>window.x=1</script> but text.\nWoo.
' + ); + }); + + it('should autolink a single known file path', async () => { + const f = errorRendererFactory; + const urls = [ + ['/usr/local/lib/message.py', '', ''], + ['/usr/local/lib/message.py', '"', '"'], + ['/tmp/ipykernel_361344/2220647380.py', '', ''] + ]; + await Promise.all( + urls.map(async u => { + const [url, before, after] = u; + const source = `Text with the URL ${before}${url}${after} inside.`; + const mimeType = 'application/vnd.jupyter.stderr'; + const model = createModel(mimeType, source); + const w = f.createRenderer({ mimeType, ...options }); + const [urlEncoded, beforeEncoded, afterEncoded] = [ + url, + before, + after + ].map(encodeChars); + const prefixedUrl = urlEncoded.startsWith('www.') + ? 'https://' + urlEncoded + : urlEncoded; + await w.renderModel(model); + expect(w.node.innerHTML).toBe( + `
Text with the URL ${beforeEncoded}${urlEncoded}${afterEncoded} inside.
` + ); + }) + ); + }); + + it('should autolink multiple links', async () => { + const f = errorRendererFactory; + const source = + 'prefix ~/jupyterlab/a_file.py:1 suffix\nprefix ~/jupyterlab/b_file.py:1 suffix'; + const mimeType = 'application/vnd.jupyter.stderr'; + const model = createModel(mimeType, source); + const w = f.createRenderer({ mimeType, ...options }); + await w.renderModel(model); + expect(w.node.innerHTML).toBe( + '
prefix ~/jupyterlab/a_file.py:1 suffix\nprefix ~/jupyterlab/b_file.py:1 suffix
' + ); + }); + + it('should autolink to a specific line (Python style)', async () => { + const f = errorRendererFactory; + const source = + 'File "/home/user/jupyterlab/a_file.py", line 1, in '; + const mimeType = 'application/vnd.jupyter.stderr'; + const model = createModel(mimeType, source); + const w = f.createRenderer({ mimeType, ...options }); + await w.renderModel(model); + expect(w.node.innerHTML).toBe( + '
File "/home/user/jupyterlab/a_file.py", line 1, in <module>
' + ); + }); + + it('should autolink to a specific line (IPython style)', async () => { + const f = errorRendererFactory; + const source = 'File ~/jupyterlab/a_file.py:1'; + const mimeType = 'application/vnd.jupyter.stderr'; + const model = createModel(mimeType, source); + const w = f.createRenderer({ mimeType, ...options }); + await w.renderModel(model); + expect(w.node.innerHTML).toBe( + '
File ~/jupyterlab/a_file.py:1
' + ); + }); + + it('should autolink URLs', async () => { + const source = 'www.example.com'; + const expected = + '
www.example.com
'; + const f = textRendererFactory; + const mimeType = 'application/vnd.jupyter.stderr'; + const model = createModel(mimeType, source); + const w = f.createRenderer({ mimeType, ...options }); + await w.renderModel(model); + expect(w.node.innerHTML).toBe(expected); + }); + }); + }); }); From 08122b8329a793acecce45d070d214cd9a945840 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:04:40 +0100 Subject: [PATCH 002/147] Bump tj-actions/changed-files from 39.2.0 to 40.0.2 (#15342) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 39.2.0 to 40.0.2. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v39.2.0...v40.0.2) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reject-staging-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reject-staging-changes.yml b/.github/workflows/reject-staging-changes.yml index 84c0a7ba21f7..6a16e20817d7 100644 --- a/.github/workflows/reject-staging-changes.yml +++ b/.github/workflows/reject-staging-changes.yml @@ -16,7 +16,7 @@ jobs: - name: Get modified files in the staging directory id: modified-files-in-staging - uses: tj-actions/changed-files@v39.2.0 + uses: tj-actions/changed-files@v40.0.2 with: # only checks for modified files in this directory files: jupyterlab/staging From 279cc2ea0fef89c2919f5405fb1b7d56bb124e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:45:02 +0000 Subject: [PATCH 003/147] Fix scrolling past long outputs in presence of un-rendered headings (#15356) * Add a test for smooth scrolling over long outputs * Do no scroll when setting the cursor position in md cells * Adjust test name to clarify the test case * Add the test notebook * Clean up the test directory --- galata/src/helpers/notebook.ts | 5 +- .../test/jupyterlab/notebook-scroll.test.ts | 57 +++++ .../notebooks/long_output_and_headings.ipynb | 230 ++++++++++++++++++ packages/cells/src/widget.ts | 11 +- packages/codeeditor/src/editor.ts | 6 +- packages/codemirror/src/editor.ts | 2 +- 6 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 galata/test/jupyterlab/notebooks/long_output_and_headings.ipynb diff --git a/galata/src/helpers/notebook.ts b/galata/src/helpers/notebook.ts index b8fe1df63864..f21aa525f0df 100644 --- a/galata/src/helpers/notebook.ts +++ b/galata/src/helpers/notebook.ts @@ -787,7 +787,10 @@ export class NotebookHelper { } /** - * Whether the cell is in editing mode or not + * Whether the cell is in editing mode or not. + * + * This method is not suitable for checking if a cell is unrendered + * as it will return false when the cell is not active (not focused). * * @param cellIndex Cell index * @returns Editing mode diff --git a/galata/test/jupyterlab/notebook-scroll.test.ts b/galata/test/jupyterlab/notebook-scroll.test.ts index fb71109551bf..e8e3959ee187 100644 --- a/galata/test/jupyterlab/notebook-scroll.test.ts +++ b/galata/test/jupyterlab/notebook-scroll.test.ts @@ -52,3 +52,60 @@ test.describe('Notebook Scroll', () => { }); } }); + +test.describe('Notebook scroll over long outputs', () => { + const outputAndHeading = 'long_output_and_headings.ipynb'; + test.beforeEach(async ({ page, tmpPath }) => { + await page.contents.uploadFile( + path.resolve(__dirname, `./notebooks/${outputAndHeading}`), + `${tmpPath}/${outputAndHeading}` + ); + + await page.notebook.openByPath(`${tmpPath}/${outputAndHeading}`); + await page.notebook.activate(outputAndHeading); + }); + + test.afterEach(async ({ page, tmpPath }) => { + await page.contents.deleteDirectory(tmpPath); + }); + + test('should scroll smoothly without snapping to headings', async ({ + page + }) => { + const renderedMarkdownLocator = page.locator( + '.jp-Cell .jp-RenderedMarkdown:has-text("Before")' + ); + // Wait until Markdown cells are rendered + await renderedMarkdownLocator.waitFor({ timeout: 100 }); + // Un-render the "before" markdown cell + await renderedMarkdownLocator.dblclick(); + // Make the first cell active + await page.notebook.selectCells(0); + // Check that that the markdown cell is un-rendered + await renderedMarkdownLocator.waitFor({ state: 'hidden', timeout: 100 }); + + // Scroll to the last cell + const lastCell = await page.notebook.getCell(10); + await lastCell.scrollIntoViewIfNeeded(); + + // Get the outer window + const outer = page.locator('.jp-WindowedPanel-outer'); + + let previousOffset = await outer.evaluate(node => node.scrollTop); + expect(previousOffset).toBeGreaterThan(1000); + + // Scroll piece by piece checking that there is no jump + while (previousOffset > 75) { + await page.mouse.wheel(0, -100); + // Explicit wait because mouse wheel does not wait for scrolling. + await page.waitForTimeout(150); + const offset = await outer.evaluate(node => node.scrollTop); + const diff = previousOffset - offset; + // The scroll should be by about 100px, but because wheel event + // does not wait we allow for some wiggle room (+/-25px). + expect(diff).toBeLessThanOrEqual(125); + expect(diff).toBeGreaterThan(75); + previousOffset = offset; + } + }); +}); diff --git a/galata/test/jupyterlab/notebooks/long_output_and_headings.ipynb b/galata/test/jupyterlab/notebooks/long_output_and_headings.ipynb new file mode 100644 index 000000000000..c01d0f57f6cb --- /dev/null +++ b/galata/test/jupyterlab/notebooks/long_output_and_headings.ipynb @@ -0,0 +1,230 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "61b4490f-af96-4e09-94fa-8de641547698", + "metadata": {}, + "source": [ + "Active cell." + ] + }, + { + "cell_type": "markdown", + "id": "39eaf05d-3a87-4320-b722-06c57c2cd25a", + "metadata": {}, + "source": [ + "# before" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "194ec3b5-1085-4ff9-9ede-9b6795c126d7", + "metadata": { + "execution": { + "iopub.execute_input": "2023-11-05T16:27:02.889334Z", + "iopub.status.busy": "2023-11-05T16:27:02.888808Z", + "iopub.status.idle": "2023-11-05T16:27:02.903710Z", + "shell.execute_reply": "2023-11-05T16:27:02.902631Z", + "shell.execute_reply.started": "2023-11-05T16:27:02.889294Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n", + "4\n", + "5\n", + "6\n", + "7\n", + "8\n", + "9\n", + "10\n", + "11\n", + "12\n", + "13\n", + "14\n", + "15\n", + "16\n", + "17\n", + "18\n", + "19\n", + "20\n", + "21\n", + "22\n", + "23\n", + "24\n", + "25\n", + "26\n", + "27\n", + "28\n", + "29\n", + "30\n", + "31\n", + "32\n", + "33\n", + "34\n", + "35\n", + "36\n", + "37\n", + "38\n", + "39\n", + "40\n", + "41\n", + "42\n", + "43\n", + "44\n", + "45\n", + "46\n", + "47\n", + "48\n", + "49\n", + "50\n", + "51\n", + "52\n", + "53\n", + "54\n", + "55\n", + "56\n", + "57\n", + "58\n", + "59\n", + "60\n", + "61\n", + "62\n", + "63\n", + "64\n", + "65\n", + "66\n", + "67\n", + "68\n", + "69\n", + "70\n", + "71\n", + "72\n", + "73\n", + "74\n", + "75\n", + "76\n", + "77\n", + "78\n", + "79\n", + "80\n", + "81\n", + "82\n", + "83\n", + "84\n", + "85\n", + "86\n", + "87\n", + "88\n", + "89\n", + "90\n", + "91\n", + "92\n", + "93\n", + "94\n", + "95\n", + "96\n", + "97\n", + "98\n", + "99\n" + ] + } + ], + "source": [ + "print('\\n'.join(str(i) for i in range(100)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6475f01e-273c-4fde-81d4-66b1c9cb4c23", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "479b1c22-96dd-47cc-83a2-913d657f088c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4a6a953-4397-480b-b52e-38fecc9c6117", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d10fb1f-97ff-4f71-8be7-8cd263f1a0cf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "228273a6-264e-46af-8ecb-17548f2dfdd2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9eea2c6d-2364-4dee-bc95-c4e3e051d943", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92d3c01c-8fa3-44e6-9097-25a71d9f2b8d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5a45dd6e-aa7d-49e4-b8a5-9416f2ef3997", + "metadata": {}, + "source": [ + "Last cell" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/packages/cells/src/widget.ts b/packages/cells/src/widget.ts index 196f74381b78..12aaff460670 100644 --- a/packages/cells/src/widget.ts +++ b/packages/cells/src/widget.ts @@ -2253,10 +2253,13 @@ export class MarkdownCell extends AttachmentsCell { .getSource() .match(/^#+/g) || [''])[0].length; if (numHashAtStart > 0) { - this.inputArea!.editor.setCursorPosition({ - column: numHashAtStart + 1, - line: 0 - }); + this.inputArea!.editor.setCursorPosition( + { + column: numHashAtStart + 1, + line: 0 + }, + { scroll: false } + ); } } } diff --git a/packages/codeeditor/src/editor.ts b/packages/codeeditor/src/editor.ts index 286da0404e66..e0e7354a01cc 100644 --- a/packages/codeeditor/src/editor.ts +++ b/packages/codeeditor/src/editor.ts @@ -257,11 +257,15 @@ export namespace CodeEditor { * Set the primary position of the cursor. * * @param position - The new primary position. + * @param options - Adjustment options allowing to disable scrolling. * * #### Notes * This will remove any secondary cursors. */ - setCursorPosition(position: IPosition): void; + setCursorPosition( + position: IPosition, + options?: { scroll?: boolean } + ): void; /** * Returns the primary selection, never `null`. diff --git a/packages/codemirror/src/editor.ts b/packages/codemirror/src/editor.ts index d34670281e1e..b9fa4022566e 100644 --- a/packages/codemirror/src/editor.ts +++ b/packages/codemirror/src/editor.ts @@ -403,7 +403,7 @@ export class CodeMirrorEditor implements CodeEditor.IEditor { const offset = this.getOffsetAt(position); this.editor.dispatch({ selection: { anchor: offset }, - scrollIntoView: true + scrollIntoView: options?.scroll === false ? false : true }); // If the editor does not have focus, this cursor change // will get screened out in _onCursorsChanged(). Make an From 5fa331677000a997a48c5068ab28194660a8df3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:03:18 +0000 Subject: [PATCH 004/147] Fix overreactive scrolling to next cell after `Shift + Enter` (#15288) * Add a test case for #14878 * Fix the smart/auto scrolling logic for long items Items larger than the size of the viewport were not considered by the scroll logic leading to bad UX when smart/auto scroll was requested on certain operations. * Use pixel-based, cell-height-derived threshold for scrolling * Implement dual behaviour, simplify code, account for top padding * Use greater or equal to avoid scrolling if item fully visible * Update tests to reflect new expectations * Clean up the temp directory after tests --- .../test/jupyterlab/notebook-scroll.test.ts | 81 +++++- .../jupyterlab/notebooks/long_outputs.ipynb | 253 ++++++++++++++++++ .../test/jupyterlab/windowed-notebook.test.ts | 25 +- packages/notebook/src/actions.tsx | 20 +- packages/notebook/src/windowing.ts | 14 + .../src/components/windowedlist.ts | 120 +++++++-- 6 files changed, 461 insertions(+), 52 deletions(-) create mode 100644 galata/test/jupyterlab/notebooks/long_outputs.ipynb diff --git a/galata/test/jupyterlab/notebook-scroll.test.ts b/galata/test/jupyterlab/notebook-scroll.test.ts index e8e3959ee187..3571ac78a6a6 100644 --- a/galata/test/jupyterlab/notebook-scroll.test.ts +++ b/galata/test/jupyterlab/notebook-scroll.test.ts @@ -5,6 +5,7 @@ import { expect, galata, test } from '@jupyterlab/galata'; import * as path from 'path'; const fileName = 'scroll.ipynb'; +const longOutputsNb = 'long_outputs.ipynb'; test.use({ mockSettings: { @@ -16,7 +17,7 @@ test.use({ } }); -test.describe('Notebook Scroll', () => { +test.describe('Notebook scroll on navigation', () => { test.beforeEach(async ({ page, tmpPath }) => { await page.contents.uploadFile( path.resolve(__dirname, `./notebooks/${fileName}`), @@ -53,6 +54,84 @@ test.describe('Notebook Scroll', () => { } }); +test.describe('Notebook scroll on execution', () => { + test.beforeEach(async ({ page, tmpPath }) => { + await page.contents.uploadFile( + path.resolve(__dirname, `./notebooks/${longOutputsNb}`), + `${tmpPath}/${longOutputsNb}` + ); + + await page.notebook.openByPath(`${tmpPath}/${longOutputsNb}`); + await page.notebook.activate(longOutputsNb); + }); + + test.afterEach(async ({ page, tmpPath }) => { + await page.contents.deleteDirectory(tmpPath); + }); + + test('should scroll when advancing if top is only marginally visible', async ({ + page + }) => { + const notebook = await page.notebook.getNotebookInPanel(); + const thirdCell = await page.notebook.getCell(2); + + const notebookBbox = await notebook.boundingBox(); + const thirdCellBBox = await thirdCell.boundingBox(); + await page.mouse.move(notebookBbox.x, notebookBbox.y); + const scrollOffset = + thirdCellBBox.y - + notebookBbox.y - + notebookBbox.height + + thirdCellBBox.height * 0.01; + await Promise.all([page.mouse.wheel(0, scrollOffset)]); + // Select second cell + await page.notebook.selectCells(1); + + const thirdCellLocator = page.locator( + '.jp-Cell[data-windowed-list-index="2"]' + ); + // The third cell should be positioned at the bottom, revealing between 0 to 2% of its content. + await expect(thirdCellLocator).toBeInViewport({ ratio: 0.0 }); + await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.02 }); + + // Run second cell + await page.notebook.runCell(1); + // After running the second cell, the third cell should be revealed, in at least 25% + await expect(thirdCellLocator).toBeInViewport({ ratio: 0.25 }); + }); + + test('should not scroll when advancing if top is non-marginally visible', async ({ + page + }) => { + const notebook = await page.notebook.getNotebookInPanel(); + const thirdCell = await page.notebook.getCell(2); + + const notebookBbox = await notebook.boundingBox(); + const thirdCellBBox = await thirdCell.boundingBox(); + await page.mouse.move(notebookBbox.x, notebookBbox.y); + const scrollOffset = + thirdCellBBox.y - + notebookBbox.y - + notebookBbox.height + + thirdCellBBox.height * 0.15; + await Promise.all([page.mouse.wheel(0, scrollOffset)]); + // Select second cell + await page.notebook.selectCells(1); + + const thirdCellLocator = page.locator( + '.jp-Cell[data-windowed-list-index="2"]' + ); + // The third cell should be positioned at the bottom, revealing between 10 to 20% of its content. + await expect(thirdCellLocator).toBeInViewport({ ratio: 0.1 }); + await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + + // Run second cell + await page.notebook.runCell(1); + // After running the second cell, the third cell should not be scrolled + await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + }); +}); + test.describe('Notebook scroll over long outputs', () => { const outputAndHeading = 'long_output_and_headings.ipynb'; test.beforeEach(async ({ page, tmpPath }) => { diff --git a/galata/test/jupyterlab/notebooks/long_outputs.ipynb b/galata/test/jupyterlab/notebooks/long_outputs.ipynb new file mode 100644 index 000000000000..9849607292f5 --- /dev/null +++ b/galata/test/jupyterlab/notebooks/long_outputs.ipynb @@ -0,0 +1,253 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "d0842a52-3276-49f3-8317-d74924526c74", + "metadata": { + "execution": { + "iopub.execute_input": "2023-10-22T18:38:54.765733Z", + "iopub.status.busy": "2023-10-22T18:38:54.765230Z", + "iopub.status.idle": "2023-10-22T18:38:54.788414Z", + "shell.execute_reply": "2023-10-22T18:38:54.786855Z", + "shell.execute_reply.started": "2023-10-22T18:38:54.765692Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "print('\\n'.join(['' for i in range(1, 50)]))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "86269b86-a753-427b-af87-fbed9582793f", + "metadata": { + "execution": { + "iopub.execute_input": "2023-10-22T18:38:54.790065Z", + "iopub.status.busy": "2023-10-22T18:38:54.789778Z", + "iopub.status.idle": "2023-10-22T18:38:54.802738Z", + "shell.execute_reply": "2023-10-22T18:38:54.801512Z", + "shell.execute_reply.started": "2023-10-22T18:38:54.790036Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 + 1" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "eb6917f6-dd57-4b5b-bb42-68848cc6044b", + "metadata": { + "execution": { + "iopub.execute_input": "2023-10-22T18:38:54.804568Z", + "iopub.status.busy": "2023-10-22T18:38:54.804144Z", + "iopub.status.idle": "2023-10-22T18:38:54.809713Z", + "shell.execute_reply": "2023-10-22T18:38:54.809011Z", + "shell.execute_reply.started": "2023-10-22T18:38:54.804538Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n", + "4\n", + "5\n", + "6\n", + "7\n", + "8\n", + "9\n", + "10\n", + "11\n", + "12\n", + "13\n", + "14\n", + "15\n", + "16\n", + "17\n", + "18\n", + "19\n", + "20\n", + "21\n", + "22\n", + "23\n", + "24\n", + "25\n", + "26\n", + "27\n", + "28\n", + "29\n", + "30\n", + "31\n", + "32\n", + "33\n", + "34\n", + "35\n", + "36\n", + "37\n", + "38\n", + "39\n", + "40\n", + "41\n", + "42\n", + "43\n", + "44\n", + "45\n", + "46\n", + "47\n", + "48\n", + "49\n", + "50\n", + "51\n", + "52\n", + "53\n", + "54\n", + "55\n", + "56\n", + "57\n", + "58\n", + "59\n", + "60\n", + "61\n", + "62\n", + "63\n", + "64\n", + "65\n", + "66\n", + "67\n", + "68\n", + "69\n", + "70\n", + "71\n", + "72\n", + "73\n", + "74\n", + "75\n", + "76\n", + "77\n", + "78\n", + "79\n", + "80\n", + "81\n", + "82\n", + "83\n", + "84\n", + "85\n", + "86\n", + "87\n", + "88\n", + "89\n", + "90\n", + "91\n", + "92\n", + "93\n", + "94\n", + "95\n", + "96\n", + "97\n", + "98\n", + "99\n", + "100\n" + ] + } + ], + "source": [ + "print('\\n'.join([f'{i}' for i in range(1, 101)]))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/galata/test/jupyterlab/windowed-notebook.test.ts b/galata/test/jupyterlab/windowed-notebook.test.ts index b27c9993753d..23638ae82adc 100644 --- a/galata/test/jupyterlab/windowed-notebook.test.ts +++ b/galata/test/jupyterlab/windowed-notebook.test.ts @@ -328,15 +328,15 @@ test('should remove all cells including hidden outputs artifacts', async ({ expect(found).toEqual(false); }); -test('should scroll past end when running and inserting a cell at the viewport bottom', async ({ +test('should scroll as little as possible on markdown rendering', async ({ page, tmpPath }) => { await page.notebook.openByPath(`${tmpPath}/${fileName}`); - const h = await page.notebook.getNotebookInPanel(); - const mdCellSelector = '.jp-MarkdownCell[data-windowed-list-index="2"]'; - const mdCell = await h!.waitForSelector(mdCellSelector); + const mdCell = await h.waitForSelector( + '.jp-MarkdownCell[data-windowed-list-index="2"]' + ); await page .locator('.jp-Notebook-ExecutionIndicator[data-status="idle"]') @@ -344,19 +344,16 @@ test('should scroll past end when running and inserting a cell at the viewport b await mdCell.click(); - await expect - .soft( - page - .getByRole('main') - .locator('.jp-RawCell[data-windowed-list-index="4"]') - ) - .not.toBeInViewport(); + const thirdCell = page.locator('.jp-CodeCell[data-windowed-list-index="3"]'); + const fourthCell = page.locator('.jp-RawCell[data-windowed-list-index="4"]'); + + await expect.soft(thirdCell).not.toBeInViewport({ ratio: 0.1 }); + await expect.soft(fourthCell).not.toBeInViewport(); await page.keyboard.press('Shift+Enter'); - await expect( - page.getByRole('main').locator('.jp-RawCell[data-windowed-list-index="4"]') - ).toBeInViewport(); + await expect(thirdCell).toBeInViewport(); + await expect(fourthCell).not.toBeInViewport({ ratio: 0.1 }); }); test('should rendered injected styles of out-of-viewport cells', async ({ diff --git a/packages/notebook/src/actions.tsx b/packages/notebook/src/actions.tsx index de5f7abd9a11..d9011e02a76c 100644 --- a/packages/notebook/src/actions.tsx +++ b/packages/notebook/src/actions.tsx @@ -613,7 +613,7 @@ export namespace NotebookActions { notebook.activeCellIndex++; } - Private.handleState(notebook, state, true); + Private.handleRunState(notebook, state, true); return promise; } @@ -670,7 +670,7 @@ export namespace NotebookActions { ); } notebook.mode = 'edit'; - Private.handleState(notebook, state, true); + Private.handleRunState(notebook, state, true); return promise; } @@ -2216,7 +2216,7 @@ namespace Private { } if (scrollIfNeeded && activeCell) { - notebook.scrollToItem(activeCellIndex, 'smart', 0.05).catch(reason => { + notebook.scrollToItem(activeCellIndex, 'auto', 0).catch(reason => { // no-op }); } @@ -2233,15 +2233,11 @@ namespace Private { if (state.wasFocused || notebook.mode === 'edit') { notebook.activate(); } - if (scroll && state.activeCellId) { - const index = notebook.widgets.findIndex( - w => w.model.id === state.activeCellId - ); - if (notebook.widgets[index]?.inputArea) { - notebook.scrollToItem(index).catch(reason => { - // no-op - }); - } + const { activeCell, activeCellIndex } = notebook; + if (scroll && activeCell) { + notebook.scrollToItem(activeCellIndex, 'smart', 0).catch(reason => { + // no-op + }); } } diff --git a/packages/notebook/src/windowing.ts b/packages/notebook/src/windowing.ts index 5729a6c37fbc..6a1f5cf2a429 100644 --- a/packages/notebook/src/windowing.ts +++ b/packages/notebook/src/windowing.ts @@ -68,6 +68,20 @@ export class NotebookViewModel extends WindowedListModel { widgetRenderer = (index: number): Widget => { return this.cells[index]; }; + + /** + * Threshold used to decide if the cell should be scrolled to in the `smart` mode. + * Defaults to scrolling when less than a full line of the cell is visible. + */ + readonly scrollDownThreshold = + NotebookViewModel.DEFAULT_CELL_MARGIN / 2 + + NotebookViewModel.DEFAULT_EDITOR_LINE_HEIGHT; + + /** + * Threshold used to decide if the cell should be scrolled to in the `smart` mode. + * Defaults to scrolling when the cell margin or more is invisible. + */ + readonly scrollUpThreshold = NotebookViewModel.DEFAULT_CELL_MARGIN / 2; } /** diff --git a/packages/ui-components/src/components/windowedlist.ts b/packages/ui-components/src/components/windowedlist.ts index 4e7ba3e2126a..2d0740d60a16 100644 --- a/packages/ui-components/src/components/windowedlist.ts +++ b/packages/ui-components/src/components/windowedlist.ts @@ -90,6 +90,32 @@ export abstract class WindowedListModel implements WindowedList.IModel { */ abstract widgetRenderer: (index: number) => Widget; + /** + * The overlap threshold used to decide whether to scroll down to an item + * below the viewport (smart mode). If the item overlap with the viewport + * is greater or equal this threshold the item is considered sufficiently + * visible and will not be scrolled to. The value is the number of pixels + * in overlap if greater than one, or otherwise a fraction of item height. + * By default the item is scrolled to if not full visible in the viewport. + */ + readonly scrollDownThreshold: number = 1; + + /** + * The underlap threshold used to decide whether to scroll up to an item + * above the viewport (smart mode). If the item part outside the viewport + * (underlap) is greater than this threshold then the item is considered + * not sufficiently visible and will be scrolled to. + * The value is the number of pixels in underlap if greater than one, or + * otherwise a fraction of the item height. + * By default the item is scrolled to if not full visible in the viewport. + */ + readonly scrollUpThreshold: number = 0; + + /** + * Top padding of the the outer window node. + */ + paddingTop: number = 0; + /** * List widget height */ @@ -266,29 +292,41 @@ export abstract class WindowedListModel implements WindowedList.IModel { /** * Get the scroll offset to display an item in the viewport. * - * By default, the list will scroll as little as possible to ensure the item is visible. You can control the alignment of the item though by specifying a second alignment parameter. Acceptable values are: + * By default, the list will scroll as little as possible to ensure the item is fully visible. You can control the alignment of the item though by specifying a second alignment parameter. Acceptable values are: * - * auto (default) - Scroll as little as possible to ensure the item is visible. (If the item is already visible, it won't scroll at all.) - * smart - If the item is already visible (including the margin), don't scroll at all. If it is less than one viewport away, scroll so that it becomes visible (including the margin). If it is more than one viewport away, scroll so that it is centered within the list. + * auto (default) - Automatically align with the top or bottom minimising the amount scrolled; if item is smaller than the viewport and fully visible, do not scroll at all. + * smart - If the item is significantly visible, don't scroll at all (regardless of whether it fits in the viewport). If it is less than one viewport away or exceeds viewport in height, scroll so that it becomes visible. If it is more than one viewport away and fits the viewport, scroll so that it is centered within the viewport. * center - Center align the item within the list. * end - Align the item to the end of the list * start - Align the item to the beginning of the list * + * An item is considered significantly visible if: + * - it overlaps with the viewport by the amount specified by `scrollDownThreshold` when below the viewport + * - it exceeds the viewport by the amount less than specified by `scrollUpThreshold` when above the viewport. + * * @param index Item index * @param align Where to align the item in the viewport - * @param margin In 'smart' mode the viewport proportion to add + * @param margin The proportion of viewport to add when aligning with the top/bottom of the list. * @returns The needed scroll offset */ getOffsetForIndexAndAlignment( index: number, align: WindowedList.ScrollToAlign = 'auto', - margin: number = 0.25 + margin: number = 0 ): number { - const boundedMargin = - align === 'smart' ? Math.min(Math.max(0.0, margin), 1.0) : 0.0; + const boundedMargin = Math.min(Math.max(0.0, margin), 1.0); const size = this._height; const itemMetadata = this._getItemMetadata(index); + const scrollDownThreshold = + this.scrollDownThreshold <= 1 + ? itemMetadata.size * this.scrollDownThreshold + : this.scrollDownThreshold; + const scrollUpThreshold = + this.scrollUpThreshold <= 1 + ? itemMetadata.size * this.scrollUpThreshold + : this.scrollUpThreshold; + // Get estimated total size after ItemMetadata is computed, // To ensure it reflects actual measurements instead of just estimates. const estimatedTotalSize = this.getEstimatedTotalSize(); @@ -301,37 +339,62 @@ export abstract class WindowedListModel implements WindowedList.IModel { 0, itemMetadata.offset - size + itemMetadata.size ); - + const currentOffset = this._scrollOffset; + + const itemTop = itemMetadata.offset; + const itemBottom = itemMetadata.offset + itemMetadata.size; + const bottomEdge = currentOffset - this.paddingTop + size; + const topEdge = currentOffset - this.paddingTop; + const crossingBottomEdge = bottomEdge > itemTop && bottomEdge < itemBottom; + const crossingTopEdge = topEdge > itemTop && topEdge < itemBottom; if (align === 'smart') { + const edgeLessThanOneViewportAway = + currentOffset >= bottomOffset - size && + currentOffset <= topOffset + size; + + const visiblePartBottom = bottomEdge - itemTop; + const hiddenPartTop = topEdge - itemTop; + if ( - this._scrollOffset >= bottomOffset - size && - this._scrollOffset <= topOffset + size + (crossingBottomEdge && visiblePartBottom >= scrollDownThreshold) || + (crossingTopEdge && hiddenPartTop < scrollUpThreshold) ) { + return this._scrollOffset; + } else if (edgeLessThanOneViewportAway) { + // Possibly less than one viewport away, scroll so that it becomes visible (including the margin) align = 'auto'; } else { - align = 'center'; + // More than one viewport away, scroll so that it is centered within the list, + // unless the widget is larger than the viewport, in which case only scroll to + // align with the top or bottom edge (automatically) + if (itemMetadata.size > size) { + align = 'auto'; + } else { + align = 'center'; + } + } + } + + if (align === 'auto') { + if (bottomEdge > itemBottom && topEdge < itemTop) { + // No need to change the position, return the current offset. + return this._scrollOffset; + } else if (crossingBottomEdge || bottomEdge <= itemBottom) { + align = 'end'; + } else { + align = 'start'; } } switch (align) { case 'start': - return topOffset; + // Align to the top edge. + return Math.max(0, topOffset - boundedMargin * size) + this.paddingTop; case 'end': - return bottomOffset; + // Align to the bottom edge. + return bottomOffset + boundedMargin * size + this.paddingTop; case 'center': return Math.round(bottomOffset + (topOffset - bottomOffset) / 2); - case 'auto': - default: - if ( - this._scrollOffset >= bottomOffset && - this._scrollOffset <= itemMetadata.offset - ) { - return this._scrollOffset; - } else if (this._scrollOffset < bottomOffset) { - return bottomOffset + boundedMargin * size; - } else { - return Math.max(0, topOffset - boundedMargin * size); - } } } @@ -842,6 +905,8 @@ export class WindowedList< this._applyNoWindowingStyles(); } this.viewModel.height = this.node.getBoundingClientRect().height; + const style = window.getComputedStyle(this.node); + this.viewModel.paddingTop = parseFloat(style.paddingTop); } /** @@ -1346,6 +1411,11 @@ export namespace WindowedList { */ height: number; + /** + * Top padding of the the outer window node. + */ + paddingTop?: number; + /** * Items list to be rendered */ From 9b10f7a0b9c7863df18ed6a2580c1084b77ef20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:52:44 +0000 Subject: [PATCH 005/147] Fix scrolling when dragging files in the file browser (#15318) * Fix scrolling on edges in file browser * Add a simple test --- packages/filebrowser/src/listing.ts | 2 ++ packages/filebrowser/test/listing.spec.ts | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/filebrowser/src/listing.ts b/packages/filebrowser/src/listing.ts index 5ab01c5fc0ad..c5a60b830970 100644 --- a/packages/filebrowser/src/listing.ts +++ b/packages/filebrowser/src/listing.ts @@ -2251,6 +2251,8 @@ export namespace DirListing { const node = document.createElement('div'); const header = document.createElement('div'); const content = document.createElement('ul'); + // Allow the node to scroll while dragging items. + content.setAttribute('data-lm-dragscroll', 'true'); content.className = CONTENT_CLASS; header.className = HEADER_CLASS; node.appendChild(header); diff --git a/packages/filebrowser/test/listing.spec.ts b/packages/filebrowser/test/listing.spec.ts index 0c6abe3f31a1..71f89ac70a40 100644 --- a/packages/filebrowser/test/listing.spec.ts +++ b/packages/filebrowser/test/listing.spec.ts @@ -91,6 +91,16 @@ describe('filebrowser/listing', () => { }); }); + describe('#defaultRenderer', () => { + it('should enable scrolling when dragging items', () => { + const options = createOptionsForConstructor(); + const dirListing = new DirListing(options); + expect( + dirListing.node.querySelector('[data-lm-dragscroll]') + ).toBeDefined(); + }); + }); + describe('#rename', () => { it('backspace during rename does not trigger goUp method', async () => { dirListing.selectNext(); From fe617e83ff73b48980b105734fd3cfbb9e4bf140 Mon Sep 17 00:00:00 2001 From: Nate Bowditch <111072326+nbowditch-einblick@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:32:55 -0500 Subject: [PATCH 006/147] Fix update button in extension manager (#15331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix update button in extension manager * Apply suggestions from code review Make action options more flexible Co-authored-by: Frédéric Collonval * Fix docs and make install options optional * Add test for update button * Update Playwright Snapshots * Revert spurious snapshot updates --------- Co-authored-by: Frédéric Collonval Co-authored-by: github-actions[bot] Co-authored-by: krassowski <5832902+krassowski@users.noreply.github.com> --- .../test/documentation/data/extensions.json | 2 +- .../documentation/extension_manager.test.ts | 22 ++++++++++ ...sions-allowed-list-documentation-linux.png | Bin 16885 -> 17569 bytes ...sions-blocked-list-documentation-linux.png | Bin 16885 -> 17569 bytes ...extensions-default-documentation-linux.png | Bin 31015 -> 31689 bytes ...extensions-enabled-documentation-linux.png | Bin 20875 -> 21555 bytes packages/extensionmanager/src/model.ts | 39 ++++++++++++++---- packages/extensionmanager/src/widget.tsx | 38 +++++++++++++---- 8 files changed, 85 insertions(+), 16 deletions(-) diff --git a/galata/test/documentation/data/extensions.json b/galata/test/documentation/data/extensions.json index 53f6b1e34349..aa632eef3111 100644 --- a/galata/test/documentation/data/extensions.json +++ b/galata/test/documentation/data/extensions.json @@ -5,7 +5,7 @@ "homepage_url": "https://github.com/jupyterlab/jupyter-renderers", "enabled": true, "core": false, - "latest_version": "3.2.0", + "latest_version": "3.2.1", "installed_version": "3.2.0", "status": "ok", "pkg_type": "prebuilt", diff --git a/galata/test/documentation/extension_manager.test.ts b/galata/test/documentation/extension_manager.test.ts index 23e058a2f52b..9a744d980d82 100644 --- a/galata/test/documentation/extension_manager.test.ts +++ b/galata/test/documentation/extension_manager.test.ts @@ -102,6 +102,28 @@ test.describe('Extension Manager', () => { await page.screenshot({ clip: { y: 31, x: 0, width: 283, height: 600 } }) ).toMatchSnapshot('extensions_search.png'); }); + + test('Update button', async ({ page }) => { + await page.goto(); + await openExtensionSidebar(page); + + const waitRequest = page.waitForRequest(request => { + if ( + request.method() !== 'POST' || + !galata.Routes.extensions.test(request.url()) + ) { + return false; + } + const data = request.postDataJSON(); + return ( + data.cmd === 'install' && + data.extension_name === '@jupyterlab/geojson-extension' && + data.extension_version === '3.2.1' + ); + }); + await page.getByRole('button', { name: 'Update to 3.2.1' }).click(); + await waitRequest; + }); }); test.describe('Filtered Extension Manager', () => { diff --git a/galata/test/documentation/extension_manager.test.ts-snapshots/extensions-allowed-list-documentation-linux.png b/galata/test/documentation/extension_manager.test.ts-snapshots/extensions-allowed-list-documentation-linux.png index ef8dbd06337312bc8313a124321820019bdfca94..d64f42b3f5d78c4461084a1b48ddf3ed538a4db6 100644 GIT binary patch literal 17569 zcmbTd1yCGc*Y2A@fZ!G&cnA;@2oT&Mgy8PM6WkpJ7$gb7-QC?C1_lZ4?l8F9;0(^~ z|0nl*&#m{=J$0(6nxUqrXZP;4_I}p-JsqT^Ac>7ZhVks#Gi+(8uPVUn?XzdkU%y29 z=bekECh+pyNk#I@v+@y&y=Tw9>PUYTRdZW7N(%rJ%_cpa;~v_&s`5ZnEU)YuV*_#G$M!s zPHmbPztfEf9y?_;I*q3CfwrBN&xOpE`OeGEJ;n#sYdmE$kXp=Pm)Rs?Q2mQ+huIK) zPB`!hQN&$;3_SuT}z6Hv{kg;$`}~6@OHEm78aJ34KL%UtE=PHn+}uBU}9qW zE#2OFv^F(0RWFZ^x8E{kcsdSq&Hm-riH?a0gFIa|w8_iKd9DXBd^4yv4#Ee;#TmhD z-VF{9n>;L7Rp>!ok7mkrmX?<6nooNO85DM{N)y=ZGkEOMh1S`e6%nfPQEh$XMPtD=gyxH!6(n3#8avk3yn&4hY1kf%LGpW?#8 zpPDm-1Oy-L?dvx9RGAX>if zU6@TTvg`i;Pv^+RO6_xh%CzlAR5yNV90B~4TpLQhk&)33DRFBx8$G>vK0Mh2wsrHw z+IFu}X17o)?S0!x{rGnl2#4xr7);P=rj*%RI6t+=FQ-fdEz=V8h+YTn{d zKC9T2#lDj5-MgMdnxxku9zY(!JPyht1ka=_EsKYsxd|2nw-b4?LV|**sHj~PH65C3 zk7sM%8(};HYeewga7v>Mn}#J~Qqo5E^KCy&l7X(msczt6>1E?14}l;LXJ`E<=6pY> zrS*DU9nMFu#WU-KiiZUTuI9o#&xbA3aa-hiQQRQm#Mvlq!lE!-3SK48A0HSQqlv7f zjep%;ZqtAIG!`h(6dcz}1s>X_Xofg>cc+yw8!2S%W5EyMnT0?eqi$^;98|QGk0*tD zQ06=AaBF2`f_UI|cF9thFuS>ILB~1JIHS8ZMO@B!k~YCVw1xC4kD~C0axq=qD5y z9>W{csfwQL^tMk-s`~or`DP2uZ=OevTuAmZm|9v|QYZ#lh6$*=D5`Wl(#~-Wuro%# z4%YVpGU0Fy?uIdq9+(swij@Kpx!H^%1BF4iCQS8Hr{Jq27?D}c;?%wZZN>9mr}H9r z^P@kPso>!SN@(UJDqp|ErJm6CNKg zZ(z-i6&NgFfQ*Ec`ik$CJ4QdB^pvEvJp(*xKX^7F!d=9@zsc8Y=YQm_4I&-m}% z;@>2Mt_O_#B0HhP^k(Mf7fGE3R7P&2TpsvK>CsE!4uf*rkb!I#IN=Mm7O*;tNr=@X zx1K%q$Vdd{LL_P9mi0=d@a%#qJG=mlitP0uDAZDUc73Fp{xnK8dK~~pnC5XFloTET z;aCh_?M%MKEjei0-Q}_s8DBm+YEiHs4GpETt4G_vn?+S$ezd-dpi*dC3o0cMlu33H z2t}tdP&&5EGlm9 z9SjzhgW@a-Yww`A`KHIUY9A_QFW!DviAgT)miubVeDEY7qUf!?uQM{sN0nFFrajHH z+SAK@Z)(fpvc24mV!c`5w`^iO>unSLjbPsbU(-IhKQ9A18RbBLwE`EBww0zNggI&H;|$gNqfbLmuGd2$@OEp#_n z!Ee=u-9rw?xBQrp8RdEz4{v?W&&re1_K0-R{nUKZ(8C%;5^Qi0E3Ec^?wA$L#KmWq zceMZV@9&1d*B;kdX4*uUj?8=UJ|KT7xe2!PsA@J#H!C`<>uCG^E{~(_JZwSuz1g6X zl?)P(#-N9mWl0C+-D%Dqrl2edIKiUQ^tbDA__sWB;nlml^1pn{_y_zOJeTw~2Z{>2 z3&cyrd$UxBX9G>(*p9uGPj`K?8h+}ctR5)e3>f13bpNW>2iQnA2hgdAE*S#A6 ztyC>P@pOD7wpk&S=U<6aQMz&~$9=F~Q`Q|i>d)A^%*BwvoY>ZA3#<9G5_l&YCln1S z$&Bd;ya~{-7|ExGKL{AMBWRBo7sNDMH8u8lPbFQ#j+9#i9^i%vBl1>hA_LIN2YaVn zo0Fp^VH6X2Q|Ky=1jOC)w-JmaMLie=ZZ~t$UorKz^Eb&CEht7;p8lPGlNT3MveuwWCl$ltC4(CxR0H!$flE-PGWCRJ=_7ZKhiof{3~Gg z0g>V-t^ju|HG28=`zw-1UoDFgWTw`AQhjo@eu+%U;P*u~U6gtww>G1p`xJDMT<{Bz z8;`=Grwq8FI?>)>dQ^s>>44c4hAQ%xfNzekKah7n+t3Q{SsqlGweqOE|gt9(BNIPb-5jE59(>z+cRlyWI4hQnqpalN# ztMSul#fPBK@;titq&zE*VRu+v4>kGq{S@2ZWOtI5X&49-^6Qa^9ltcQJ`$llc^ipf z^tRjNj|FPqbD~a;mb29Nqz#;xkT@2K_~@rTIc|09yg(yI>sqNiEzI+R=OwVSS zoU<}vEj2)V-EI?%98h#F!e=H!lsuGM8cmc-Z$E?LMG$%wwBoXbIf=`Do~sHEaoLu{ zEkM>%?|FTxw1zWtHF0n8Ll}Juub{q!4ADLWQ%qS1s5y_QOHS2wI7#Zke&*Dzr{d$?Ow_R+af2UV` z<7uP$NNV_!1z$>b!wpKlzyGYQBGT?R>b6doVX6?l6_ygl1p*z?wuiWm3AeVN5ANb6 zBrG0v<}l=mUV(s>;-jb9Bdafg;UA_~XUwWYu4==Re2rjxS<%y!teb(lmlD00 zS+4yUAzq$eb;ReiQepeKe0BVW48BHMH6uF#ZekIdU#_A31QBF;PJgcDga%mG7PcXYPV%2RbJfSXT-CSCCvrg#}D#|k^$LMuKv62?*1Q~WK<1z)ald9ZRP zRHLPynF5Ks@h0(F=;Q?3+}GMjp{fhpa_!|ohu3r$H^_kfK~8QdOwsh=B&(KQ*hJQA zAPmA|41#en02w(!WzP(&&eQ+mxy5jXz$kCNOgcOsmAO0Kaa=bdA4xsL~$$Q(5;iIa06*8Yh-eEswrsAJG0rpe0GPjC4;+e=Z|EM+gu>pO=W!%57&9wgiJ4l z;RZ#8>1X-{`I5$z?cxu`3R=yNyS)j61r7rJJ$W0w4w^1+=+_{KOKR}SOzV$+CO*D! zjntjwv5nBO1yLX2`Zyh{$J%NS4Wn#>kqgd!PCdUfyNVmjTc) zM6FD%$7ge{geO1*K_;Hug(cHl?DaHF!|pB>jjLNtWYg|zhY;C`Q9E%|dcq6!FNJ>J zgTuv0YL*mR&C89%Z`8FsU}8|!$rbf@o&iH5xZ!R@MG_t zoDAhgNg(#+*9?46kel-UmktIL#P=Fd+g{N}ixuU+7$AooJS=s_%mkNHIDtmdSuC1y z+aW0_%h58iCac;*i&r4?(`^Fhp2D~D3p*b+@ypFa>nL*+E10rsbWPXSKv3^`eKO83 z-mF4iYaF^oW|_QZ0X>pSl0rSZ*~@u~lC)270~cHho4v2ouOw{TTOgi>5Ja8{4V;yK z`<9qsz;M;Os|*iy_%qmy{GSZ z+^ON1d7-}U0S;;MzWjo75H((RcK9+j=TRKLWp>Oih5>(4O|5o7k$SMQC+#+rAm&>B z@@uL6$M&8c;=fI5#UFCKFk#>1+kSVMi=ZbYeu0IBzBN^-N9d{?+s1t7#42C1m z8@<3$KbGnb4|>pf+^ylefB8zoE$xfFfs5OG&403d)>$_C!$2_ul+C zd(o3d@~svUPxraszF%5orACd4L!Fl6rIzbG+_y*yr*fM?N=5YOfLm?pD%7Ic&nv1A zB7K8?B${)&geD1Yxt5(n(PA8J6S2miu^r|J>zo)@!|l^Pe=+al+<*62$_aB_M>#U4 zG2OL#++Dk{GBumfi$^Q!ioc7K_1JKxCC-b+;o*yU!ljnUK~?!xbaE#3H}7wnaEY>y z;%DfjG^JOogJP4=k*#XR7!ze5vorgtvBPL^*ouihP^@#Ls-Dz|%+G@gtFImg_6avg zYa6Xiu~>DCuEIfjV^}eqF9^yx8Jje0g2IY;6arWOzCh%)y84NVxd!xA4swhK3JJ`L zOhhJnLdy3-Mp_Xi2ZUbtoiL4HR)KHonpG|-GOkT#con!~g_JWUbiOSEhU-(cOw!)J zph&YZp_YWLu%+d`P#4YVcA;6>33d6!7VxYeJ=hAy*@Wpe+Hv1#C3||n>y6G(pFUyq zyS&`w*~5wLLt(UN`-&;t(4(Rv6$@@GTPJE*l!CSL5f1hhv+rDXBKh-CFP)UEeCq16 z2e*)B&JH)(gg9KB?4gAWgsZ!04qNhgqtXm@klUWX`3esX(S&Hed#6YXi(&%GCPMr# z8;`5eNJ$Z^Dz7P3H@kpRWhyK^XpKeDFJ`*kax))is0_Wn4Mg(1oZTyp*CBmNn^=$d`{4Y=% zYtC*^k|74|Jjm8cP(Yxkvy}9K+sD#cpVk#;#jp9_*w@od(%Jl9Sh5&c#Vqn4wsPge z86r&Xug5RsmFwsp+r)2HQsS=A5<*mmL){@Z4wr37R>74mW1zbQLzL{LABpoJ`{+le97eq5cu9|$A}+$ zvJZ*;ud&!{Z?GtSTD!ulNV`cRIP=Ijn7wA=fi89q`}Q; zPP}`hvS9=;%EPTUn{LggO$(;N@NCXqB)Gh_-15vDLQ(CL0CrK4z(nqmErI8-G`>_W zIL?(Lo}=Kho1P5^HE60*)1E#}nb*xDZm-|O>hW;+Uyq}36f6gh)4YV-IakS&IF`z9 zcoX?iSWQ9{N?1oTPNQp(4*Bl-;K0H!Al-=iTzk~ z^r;9dmXA91>lR%rX?ufDPipy)bA~zW>+Rg`_(fcBOpumyn9~Yz>{oW{f2;H#POCk8 ze4~Rz!aPp|(Vu>Sqeqkwxk=JD|91-Arv;K4eZnK1v3g|1Z0cWgCZ+g;yF$O@u_!g5 z=?|Su9|lUw=GScCwz2d>iRzSIJ||~weuJ&V>lIK;4%tst9XT`8u#0vi0P*4>@pj>? zJY%WX)hjQ_jCju3s0Bs<3z`?nM>@jtx5NBXa1O=%5A)ol%dc$-5mATtie%b1m;ndL zh0HeFv*ZJ9`o~R&-_zk;h3|#ug)Efc6C+cc(j4ydQM*@qMm7yNB`PQf|+3cuxy` z9*GX>Sz>{AkoIsXP0~B%g$;WJ``{(`7($%VVs*Sev?U%ewh}&`e9JeJ*-viXVd0%w zFbNaL<05|D+AdX{NA_B1KG86r%LD<>#NIa`mkpUcT#m7gT5c!JEJxzs(mapQUS*l& zWkeI@v-nZS=V-2eAbh;5&LB8sLtoc%&B9L$pZ}5K*qJkRRf|o2&LckPo++xdXrYLa zQ*REVTq|cOcDC%MoQUc^&3w`IC7z`sNHO&_Jc~phCA>vbYlqJ){d@3WfjmhUilG(E z0$T~Q`1PlD;_1MMd37(oHoEcN43Xa`%=%x*vmd$6r0F}cb?yav&3+p6|GGup;&$j2 zI_H^gef@;AVtxFl=229h7AfM3O32ip;4Aoj2x{lYY_-s|ZIXPQRJ3|08cz5|r(SB< z5YK_JX5AY{j@;r5dp-=1U_%EIh-F!ijF93<@^U!yL`Jm8x^%8-I8cD4>^)=8?byhw z!XpLQy14kNYF4lNF?_{&eU`!p~SD#vA4K@RIxXwg-`MGqi)XE zF)Zb~Hh@sS(FoCt{)r5-q<@to13gB^z!&m0&$2!%uuivsq$HPiT^1bWY9Vv?3hPL# z;C$y9jsAPAaES}9`cq)`H4cy@gX+0v5>9pxJ=DMeKQ*&svkkL^wRZPmC6lw87dcE9 zgBf@2ziwdNe{b+gqn=H<5><(osQ+Gw-&`jI3j-1H#_!Sxe|^Zhca92!lthuvsLzgl z8U2>=9M4S81?>ZQwyu7&sNoCT?)Ww_UkxfkMH=bPUOhqrf76I=QDc*qEY#IVV7%-c ziBYtjUtdXc2>?5x*~ZxqsSjEp}UweU9n2!W(Gj*(f( zufy($Y$%~B7Oos$I@)#*Q(nUZ9lPFp-0n9M7X81A>H|iEl6dT(TjS-}*|h1Eglu@7 ztAlCz5J`|Ftd}IK*uFp{c#K+gUBKwWqi9cI^@pl5D*XihzD?i;1eGLxff)?2YP55c zA<&b9t&^HRzzW97@n8k}A1z?4N9O`SkqRi8DytSYqBD%o7UbOd0e{3R&fYH(4Azwf zDPlS4kV4GSgSJpHG5}3di0M@B^XJ9GHke+5trKGrnYd}f)hxiKnwSG8Tm1tr{RK#? zd3dqG8_e$K5DM;_*cvdA5sZgL)3qUhfRY&}D5OQseLwFbkR_s&zx( zvX+wzayBTlrPJyap|-GItn3P7di!I-E>6RHVQQp5O28rZgk1#R(Qh3m#L@A&-z39< zu9QM*HuVf&8PITXdJgOi6(GHL{C~)@*RMBOnwnqExFsx(n&97j9{MhU@*h(~Dd_#XAd@gC%q2mSui?$J)^&95K5i&I> zOH(&l^O*skFCCSAfHK<`2pjwRpRYtTxYU%}R|Ip2zf>jV)l*{#r-v+t_|%`kNom&9 z_;lm`@XH2nO@|BUPtFC(D!n2h2%U0OFn{vXfu$PSlMW2UeM%fInPr?Ue2f1IuB&Xc z`$1vh9h^Hyy@Q;6@jh)LAq}8wNd!)MUxU z$c-=5%)#|F4O)5juDrdynBVjDqr`gc$G2)a(I9fxpF(U#LdMTmVkK*DNwVd z_WR%bjKqkbR*yQ6fKk49ARCRYqv1ZGbTb*5w%1rr&%|5c7xe31`ARnathhz#&2K0L zqGf>plF$SXcGNARv+7;lf+pWxLD%Ce*Z=o#ZST_S;J!xH<)vMb6Zk-j18FQMfjGuV zChdp8!7r(Db9cFBeEA)RjGv$aEM3RTt{8j}(Nws@Q8(1cXOIc$C4s6PeN`Z54yb|T z!pb53Bi3|4b-@q>`6LgeWUdK&-0+mA8(Q+sd}=Cjc}f4CgE?rpoG}cG=vz7obipA& zPd6|Wt2k0K1T%<3Ohq=7>Ocl+R6!g+k)ZOpj0Eyz4iaZ>)={=ngf4dJS%4T5V;0gV z^L+bOek=tF1V}_kuktRdp=`+n5Y5^;Ke-v^kW`Xf9J~g>xVLsT3wzJ6i!Gy+p*g2? zBk3Kv<+$pQ!AUQ3&_#nX6o?RF`3gqnpuC9o?rg3Y;vgTKi0}WrL zS45Jz{Fu>Ask?z}%{|PP;HI03AbLbtsLu>LvGJ36X}aXEg>PoI(SLbaz!jZlwRvIyV!JNm7QsS&dIvc_d2BKfnDq2YV-SS9_O%|0oPwUPfo&l z8_!*fW=IUfgGlV{t7X3QFJd=8d3`d9!xxhDX(agHs-%PmPW_iw{okOy{}Hnf!CdY< zK^{$CkmzC#75Da`-?O@*oD_2m*k#-q#_IS21pS1SW79W+^dHmND^hlTqp)aK3z&BT z3hhkQX@UiiQUKDZ~lqAVy4GW(jyAf5!xk(39k&AaE_o zTDC_LdjA>>u|%T%kfxp3QgZRdQ+-85yPTHuM$lN>$eS?`CRhiF;n?Dv6rh7R@meO| z7d>8ByL(uj@7MW~^6d=ca`yXnufl~uzXTPf*!wi{q7icae*`NP@+j>>YkRzI*UEn% zp2C0ob?C_xBf-u-bApK^Y}i^`B-dEj#WcPv=p#P)K?VnJ0VVMgP7xybOPwlQS+O$^ zOJHFFeQSqRaFLPxMtzx_KJT`$b>6HsBzc9=wG5FsI%W?daGVRUFAi+f zQ_nU?G5uN1dS=eyt{c}9%0Y56EA=6-BWKW=cK_iIaYVEQtRU}Kpj@2O^2u(@5EX|$ zEUsV9XtUHFjjQ&L8l!P%?1p|5y+*!R&Fdt-C64!Y8My(&D__Gictq&(>aa=xR2V@H=6(?g_vB^3%vb;rWX`V~LFp%7>pDuU_bZ{{`ro%|cZSduMsa!)JHwec9!Cf7Fe!R|L4j#4_ShW6ZA-^;Y%Yoqrx1Y<^d) zc41}0P4h}YLJFdM3=F#}2op=Mo+9?Svb-pV{xQxs_N(8CQG1cQgGh!RQm?xrYnojy z0ZHNEy~TE_$_`g>@0&D)rM;Nq{2CZK)Ms1>i0Zj7Z10K4l{F5%m0Gz{~TlEvHlpYf8jX&aRF*IX(;jU@%(?E zZILM8ZoHkO#OYk$)^d_Tdl9Sm0k$gaBO;=x$a*OCG#0z_RMX4xxVGG)^c{1z^D=jr8%f>7*Qs6EJD#_VxfJB6=HEF+6s29h?m%*_<}1GYCFsSDL`a_y^hpy`8+GGP>(Zi|+$Z z8$h=};k%l1SMl7{#5}LVY4n5~hlXVE_;|0uTXHgSFFac>D-3dA0}2u% za$bqwlKf?$ZFk=-uM}jJ^eguKvZiw1Ss_UAwFYmWMn+>YG6O0iyKYO=Taau$tvl_u zw^hvGdb)olytQ}rdalHm^QvdR7ondX3nnJVPtif&%ZA!TmZt0Jc^(;X^lwJ6xYudr zctywlk(jujozxm1cM-`*YY!Gby$y!GUj!PFPgf5`PmlHsor4&N1jK8HfIXr|qFSR_ zp{RZzzD1r}vX%-Df9W=^9?qJ(pzctz>L$ts_8AM`@%^x(2dm zi@_#$d&@uz=2pO<@20(!lYPe?VAvLH8-1-5%Pac29+u{9dly{B6o{rCwu6DzV!71M zNmP3J5ca=ZuoFlGZF5ZSgQfF?G2RWS>q8y=O5!*G9&+Yb^+gdl=Qrimcfs~lGQ6CgL(~F z!Hyj*uTb%boSvM0_JuIdGxXKp-q6`t#fqq8OpnYqSjxv-v2trF$jCB_Z&ZbJ2-#_J z96X(UW)yefctZHx$?{u9W^6LaZxLj$E4(`ZTKuKMk=rW6TqSsq!h`RQCl-AQdz?8Uc2Vkpn8fZCOXwuT;7kh zvQ6N^ksnx9bmzC^tg_A}-5XjntnB~Q$gr$F7L-ELw)+g^@P`;uB9W@yrJ3wD!=aii zj)I*MhxJAL&mS&0uM4K1uV_R^n%9wwdjEYpJqoYsMyri`ngpBClsNBqRfD#tZiAr_ z7^KMGg`)XzT4##>LzEl_=+|7w`%lw~GvgN3BxIo#wx(TN@NjJjrSr)4!@+f4JK^w^6XMc zwT)M>NXpa zbLZJ|R?DrE9w~FD4Ccf52rgrLQa#-@=bI7VQdRiix{sqeyCrpyISzfAIb15@=&2t6 z@fRwa5*Y( zQ$*nBgs>C3%zGDcw%@NQLtozfFRlrP7^C$^CZ`4!)MT~OP>9n<{;|!EXmROmXZ?u< z2mGor;-o;gSEVX!AAvsc;!}+b_j3#I2|4)LRnvZ0-YRjqJpdi@ z>=NduC{+@2Ri9o=K3>dC;w?>IZcSeJ^%?Ru31YsLSrvflOzY+QXa*JGgrDa8+){S| zf~N~kWzt6K2zSwK)|e|@S)o%I{}tEKC(T2CsYcC@|ARoD#N%@&xFiw*(=n5*DE{~r6NM$ zl!l77LBokdoP`tRmZKCapfc!h2ep5Rj_d9!nr+cJaM=t-dAN4G;k0F#hd4K@LK;Zo z{#v1Bn8KV~>5_W-;y(%r)ILca|NSn0sl$Afae-C&s8CY$YeZgVJ7&P%(tKq;%ZrY8 zK_oS!+3E0<;(&09?_R^^sldh{L^RV#hAWecb%CSa&M8ztNUi#J3Y9N)3wzk|Ij~KN z`Kk8X`EW0a)DEoIhB7fJyv&wz8Kx9>ik_XDyO*;-ZZE zQJo(>;DNbDd;SnmCl4_5%7lMMlF{I4-FXr)L|2bAPp$cEx|8o6hKeiK)^-Mm)kU-M zq(EN{0jQRQSXJ77-{+XRXYwFrZgRoChwC)x+O)SHN1dnC>j&IyI36)ogl_$mX?lU9 zy#uekEFZMqjwHahD4Q~&|6W$$ms_PE$OUOzp1PIsnnX(z@)H2nILv2@8{iy)>$0>2 zJ-l3PC4cl6BCsR=o<5b;ua#}rb9CNmp&@uuTwb3z*R7Qbj(r_<8%|qEtsQh&57#Sd zFPANAxeJ*`0h&I#&Fw16m=A6qDQRjce6xozBWwhOzjVb>LBcGJbRmM+r!8k^b2Y>u z(d&MMSqFbN<0>pX9!pKl%R#h+;(%J%f~sG%SCv!&Hz}~7!`#+dfI|maKr_~BTFAaO zk5FCpb=}sbp(HKeXNtq>{ z!ctSo{%ZQc6i8hjq%OcXiY|!0vxvr|2%hQiU660J)J3L)Dx;qpQ;dDJbFs|O%!ybkj4HPxc z8R7R;{36~{oRJ2Q#i?f7up*;}{DSHUhF4ggfJ20wXBu|qJOda`Gd7eNLBHE}sq{&e zL~ovMyj9hPj!8EAk(r1Mx1|V4l#5U zH$K;)8^CStjC=l@WfI_g9e6nuRH$X34(AnNK^Ja5cJ^8b3IwMi-~`T(84;#`B&JXU z)J1BVx|UYxh^K`-=d?HVZYiC*0)5Qm@ImG;UzW8FdqPiH2d#Va%1(H&=bj8*xBX9& z1LBFQAEEAzW<%4c&U7=G?`qQSpSFHYJ+7Cdqai=J>a%p`m{wC+cpFL;;}Jzyya8ue z{d&Pj<{&c7@%GoDT4G-7))e;5ph^`A$Z{|_QwF{C8WAw5;EJuM0z;q%topa?u)Nf2 z66E02Ejs#jVeL^~-8$WgIcmfCr}e{QZKJQt*YIwLz|l`%2T)2wlJ)Yp7P^Kc7Qs*+ zd)Q9?Mr%VVV~7CH)XsaB@AlL|MdZB?Pj`ukOEa`y2T&1ng=#jYDu~mC$hmm2kRS_% z&~Xig@}AoBJ!~snI-ai&ABQd;RqG?FC*zxV&i640@;mx>1&GO&ZxhrOdYL z8g8H%Wt=6jwVz{ryaIfZ)13m*tAM2G(8I$w{Hx7pT?P0aYs=oQFXphsdbWK)kNd1X&`U&}*ax;F0WAe) ztN4AeF=1bGL*aoY>t?1rKIAMGbs*98scv65==6HB!?PYUiePzD5S|Y;GIeoyAWSm1 zZ9-$wH*qtWcL3}92AYyRZ;EDoX2stAR{~aKgbN{7*)H5&0F$J^?sEn)%jyJLoZOot zvfu81d-9cSRfZaG< zCWnxiki)tduUc#p_J|bm`iM*_<`gjz_gU-Ejz}o*yGK&y)d62)|03ClrU*kD->ZFFZ>SZ1eJA^oi7m zKGBITxB?F(cklcRh?DFO084euOgh7JwFm!OIt*6S=%q9A5@W`|9cp1i!JimCNU zpo^ZW~{w>%?sek+1WtV(&LIfZytO0<1 zl?D#OO{c#@sa`T`l?@Hp`Rq}DTg1j@a)jwrvQ7}{2`pVA9QGn=OMlxQn{zb52)mx2 z1Op*C4S);m+C=loZ1||9D2Z<^DyHjU1o3rj9c*xl_compDiZ%ed1Ob@ppyVVw}%1!M!R@5vKW&9n`}Q|Symv6 z6B}K|!tgC${YIw*`G>F&8e75vBHD}ZIcCcL1W6J#We9hQR_aVJ7j-m zJwop9+i#JXw9W@;P7RA(x6^OYcvtc_hn$=ghY=EkuBGQ`Th@&Fp2)8_n?GsjIh+^lsHBau z*4s$NffjZb>yDTd^5smUoxic(JkZIDOLy9KaE~3gZ`1yUQGNX-4jmk&FIa^SGJ&v3 zb zvuFrl2Mr9WYH^BypY;{8GQ+E48MrY!8f4aXQ8eZI;n(rA_pqa_OoxnES2TP~`Uh?0 zq}35pG|=n1IoW3D+1OR{-7M9nl`R7{r3PQNc!3gC4UZ)#Pw{Uc*3p{n=4&DxIiq)~ zkc}ma&!J_%VuQn9q&+|I{PX3qB28|44tjn(rRdBU3P6^B!Xha&W^CG5`{KP>BfmFv zm1TjF{!P07;LnW&_^XAq#}$$^FE`si&^lL1(wwg%<#z3e7_-NGbAIDo9wCM}%`W^? z8{;o2w@U+mORivoILf3Lz z*SUA?l*3}|wb*;}h1$>T3$_CX3xxl$=-|xL

8Z;ETN*507DJdwXy_I+T`!3}Ny8FP)Hr%|8zs)%H#J#o@pHkN2DhG_nbcDS=A=x*Cq) z+5ujj2+C>!_4?_~LEie^d$;#51V|}L9A+VkM*nyOEnvWP;)2{;f|4)1-w1&RlgAT0 zs#S;y6E;a+Lubfi-gI@k$p1rp%_W0yzOy>U=*X@OE=_VbvfY5r5sRu%RVRZrUdVeq=2^(E*wUtTT`aOO( zRCo}sm-yvbCnVZo9^0}8e?uChpX>RRxVIcLvNk9FyKtFjw22j>`mA~@6Y%haA`I&+ zU<`iXhb!Y8jpw4$IkrIs0e__bHlV<-vIFTCd%9vBEdD_!0Bt3LPYKN{c&(PS%;x;vk^1vEeCsd(1? z<~4IJ;B_K#5Sdv(`MhgQ^xeEm?+qcc)+zkkUi`OOuAmLU*`gP_U_WTfdqW0?l?N66)$x_Cx`Rs%n^r`@_->J`BT{497MK{^w?&r;9wa+eew=5{Z%3a(JJlgxWN|^=Z zGPV~ofOl%%5P*O~C#)BJp*>!fUtF0XwN&qiR$i4=l-Dznfp)}PDObaFCBDyjMm2JW z-PK~v*98bnAbA;5wo4g6MgGkIZE45rAS29r|F`a-Xl;)HC@GPyP;e3JY=g1g$*0bnS~gQ7{UgT!9dJz*#%2v1HQ^fnO`5gTbw}2fWLWF0S8b7 zePSN&_~U?h7)>dVe08kw`>(hx9d9eWJCCOhMVgmM8s$8t&y%QUgxd-FXFz26#`F+hyIf`QD-Eg#=0aO65fLV52E!(T$qu# zkHMFMwJ$$6ypH=Lr;t>KcOy$DgpJd(Jb3kf->o-4SKetkh4*#j7S^Ek1x^nnR7+W* z8Ce2k4jMz{3KHH~X{W`Q@ipy1$OWxMr;h&c*udF}t3sDDW1*`Yh?#O^o8Y$ItX&5c z;ZB(O8GSRxL^5qTU}r>zRBGnlS>lNvRaObUd;DfaVqqM4x^+(g6X7>22}tm8NoTiw zNpc6!usV8Lx>V0yy4Nc?&UtGi?)HyI1>tDM3_qcC8yD-g_qxOm9o^&h_+0Q9b{96& zdOmpRBshyi)TW?ZMvP&Z(!9R(^sBS7UQzZstyJXdk!-hrK~LAKkmp(1zz)u)CpN z$~59yw;)a{b7uFXmeqgEVC-`Ftywv5BZ6Zj;IH3IsLTeO72ST~4zOYf5@=LLeU-mD zLW9S5Cb|2ypDETtQ(V5t;`iSrFdV5dNuJ$ZGa9^#J2&3v&Sars z`(piN9SFI61^>?>uTB_N>IEes7!P)B`%CfuA*j#s1SwvzNvOU7YlXgxQL!(`9>BEf zHlLTMKIejS3{YTohm(Lz-mhZI!ZIb-Y@Hyk(U1h&ptuBYZ2&aRA&by-UR$xt6SbiD zK>@|SwUpZ~TLj`_Lcql0}s?tEH-&X)n_q-M3+-2id`6S4x^Zpsp z&%v+lb13jF`XOWz{fN@0Vt}4jGqb$+=`B1pMS|lEk*+nrg!CIw|BTsxbk!o-DdG1P zxNP@$R^qydqp(ujv$U*q=;qLy=8MVx#^|`U%4E!e+UZ zdL}uQ{{g$+-rQr_`m1~u$&s9vT&mpq2eD2Jkek%SDFd4Ke$ywd^`Z8(<(B^^=KvW0 z3dX`c7*(C%uGAY|!J^5QjeJgb+_hbGPctDabHXDn?s^)H)-ydM%;rxTcWu)NcN$iC zUx0B8%V@OkVQwq!HMkmgZPU!V(}`(G-i@lWzC%8kk%N-6?!{`{wM{eJ4^=m)U@ZDN zfJ^Vnm|%ZyA$7oFqsCp^w8Wi$DjiVq-2Lg<>s^=0^F;CjkIy@LLzgXAuM<-$-}rhD zjk~t#BxahEcFBVmF&?9{xgV};l3NmeX<43g=E>lf7~)?2DsQd9)Bu@!HJ>((yEbhz a*8V>)A>7zH=dsfO0000x8jEY literal 16885 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

<&A{znt?@!m+NK>L%)>Vu5xcFNQVL` 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

!Elx<#@$s8+uu&WKplQHou};D#PM^?u&N!) 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 a*8V>)A>7zH=dsfO0000x8jEY literal 16885 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

<&A{znt?@!m+NK>L%)>Vu5xcFNQVL` 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

!Elx<#@$s8+uu&WKplQHou};D#PM^?u&N!) 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 literal 31015 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 sk7D(Zmi7({t<8 literal 20875 zcmZ^~by!qk`!$Nv-6$X}Fm$IhLrM+Z-7PIh=gy9;04K)Q^ED9_nBqUrVMX)9k(lbinm5lKmc!y9}%LVxJ z%u`cA2B~)J)d3O`L{14Tt?jpToEvSVtJm@r{M6Iwd$QsHyuPNwph_KCdB3b(;VPCe z9;VW45JoIUOw1)KEiJ90+f=}5N6*_+s(x@0OICUPj|C#^{nNWH>{jMIjt-LahPg)S zyuh(VyZk`YIlH>%>&3gAC%uurK3?zhr=YgcFsBuBi%h;gaQzo<2s^t92{tzI!2c{p zW`@6d?#al=v?!ruFAR0#5D)OW4vmhY%J2q#f#wSLcaIy>!Sk}udyw%vZsAn}0|N^p zyu9aQZ%)naWMrP(czSsBmDbd7OdTH|Z&gQPzvy~;ybHKGDs=X;ABZJ2tcyg;9;>XY zD-(a?b5tOXi;GLYoX)049e4>jy8kKpNGEP$+*(nAnf)`7O7<2pZp3 zsI9G?*4-^xjaw?~_e#ugwi8Hplp_Bl4B-~02EX8yHup@#YR!}WSncbEby~g;&oM+icjH6Bg`Lsw+}69OOt+3(*ISyJ(ug95$MYqh z()CP3(UHZ@Sj(d>+bIQ|_1nEzXwj*8VH?3;%9@%;IA1{f`@u{3pFTyY-Y^!|nRN&4 zmeSS;z=_ifv4F?1ZHPqb5F2Yl@2x?y4Uth!vlaYgu$Dw`&>i$}cJ@VhX?|Y8gxw*I z^H@Q819u{baxF<&O-();g^qDbPEL*&=ALHU>$@HpJxt4SB1fR3rx%U#RYRJjGNFxR z0Ol>Vz!WAcO_IYgg`_ld|9dFaut=hbq=7pf4Cnp{My)m7P5WFf0w&2aEz!g@N~4M# zg%p&C$b{I}z+kZY`agH4-5FFAsUYJRe=~P5a8+&A$qZ!agprp4r=1F$`LwazshIxZ z!uLaHz?xxjKMH8s2r#*TM-dKq-!!payE+-FHPg4ad- za`+I#dY)uZF&`gaFA|X^tm_ueRy>Ja+0Igc=el?Q_ktH@zNqafO_Cm-{8269@5klB zB3)hGer|eM?Qr{TGYG}Tfei+me=K2OAQw&B$L7}ChpkPg0 zT3ociKT9Pba|u75Tmiw~tNG$s2mIXH-cz?}RlbBaxPRFoufJ7?r3Codmc z=$>iU*xEY(8f!X}|EQ0E`4H@|ug0hbIM^CA~>AnugHC9#88W0R#9acObqj%mk5r zdPh7vJ8Q#F9g1!R)7LMJ2VtmL02`k{WMoXSk&#eK3kw*q_>`$Mqf7S5fr~F5xP+JkS1GJWLDNiAi}~WwD@u)(shDk%E$vSp*vjwanZBuWru10x0hv<8ecP zjgbV(3{I1!?%?3JZ?PYFA+08?tgOYS^mdsxnmRf*uCAPDv(7Fq_%sq?dok@k#}qiL z>+9=7Lqm&;BCM?N5w31);2wk!gU&B5mRsFuq#Rvc1@%TgYm5rQJUl$Y!oq-OSrzw! zD^J9;w4-BXauU*^o&&Yy;a)%%>rraIn5ocOU24S|E}ib#1$VuX>LyUsG_b; zVRNtrfPSOhI1xliNl8FJU@-h)!P+Q?>UlQ`dSrLQ!@DsowINeL#*KOn$CazX}4>EL2-{2GBML?Iw0AfV+u-q}GtTXvcabyItFhlfWSYHXmS1Wg0|*|H1{4mP+}@w5(uX4$x6onRP~l9DzzHCbC*PrMZE zOBDgn988yKX=zEPc-a9D-N#3esJ6CNQ!+7bi|b`_*E2DCdKor|eP;jN%`;}lx8E;S zX4pRgyj-ZRMjOg`iD52j)m~vBM^gQ2?6s&U1=VY?|BYN$R@O$?)yZmH(LToJK9rbw zqAcuts5*MUAQb0JJA=y%_!q!=mezqsVklm{0tb10pE!`=R(cPx7^p`{QoE4@{>Jyj z*wl3G>UbH5DHCJk4NoLwD%hmwW%WiiJp;o)k?0W$hpDNlx3@P2b_yL24@90M;i)Hk za$n@H5L0YnmgfV2w`CO4EvbVHhkqCyx@uTZsQR_+StZ^!=}NY|dTlLeT-Bh%kB9e_h(`oo;8`#F!e1cs znJDe-6c?3LESv>5sk!J+ueG_!~{W`5f2(q)xqM z8_eXhi;9k3jMu*;EFnK9S4^AhQOAe)7x@1fcwzA^X2&$(Sb&+iEP+y}*7)oBVO{r+ zv7M0&t-O-A8Eb%U1CnXh=`V0Kjot$-2kyh>%GsHF@w@i4@-JVw?>RU*=hV||Tf))t z>7SLM{BkYt?(&r9(5p4J;Io`m(6+Yz-5=8j)siFWV}&ahqDS^{@$mc!MWjE_zz-j+A3$)ths$YoP1x1x`V*jch1k|kPF2yMQDoCpdoIH)h&;Ia;cB_CkgnbU0tvgu|_*USsLbwDu( z&#={XXquYhy-t>|u?7Q7p4>Ax02LifUzHf#=AST-YRPb`ZBp%kq9^h4VF~pYz zUof@FIE@tp)bhmsrPN1_<74@f-3<*5${ba?HGOnmzl$HrWux)v#=}st3CNWp(zn}5 zlBH9Z1SOdq;^I^eesABr`8F}3;nnmR^vQvTmrmlSLLm=$o(olxk*F>~=R?evZO`lJ zr?`DnL?k6?0;+3jO2E=$Vq#woHa2KwQK^3rz^oT(u_B*~i}nZo%annzP>(3c%CE4q zfc>BrKb=b+iqC8O@{103(nD-Y_UWq}$+(QAMW!;$uaq5ogoJpYN(9jE&mhxxgMGXM zV9Z(|^h&C>n(@;30BqmylKqD$Y&eMp#Kb-%MB3nCs640<=olIr0$H%T7M6j&ZEmu~ z_ZbnmmBnVze~Od;#S8x%N&H)QMDG9sdN&&uu~6}Jyafn)9N9C$!otF-$hQmqV>dT` z{v+`C*Rg9-UVqPRQr7CN(5d`4BVT-Zf3hM{Oi4lUiPNOryf^G$o$`2l2weCZnxLD_ z0RD)z^RmMcgwv`w7k6h zb`2BLnaHp)wmh2JKjuV(!29w3{gH8p-#aO(p0V6F0GS;GJhz8DM@ANMUI=mK>bkKk zFzmY7!WK89=FTYxiXhF<-rnA+%Gz`2YSS6z-=pzg ztfuw#$tc@LmGQ!;wc~)qIStg`rq}^QqhWXAv$BXHcV)}*&%iMwKsmK#m1aSI8R5`T z05=WZWBpykUqZE}Vy7<=kSSKUKjLeA`A_Nq=j9d`e6h2h6b1zp2mq3L$Dzliwdp zQ54vOgF%yhN^`S-^Xl+$?5h}E*pK^jq(ro}v~kHN<3qK~q%e)-3K3olH@ERQk!*P; zpo+ci4&-5ghH3Q3gLB0LK7~oOp(fipJKqC!jy2*M9~ zC?Pf;9=u6;<6)B*U0o7w?9M>pV?I4PI@)8OoPXBvWw7w0_-3RpZ-?isV{V z51sX&9959~PxksRnf*T(JFWx+eZ0QE6K~4b#H*`4!I>akU8dM{ZzduEe*S)NeS;b< zWNd6qUS1vuPviGVjDtUbH+dn|)vpls4d;U>Y;42>62KQu5JhMW=Y3)wywAfF+R<;; zhlatb%5~mXU*CLNSX`7H=_@MjxDQxYddkpC<=duQ(9pNjnei6GF1 zH1G)z9bJK*m4Vj2V}M%s${A7aceGtC$*FF1X1ZI}GJq>mGlg@iPukTc*vizn<8y!6 zIY8Az>fc_UgqxzkRQkDmX<_y1lkV{6)v4S&UcM~xc`A>f$FS`%?5hi)HuUL;s|B4_%^Drs1e(bGRruD7K!Si|*Zb-@c>JiAFbia2_;Bc&n#R=Ip_y zXHr%0;3og|Lf3X7K(}W?dg)}a{im+Y0X_V0^KFdbjfJhb2ExhquOdVI{a34kvT~|} zbq-bW`K6>BR|za<`i>Q?IR|S!89RfopT6_7>@)`&tl}nrv77y5d3}tfoKeb;2`dOT zO=+2{weFN`0RN%pyMBkOtP6V$aD=v~xpWptBxvJjKkHh2|Nu#1#Irk?tc$Q7sY z<>!dKF>)aHWw6=D_<}>jdD^7_uTRNA>+J+rvHd{;)PqIo?>VMYUfn-w8m&PU(>ZMKgMt zH4NoRL`4b+eUdK2Pi^jJBq-v-=r`3T3}Cr-S+|b}f!RFYn%IdX(dr|Sg8kdhcHVqv z7Cekfc~ydQ1K|uFh{_B&M$~9sEc1=X`hwufteg4n$26t|&MnS!x-%koErmmW0xbCZ zzQb)ZTsLG?L7HU}2O?C7{9As?m`BgRwXRsBq_M;q#K-x*Acw!by~?rpg}IT(mFQ@s zCOrqC*StXw{#bZ5U)ZI*ega8-Yv35z#JTVFTO{fbtlEv_21+W*HVhOK?yZZO)-U(l z=ll!rY;IqcW5c8pYsKn5@6v_8%&@b8xpF}Q$wee*T5+s*a@{lqW^vy^ZCh)X?g5>lU9FkCrihx!~%>L*CaIE@N#`_V)$2vKJ%zXvlk+TW+gURZJ>;)J7kFyEGb~Mqp0^X~Gs;ymB#7T-izM^) zyb#|6&|bnPFA^vXrIEOh3toe#ux16tN1tZx(OUP$FMTX@iDN0XC+^0Ru6g$~Ho)B> zY+jyzBWbUv=U93y$~ViGKkJmp>PR@>%VjY=J~tLzCBDZPIU(?pbmCqAKEF1Dan8D!M)+F(rOxB^&pHJ=Uly zU2Rqnp5NhHSh0Gx2T7fIqpt1YwHn-!LQl1rAN5Z#aty$|3{<)xZ_a!zP>KbbWD@%<$KGv&ah9g5i;LT`5CCeZ%O{^Y3|huCLQy?Fn?VqZT%YCnHNNw7ZZ1x!TO)t zdn;yEDpZuUU33rmsyu_f@guw&!#2zrYvv1NLF;RcKpkSxZIpF;Q+aeYv1EZL?qZf4 zoy+NnE=mf@L1YVQx|0sG3tR%wMtj2qwiP-SY8 z|8zL7#k)nxB%j91I=1fEm|740d5b%gw`K=#0vjuk4*TihtMgsdV03s63dEs9Vs6qw^xqeR7zn_A>l;(oK(O69j@Y!VY_Frc~DhQqQz}V^W^9phT=5O z%R@+rTG~HzNUxl-28#oMZWKcK7qMzE@j6{9JivKy@~FL;NGne>xeVKy4j>or!U5v|IwEemx5e?h32on}^ zvkdKhX4bg@s*O;#-|MaxI#h&3Ri_bcYG^OTh#t~U6k6UPDoW>bpldgS*m(GP9{MX= z%@)BvEsv4v(QlQrA6$||B)ofrf&!8++8!hAog8#fk9N7-%DyKO?iGx@&RyqVgwq^q z^HI+^>x_!DuN7sHfns1H0ifj4s%at|`qI?5Oh#`iMOqVT@jR9; zmW!X6np!dp7P7z%p|Bk~L#Y3N*Ua&^TWuoNOZ)6vRBql`t?hvve}kHh#C@W{D6CgC zhjhH@@Ds)daQ+gRx{f3kX9=qq1+~0h)X1^8e~hTfazxR(-fY6vII&i1rpwGufpsAB z1;Un|WJS-I)5gu%)p2Q52EK5>G^dd35!CSWZ*HjzW%hHEW!b>x@Ap~HSF=6{dbf84 z&m0(})chQ`?06C}T>ZUU6cQQetc4PN*3Z1y^lIW)y>@Cj`ud2JsBl{po{Lu(DPod! zmGn~)^+%kvm{`oZS>s%~IpHDa7k@rSdXj$?evXeQl;vJzq&#oVs!l7H9d=cPZHR*Q zToKTx2VDvQtR_ut(@3%?M)0fyBbLsrKQ_;1>+|j6{q`YZf#gi=Lxl~y?jLQ^*Cc!o z;NziTUw+=N&CX>V6q#K1M#BWBGzE)Ju!v>dHwH&#neU@U4-awH-;Es*BF~?-po*## zaQsr$SQNiyeY(x6oJSd8lC!a_=tu))r8D<&XO*jg-&Y3Ce5bM2q>WgMp^eQUq7B`Q zoh4UHO@d(_!BqUe4(yEehJH+p;btU1N#b}|xX5=a)tpzGwthNPo&ELwXf=Ur?gokPbVW`a8^oPPZw7!iyb z+KV1H%p|Yv>&PT(*t!zeK$yAN)|9AZ&r#^;`;kdnDWAb7mUA#98DBT@{)Z3aWh@5IT(lah+Wjac zOU$Sg9$v5h06j>dZg_yELjKI1vz^WU=zt)MzpZP%gGY2QRF}pt1ncZ?*-j;bR`qLT zH2SWZ&DIWC78ACgDeut3>Xf!{{(iP}DLcH;gX3xYyy-m~U(0DZZ0CFNCr+C*vs*O* zgBtq}sEkGTlI(9$f7>%mlui&eEKAQQ*@QQ7@rMS@{DDtmJ{~JMwBJGuk-zp0eqPIY zj?6cX1ml(cPCWB5EsZ}p_bG0@zUEF}4l#)}Fz^Qld5tH1tYH=Ax}E=(+*nnUP^pg& z1x+VTypT@$fGzV9L}+tT%@R95380%0~%`iPBuTDsO1JQc&G`faF@-7X{&RY@- zWGJvY4g1Q)IM0jTZG7v&>60mT*ub(&kBWf$jtsQ7F}JzN3ML=eIn+2+t9x3@jK|0R zNl;k{b$g)DPoL@UkI4Sm%;t=yYW04R-)(e`$d9>=%a9WGcdY?aa3P@+qp3?LG3vv< zDu-?oNZ7gRGY$kqW=&5>4Czk;wgpIQY;IN z7Gb#cKJ894aJ-0LVSGqIN#QS(uzwJf*s9eriXGsagA&SdB}hZESBFd9ZRBW9cBlr)x#OHDrEvNa0Xvx9*)v1|aGnoG?CAaTsMClbl7u38lb)0&Ad=??QC%SH6jzJy{tYMO+dXJpK1)U?)dL9(=f;ns5=(c>JZsT18}`hG zZrt^I*viu;W08IX{n{P3}~|L65rB2`u1_Zu2>+d zF|w-gUSH~Ij^C#I;p{TEft5-*ta4JgdA(jbU$TGbbF%H`{=?fmEj&5F{4g9<8${wF z(ZU)h;ThwV8v&B#R6wRf;UFy8V;4VE`0?!~EFGHlyd)V%{PFjGds~d5|JSd-`U(5! zCSZvPmu!^8w8P0G4n+U^?sDV(YmHj~RhT={xaxK;fT#Pt18uCHgULNaRAHAnCBYAa zZDeL&fL= zddg@Gh!VSRnOU2$in5sLTyewSk8{7QuRHQpg8x?N8xxXB4YkKOF6O>6ciFaV7_%#~ zF;Hx~8Dt%+OpNJ;w&}*yIad>nWQ|kcCR5RJJcpybjVFzu#qXQY;=lC_uo#J1^P$XpCrxP?DxxO zNn%xhX3deIVHGJUn?yWgu3wmWX+PslqN4JFRHqfFbU?ZDSulc^SX(q9JjFrMVx(~` zQo%DqpVNKYTQFPQVWrekfe}eSGUPB7Iv-wp9T8Wnk34M@z9WL2MY60nIOxQ$q!O$J707XG^grk3y znzM?iTh=M;7CDuw6O@USX*ym6%%yQ7l=t{c$mK{fc}bpZ|J0DYOy%c{^+kk4s>Xvp zhq>eB?~(R;4I=mSPo7kM5}BKMQ*98?Rbi3}n$b11phbCGVkw=~Bu>o-cZyY4xjZBC z<*4CUdQ2?lHwY5%M#@6|5^)D;V(@TvL#6U#kqvUuwJiAh_G1)@DZeBn+E6x|p zAWGl)#_*F%<47H`l-J4-6NKI-P)}o8Ps{D+PiW&8%_<=g|T5bILDp{H9zW$vNp19v^f0|xu>czqDESYew}ek@oQPTrT^RDeUh`3z@N= z%Am!0l{tvNfcdhYjz1?1bVFnD-d*{3(I8`AAW;7HEM4toOpKkI5Ng6ExL=?}*_$pA zz%(Q{amzs~VRj**6emTO_s@sRg{8uFk!r2HW~~(J(m;O91SN>Ka_vFxy{w|%?jaHR zGYU#i?=)!yRH0Mwtfr`7zT5X{o8aC@ALj;Xb>t_j&)8cxeBPt}F@2KM4)7O+c3~dH z;83AgVY$6Nf6QJm7U;;MBiBlOn4+vVhzM`Q*xQ&kH{E|j=i0Bv|2uk^q&-HaP6@|K z24ZnT9$UoO?6oze2?IJv4xmPH7F8!n<$10Ul=C8{m^qD@Gwhd zp%Mi8&s7WNhEZN)k+*jcz9`VS3}Bu3nD97C2s4-GZq5}n zvHD$gRd?M2X7u$XPhT#wU{PZT}Kunxhbsd*@HX1dAaZ}6=7~M$_ zJ(tOgEY=|`9H@9Bo0 zKr|Z;Ggc%m1vn!l!NW&l6IJ`pHP%`GLq6C~0KG|o4)u>xd+YkR%PKJg7S(GnoQc-V z>2+SFFm{%O5XJ-)GNl4!Jq?Q%leKT%@9 z0REdeplJMN6Z7MnzasOz8uX;0xqFp(6EB^`{$!nm2=U%D?esx^HzAk#Xem4@ODgedZW6Gw*me;~0yPBlb$4>P_fvafN zrFI!NhhJ=KZr06C$-z<72rFfWnP`+NfW+wrht5|E$q@$z{vo#oQMF8m46kz1A@9oL zgJ2z_9CF{)r!^h=p7Fj8@Za{);31i+%`=LqqLoxNWG;c04IbHBZ#mc|4o}c_)e5jT zm?;@n!3s?H=`bh`HrR1GC2WOMJ5%KY_b8T$R$3>ANs`%P`syO`N30&uW*Rv~C3=?R z#lFuP|MDoUU33aZePn3b-8uXiO9MAL+n$hhpE<+h(#-h=?p--}LO1FUj+y#Gdhdo} z-h=t{HFX&6J_%^QE&meK>_NjAhLndQrBBuhaKO|qV{7HnU%>uH`E^Rhjy&&(p^uIzqKXm51moo zoyWd&xaYO!b)OIUB-TNTB^jpwP5L;CRv9jHU`($(Z%IZDSqyq+&*d({BmNQEP7K_N zZX_c@Y&Uz%@FqqyZvLM*SFyntDZ>;^{q_P5?Ul}7rLBx%m%-I+@ab15$THqMn*%G1mtXyi^eTA2JR8LAkNNt}T-*u?PwdE{7KLp>M5WwZozLux2bt3lv9p*1DbiJ%Q*K}8A=paP{7S~4w)GDz_#!l zj4uu4)#7JEK-zWP2ZIBZ!^u)XEukYzTaM||aI+f!GW{(|bipjzyGmiTv5Ze(j1 zh~3le8SK;fiM^Mh&#CP(9E6Rk&5{?fvaG$_>;?I!U^J)?fDW73Z8lqjd#z~sB6Ze3 z=^erq92k`^i+a903x|6T53@GNT34yV!fxY)q(= z=|4!=B(|&W}v9M4sL>l{R<5{Vd4c@17Uq&rl!)Mc`(D16 zY9t)~q*j647@sqAVg16CI2bSbb~KdCX&JG`o(;er+pzzXzL+$nExY94FTgv&%dEIA zal(xYf}Iuhb`7MNE!2A{bzBNgZqeTKuWov@C3{ulpOC2dyL>5+OZ-`flEnHWl>bFz z@*QlJCWiZ++`Uuz*g~+JxP63vxGC*V@G2+Fb~u~U;@UE$!#OC*%cf0t*6sF;(p`G` z{%VtwDnX^#g?v2I9QLCoW^|&nG=@<1)Lb|37iLdAEtyQXxCLw?v^Po0>Uvt0i{E!+ z4m1R^+2Hb3~_1PPzxs%u6&0u-MJVF5PD)vD1ifOtbZ;I9A$q_`FuABnzT{II0WTH+9%q-v(PECQx^%@VbI8}4?gE6 z#2ahcdN~-muw;Q>sc>lK{$fyzf@rsKk_dB&#nBs9DzP*~diPGN4;?6ph?!Y!ven}o zDSy3C!_<{qAbDxi#Th&%KB&#u4b7*Yl62FV`C+F%UK>>?Y8@$Cvq~M;#7)}aB9t=eF zU>^7Rz32dJnQ3IOei-`=s6k9R&5wcNrtrFCqdKg|{J>5d6+)5(d2Xv}R-G*a+rK!^ zFcgJ?jInLP&<*QY$m}&c{A=rRX;<6+nqnn&3&EtYOod~CQ$)S^fqX}_8P z#2i_I;7)Gl+C)yZa^A;Z=Ld@o!(UBsR5?-8yfy1wa(S2hITam?}WVy zzaqCnYbS9LQ|8hnaF!1w??oy}u`;?or71A!|16XO4Nsy@QXIK~-q6U)pXR=L0l{dR zxKB+l0)1v+Cyi43PE>=*aw(`DVdoxl$#Kj=hlMlq^GH57G@2IfZa~_neTyO-rejQz z9+BTmJ%gnq6B3QJO;RA|#xA=TYr4kdNNVY%;AR5`eLgt`CJR8J8>o!?bzJ`K#B-w; zb?20INnyuR_S7&5f6txJKFtOHtX8ggB1Rx2d)`p7Y84cjkr7ESju&arq_G50N8*H# zPf{cxCWTNMKKPpBSB4r|Fw1D%R-Y&HQicKh_A~ga7{p@Rib-a#?nEPkF$p*x)hq^FP?<1>C;>MfpGT z#Q(PNjVbpG3?LEtdI!(`8~o2Tp<8#2`?bC8pKD_L@>2c%-|Mc_N3J?1yB|U-}I_W36-8znaO2>XYcA7QD!r^h;w92Q*jA%beKy%eG(PF_iPwdv=c8x z$c{_xp7e*p-+3D4OkGCXRM6@0ApE^WeCLbbg8j z2^4`_20Y2{H9Q%lB)&BtPtCX81{s) zN-99FGVUrXvx~aEsm$zzc5=uu>fK&fKFT&9yg1rB_^Gr%8@rFWa)mV#rs`UGTGjHR z>1hKQLikZoGD^(0=YmXO19?q#>E|c7Jf7 zs<*CYQ)MMErTw0O4SRG=3+Lz6T79fnn{`CGYvc$-F*Q807ue|i0?m(uw%>tqTXjN{ z^Xc`Hyo6TwX($+i*9rD-Mb>c2aD+%-{>NK#Bb4uOa#vbvv=FExk4-q6w~l5fSM!Y; zRxXl3$OxkfiB;{FQMBt9(r6Pd!23%u3F$9UA~8;7CyD5jLWKzH%@3kr{cFmu#9glJ zt_V8JFyqp0%4^HM5f3;q%d8JnAf0m8^3v6_c(Vg`W{II!vyR0IcUNX8C}||9ww#tx zB_#INgHpU|C+YZ5{LQ=LntN*gkJjLuN(-s)=lO2OWmlBAtTMq(i~Dc?pAHnJI0>Su zI7tdz43>@*LY=hgTj9U;x73a;(AkmP@Zk4GI%CRg>6DjeIvSYaB}L#|=T}tLp4xXH zFGYBRQRI|j(Z0d~JysCf0e*EHywPYxE4$)%>58t!Tse1cvs_@=?+k)VdR1}GnbgW$ zq9V9_o&!AfwtR0!0!v@{Flvkv(505kHvfas+vCqX5^(K6PkBZAj^L2|I5rYY=e(?a z;xPyCjC%kA*IUw&eo2ylMKm#(((>hP#`n;B-PKwyz}LcwBC5S#;FQJw;YB9P?a8Sd zC#)$R z#1S867Lt@^wBQgFiguFORH8_y&UOhJ#%VbRT9kLCO)7i}>DzKt@yE&&K8kk`gKs){ zSFLa~dj)fWK1uKwF9)oU$wOJm*s$-C9O~kl^KTgb$8%8gww*W|Fq-qNC?Hf%<_{=& z#XOaiiI33Uy%4DSXOr1iVIY~g==u4xn*x`cWZ{FeR5} zs)_GPcF@t;SR^8RDy=wjP>X^$I^+K#QkIP!n{_H1mkz6tVg-Zq;il4g0;ZJq@-Vv$ z?*eoV4&_`+he`1>ehF&A28p~bj`9zrqbsrsjP*)KR=7c5N1UN&%KI>SCEXmz$fl_W zWhGo;ykmiLMLjDMft)Eu&*o>%KelNWf}_6j+HKvL39=GvY&e9-s=Aj1x+1jDCje0wmmukE@1P4P`=AIpSj_m~ArB$c?Piu{<5A(CgL<&YaYuoxp zdm8B;8W7y43zN5RL}1&;CS{@lE5}r7US3ugBe$jFPH^{==Fm>&rEf`1J}-kV?7b}Z zNl+p4^xLIxWE5yZaR)@K)R=%O%3r`$BG!yUDb%xVMMOAI1*?-5ke`EX=Ns8JVLuLa zGt`0FGci3T?L95k&aP)V{F-5AQo*)1+Bw~`w#-(@_kN^#wl~k z?uoA(C_NN6)JXDvm*k5;()(F;*lMJ|$!>zDJm<@6=Efw`8#u6|dG%wbwDbkUMGmm| zf0X%dKU2s}ci_LS5%fVOP;JfIJD5d?lRB8M$jT5awujDmI!PS)Oiv8kR-VmRS;voG zEdJ3Mr~r&4O*~tqC9KfCSUg?p6yet`6aB|oU?>AHf^8j?$kpiBx<21_mt1zH_qOz5 z`1W*J6x)Z{a_@)tA?G!IAY@r8NGUUY+E7CbE_;8!*(Fg``_Nwa-p`zCiWXjlW;)HI z-a8%drDoPouK~LJ5MZ$ShKKvXp_p!={+2Ne8 z_7C(xCXHU)vn`nGR!+g_4Hx40ufcJJ5BjN$-9hxp4ITPfxWo89_GAb{z`<#V2<%;S& zEKh*?(}os;akWvv>44)uDh8X7CK^+y{9PeW6=<|4Wi#vCd^1#c^zy=?H_pjZ&xIxL zQ4*E=KSOY>JBr9DWgsXwCYq8=DUOPo&^#hjcz{6aCs1`K5vC49*nL z=5jDoth~E;yE5-G0nY_~53oI3lmuEzo$^8eGgPL_U*(@u7>FxHh-*ma@0i>6k8h=( z+^teJ|9+QsqInQL6ml!Rx5$L2fCLpDg5>6-v*Y$^e)T+d*A6MgR2J>=S@Ltyt3V&R z&9ZI*)23e{%e0e8IN*F8uhz8**njvn>bhfRbZalMnXgw)?N0i-ax2+{PC!xeUfOmk zLf1#^TzdD4N(V@N08?MnYzrg*`1W36sH*Eu`BOI+WOOAU$c3|}FikGdF6*wvU)%hm z6)-ZrY4g*|b9xxyCp$)(2r#4ky%~c}=TYsJXAr{YHYhnlbm1%1>PWVp@I$rJCd=Hd{Df0ZF0BTgX z(!KX-bN?BB|C(X$5Yqd4ZR+lS6L!(ZR$?)K_YI-S4yTl{-SF*2{JFrxPrOXn?sfHa zr5xQ>gI2hWyAh|pwuiC)N^}MUt43HwgFp_Sjc;~($ik6-9p8jRbD>p#HADE@rt^m8SHrdqiP2-PjNv=jMu7T>coyD zC&_GN<{qHUF8azs#8L63h75k*sDz+ z#r1XL*SbQ3{7fG%eUZ?-4!S#XIZGK-%x%#7FKb&H6!GQ87Uu5yCkkBv7O^ z<`B!lktStld(QSI=QVh;rV#HK^%(G*SQ19-(jaQuz4yBhiVQD|Y#QCTns9&l!}=G( zy#&w&?Y=wWEgDv^m2AGqp`L=Keh=5fLQZjc9_dTkB8hMiyv5vip6HXl)Fhy#((sxV zYXTJfOMZNw?RyHfGK#IPiB8rL~WL?I?=a)VwXE+xzI+gKP4J>{ zUb>39AV1v~ChPGujH7?iSmn7z?-(5{GG~VY3E>m%AS+{NcwVLc;iJp8X;6J9jW}6< zfG{>5SRf{C1{}IbXLD_>>!S#*RB=P21YPL8)pQT1StLni_?R2EK!l4GBUAM(Kc2{* z|06U@2aL5sR={OEi{k>82>B5 zYVm`*TE8}zRNnql$y4ti2LG9N{r~JcAjX`6ddB*ClHa%&P9<}z^LYws@tMSVW|oty z&$RB$0@jxsA)p_4W9Y1WeN}`IB%bioO9H0mmbngXov8qK*`JYp`?NYVMY{Tq^JL-F z+L_4GX+HZCBi^jsPqzmzRU7@{85H7`)=@lIB8%g@dC6v<`lPI8aPP@8O~^#;X%(Ea z^*YDJ{y=$X@Cy6$Ye-pI0^qf2VJhzN*ElNVl{53d+J|p+SQ^)}iroU_*fcHfV8Gpw zHK;G01{gzWRXcb_2w+j)ZckrB7*>%D%~FJHTL9sCHYd1k;A%Ay`MLZ6cH`E|LW+WR zIE?gCYS0Ip?b@5te9m$J0dz6j8eD8C3wzX2xWmf-uSU+qovn0_NRH~?|H9{h4tF&rqJ4tA@=n|ANwWQQC7*b1BN6{HW(5h{U;=X3) zE_3gF?sNZu^PD{AyubJN=J)xYPipUJeBh)sm-G^YW#>*zyHs2sITM?_jhaF}I5VDq zIHV@x7#{9)!P#Yh?F3;Su&v#P4aj2sA1(`gExTK8x=eR38REW5xRdB9w+FqCdD@o&1#kOU>Qhy(lYRTla%idFliN^blp|B zEFOg07T+vZ(bK(T>`i)|ud*YBmISku_<&A44aR#}c79sShe-SIs9Qzh7Uten=5l&2 z%RA&gzNPEfJsSgWTqo$(jM`BL-IB%V`Kv|my zrD;oUU49#0ZXVTUc+6MRzzIc@fX1A+i}RLVI3w2>A|l#*j|6YfqrwQ^pt`kJf3z!{y|NWM-dC`&HH^o9d$4{xx0 zaGc>o!;x}|f-Qq)ryuR_z4zW>>SMcV^imnZ$!l++sLV6+%~uz=_&52#IOwt%WH`q` zHHf|r*$r(lX) zAVxuI+JK0XNiI+a#fg7z2B^xP$tV}-Ja6=fDA^cWlMVO%F-V)y6uwx_Go2wm%>&`_`eskYAZ7Z> zO%+_xr%Ru;DwuBa1qrFPQw#8H)IZQR9P=9ji~9ud^(`;g*I9TOC&vB8q?;pPk6F_b zvU~bVlNv}7aaTp~*DOa%-dL2KtQlkj0?b@{S1RI+iqX2ibt)qEu<7{XOn0;~_HZWL zFAHO0JZ&4Gqj!s)FV=5@-M5Lh6Iy?*pjwp6uG1jO=nWEWt+9R%Clm}wxDbOm>>BAM z8ScKXW13zxzFmG;A9Rs9R}i^(RTygP(aF7qE0|ey79j?q=6oQ?juCWcZl%hNpQnC; z-i4F7Cu&u?0#?eHsf)KVi{()<xB zjoQ-DceCG*K+m{JXd-YT9kev`x$sV~cvU$GIID1IqO$RgQ%=w^W1R|^3ZK4^7}`dl z=;guzH2-QHFUS`V^XeEe_c}yVB2c9Rg|VU!U{h6Qf9#^A^V^j~l%paJ zkALw+?>}<#Jdy%Bl$~I>Qhz|FmZCB3RM>Bug~Sj|;cc}B#EI1=TdgqU#$ej@jS9Gf z>=5sE1z3$!>#pJLEeYkrSeATDk zU8wL==C_#%4zoY+9-}Wb=tOnLJ=;jB>|NY}>$BS|M{NYbmsXii5%2hN*TAa^-cIjb zHFc<8iRFFD^|U{RF6x&|Bf+D6B{CEJJUaQ?zZuOul(3y{K$l2U%_|VAs$nj>c>j|Z z{3lZQZ+u~-8(!^6ePlo9+DA<~h`9&a!+t-@sK#f<5=Px|B_9fAn^Z5{3;y7feTZ$H z@-M&-au*Rw#^fbsp<0wZ;N^Z{)L}|y;0q}fhbc zANzFhVBfvYhkX*v>)WnBU8$q}P{V@SY-pf3-!w8tI&(k_ESc5__SLroSyCw49hO6W zA!uUwDdLqNLQH}%5xixI>b9K>=1M@&YQ(3DlIgZ0eKy{f6oMt|M8K?x-e7Sa^8K&& zar^>~kIhRV^j6!%-U~xT6Mh(!l0hZ%7G<0UlPR>+hNv1=CNi>6reEP}H{*3BPR*)0$-XH<=WZD#U{(+*-7hpMjK- z2m{p(4vkB$Aagc5TbNEf-n{}J5;)fb+82+%eXaf3#Lh~OM>^oJxBi_%g>PyB&m8Qv-ZmS#NVvfhH#+5 z0?q~&eMD_+ZH>Qui&*YbceJvw=pu6qiCeD@Om}4QaC653cU@SRf~+h_bl->a!ZXM_ zKp(&-AaFGe}r&_!t%u8k%|ZOIGxmGs?b%l(DC0Neu_s zsbRoSCLxd5oMn*7f)ZKLCr&^VrGD^*ae(~{Z4N%l&x3%-AxL^!(hXa_F1HD&w9mAq z36zt7z$3SPJhCP6^E7(y`RC7{-|WtmM4oAi&0f#r$)r$NJI4_l#8~=;c1|jf0EfdV zBMCZ={Zdv4qwhPy9vmEWOgm+!b+hRM!~e5AB=o|CkzW`@rQ|)qOK)G_4P|a^aKA%* zB)kLOYRRX|KOi9Wwo$Zqk)rv9_N3*r8bBJMx(nqqc@dTztG*{;gZ^>P+(HK*#lUh9h416yyi+xw&a2I@yNu#LnFws{&yk z&AXelnYpZDlk|1s>L)%gU^&wz(Gm0;5n%xBLs5`kha;8$RPDp!|^{Qk4Ot!+wWl-U{vrrfuePBJU2)JIfwYAUZ z+)2v=lA7ivmdKJn5M!B{ncd5Cxpv29YoP|%pE~{Q7;@Gm*?3^4NlOFlYC;h9V2Q~* t;UbLZra4dn4#n`Tz4A+Rg%JBD^eJwo~u{sso$He&z) diff --git a/packages/extensionmanager/src/model.ts b/packages/extensionmanager/src/model.ts index 640ebd02e211..86bb08ba482a 100644 --- a/packages/extensionmanager/src/model.ts +++ b/packages/extensionmanager/src/model.ts @@ -176,6 +176,19 @@ const EXTENSION_API_PATH = 'lab/api/extensions'; */ export type Action = 'install' | 'uninstall' | 'enable' | 'disable'; +/** + * Additional options for an action. + */ +export interface IActionOptions { + /** + * Install the version of the entry. + * + * #### Note + * This is ignored by all actions except install. + */ + useVersion?: string; +} + /** * Model for an extension list. */ @@ -343,9 +356,13 @@ export class ListModel extends VDomModel { * Install an extension. * * @param entry An entry indicating which extension to install. + * @param options Additional options for the action. */ - async install(entry: IEntry): Promise { - await this.performAction('install', entry).then(data => { + async install( + entry: IEntry, + options: { useVersion?: string } = {} + ): Promise { + await this.performAction('install', entry, options).then(data => { if (data.status !== 'ok') { reportInstallError(entry.name, data.message, this.translator); } @@ -480,19 +497,27 @@ export class ListModel extends VDomModel { * * @param action A valid action to perform. * @param entry The extension to perform the action on. + * @param actionOptions Additional options for the action. */ protected performAction( action: string, - entry: IEntry + entry: IEntry, + actionOptions: IActionOptions = {} ): Promise { + const bodyJson: Record = { + cmd: action, + extension_name: entry.name + }; + + if (actionOptions.useVersion) { + bodyJson['extension_version'] = actionOptions.useVersion; + } + const actionRequest = Private.requestAPI( {}, { method: 'POST', - body: JSON.stringify({ - cmd: action, - extension_name: entry.name - }) + body: JSON.stringify(bodyJson) } ); diff --git a/packages/extensionmanager/src/widget.tsx b/packages/extensionmanager/src/widget.tsx index 1f115e368733..f532bc5a4ce7 100644 --- a/packages/extensionmanager/src/widget.tsx +++ b/packages/extensionmanager/src/widget.tsx @@ -18,7 +18,7 @@ import { Message } from '@lumino/messaging'; import { AccordionLayout, AccordionPanel } from '@lumino/widgets'; import * as React from 'react'; import ReactPaginate from 'react-paginate'; -import { Action, IEntry, ListModel } from './model'; +import { Action, IActionOptions, IEntry, ListModel } from './model'; const BADGE_SIZE = 32; const BADGE_QUERY_SIZE = Math.floor(devicePixelRatio * BADGE_SIZE); @@ -129,7 +129,11 @@ function ListEntry(props: ListEntry.IProperties): React.ReactElement { <> {ListModel.entryHasUpdate(entry) && ( )} diff --git a/packages/apputils/src/toolbar/widget.tsx b/packages/apputils/src/toolbar/widget.tsx index 266c02b25f6b..e3aa81af6a1f 100644 --- a/packages/apputils/src/toolbar/widget.tsx +++ b/packages/apputils/src/toolbar/widget.tsx @@ -175,6 +175,8 @@ namespace Private { /** * A toolbar item that displays kernel status. + * + * @deprecated This code is not use any longer and will be removed in JupyterLab 5 */ export class KernelStatus extends Widget { /** diff --git a/packages/apputils/style/base.css b/packages/apputils/style/base.css index ac56a945841d..37422d04853d 100644 --- a/packages/apputils/style/base.css +++ b/packages/apputils/style/base.css @@ -8,3 +8,4 @@ @import './inputdialog.css'; @import './mainareawidget.css'; @import './materialcolors.css'; +@import './toolbar.css'; diff --git a/packages/apputils/style/toolbar.css b/packages/apputils/style/toolbar.css new file mode 100644 index 000000000000..60afea007d7e --- /dev/null +++ b/packages/apputils/style/toolbar.css @@ -0,0 +1,13 @@ +/* + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ + +/* @deprecated dead code to be removed in JupyterLab 5 */ +.jp-Toolbar-item.jp-Toolbar-kernelStatus { + display: inline-block; + width: 32px; + background-repeat: no-repeat; + background-position: center; + background-size: 16px; +} diff --git a/packages/apputils/test/toolbar.spec.ts b/packages/apputils/test/toolbar.spec.ts index 9d4685628273..48f3f4af0eb2 100644 --- a/packages/apputils/test/toolbar.spec.ts +++ b/packages/apputils/test/toolbar.spec.ts @@ -74,9 +74,7 @@ describe('@jupyterlab/apputils', () => { await sessionContext.initialize(); Widget.attach(item, document.body); await framePromise(); - const node = item.node.querySelector( - '.jp-ToolbarButtonComponent-label' - )!; + const node = item.node.querySelector('.jp-Toolbar-kernelName')!; expect(node.textContent).toBe(sessionContext.kernelDisplayName); }); }); diff --git a/packages/cell-toolbar/style/base.css b/packages/cell-toolbar/style/base.css index e3db6244a365..66d79e3d7284 100644 --- a/packages/cell-toolbar/style/base.css +++ b/packages/cell-toolbar/style/base.css @@ -18,11 +18,10 @@ .jp-cell-toolbar { display: flex; flex-direction: row; - padding: 2px 0; + padding: 0; min-height: 25px; z-index: 6; position: absolute; - top: 5px; right: 8px; /* Override .jp-Toolbar */ diff --git a/packages/completer/style/base.css b/packages/completer/style/base.css index 6b131d92a876..15845a671bc7 100644 --- a/packages/completer/style/base.css +++ b/packages/completer/style/base.css @@ -329,7 +329,7 @@ display: none; } -[data-command='inline-completer:next'] > .jp-ToolbarButtonComponent-icon, -[data-command='inline-completer:previous'] > .jp-ToolbarButtonComponent-icon { +.jp-InlineCompleter [data-command='inline-completer:next'] > svg, +.jp-InlineCompleter [data-command='inline-completer:previous'] > svg { scale: 1.5; } diff --git a/packages/debugger/src/panels/breakpoints/pauseonexceptions.tsx b/packages/debugger/src/panels/breakpoints/pauseonexceptions.tsx index d68abab8216b..ce43b16c1b8e 100644 --- a/packages/debugger/src/panels/breakpoints/pauseonexceptions.tsx +++ b/packages/debugger/src/panels/breakpoints/pauseonexceptions.tsx @@ -52,9 +52,7 @@ export class PauseOnExceptionsWidget extends ToolbarButton { const exceptionBreakpointFilters = session?.exceptionBreakpointFilters; this._props.className = PAUSE_ON_EXCEPTION_BUTTON_CLASS; if (this._props.service.session?.isStarted && exceptionBreakpointFilters) { - if (session.isPausingOnException()) { - this._props.className += ' lm-mod-toggled'; - } + this._props.pressed = session.isPausingOnException(); this._props.enabled = true; } else { this._props.enabled = false; diff --git a/packages/debugger/src/panels/kernelSources/body.tsx b/packages/debugger/src/panels/kernelSources/body.tsx index 1c069bafdf9d..85ab4e2c583f 100644 --- a/packages/debugger/src/panels/kernelSources/body.tsx +++ b/packages/debugger/src/panels/kernelSources/body.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { openKernelSourceIcon } from '../../icons'; -import { ReactWidget, ToolbarButtonComponent } from '@jupyterlab/ui-components'; +import { classes, LabIcon, ReactWidget } from '@jupyterlab/ui-components'; import { showErrorMessage } from '@jupyterlab/apputils'; @@ -27,6 +27,11 @@ const FILTERBOX_CLASS = 'jp-DebuggerKernelSource-filterBox'; */ const FILTERBOX_HIDDEN_CLASS = 'jp-DebuggerKernelSource-filterBox-hidden'; +/** + * The class for each source row. + */ +const SOURCE_CLASS = 'jp-DebuggerKernelSource-source'; + /** * The body for a Sources Panel. */ @@ -63,12 +68,11 @@ export class KernelSourcesBody extends ReactWidget { const path = module.path; const key = name + (keymap[name] = (keymap[name] ?? 0) + 1).toString(); - const button = ( - { this._debuggerService .getSource({ @@ -89,9 +93,15 @@ export class KernelSourcesBody extends ReactWidget { ); }); }} - /> + > + + {name} + ); - return button; }); }} diff --git a/packages/debugger/src/panels/variables/index.ts b/packages/debugger/src/panels/variables/index.ts index 3823503c578d..00df08a97d12 100644 --- a/packages/debugger/src/panels/variables/index.ts +++ b/packages/debugger/src/panels/variables/index.ts @@ -86,15 +86,8 @@ export class Variables extends PanelWithToolbar { }); const markViewButtonSelection = (selectedView: string): void => { - const viewModeClassName = 'jp-ViewModeSelected'; - - if (selectedView === 'tree') { - tableViewButton.removeClass(viewModeClassName); - treeViewButton.addClass(viewModeClassName); - } else { - treeViewButton.removeClass(viewModeClassName); - tableViewButton.addClass(viewModeClassName); - } + tableViewButton.pressed = selectedView !== 'tree'; + treeViewButton.pressed = !tableViewButton.pressed; }; markViewButtonSelection(this._table.isHidden ? 'tree' : 'table'); diff --git a/packages/debugger/style/base.css b/packages/debugger/style/base.css index ca4d77c8c382..b36127557889 100644 --- a/packages/debugger/style/base.css +++ b/packages/debugger/style/base.css @@ -5,7 +5,6 @@ @import './breakpoints.css'; @import './callstack.css'; @import './editor.css'; -@import './icons.css'; @import './kernelSources.css'; @import './sidebar.css'; @import './sources.css'; @@ -21,6 +20,11 @@ margin-right: 0; } +.jp-DebuggerBugButton[aria-pressed='true'] { + /* Undo default toolkit style */ + box-shadow: none; +} + .jp-DebuggerBugButton[aria-pressed='true'] path { fill: var(--jp-warn-color0); } diff --git a/packages/debugger/style/icons.css b/packages/debugger/style/icons.css deleted file mode 100644 index 31e066dd9cf4..000000000000 --- a/packages/debugger/style/icons.css +++ /dev/null @@ -1,28 +0,0 @@ -/*----------------------------------------------------------------------------- -| Copyright (c) Jupyter Development Team. -| Distributed under the terms of the Modified BSD License. -|----------------------------------------------------------------------------*/ - -button.jp-Button.jp-mod-minimal.jp-TreeView.jp-TreeView, -button.jp-Button.jp-mod-minimal.jp-TreeView.jp-TableView { - display: flex; - align-items: center; - -webkit-transition: 0.4s; - transition: 0.4s; - width: 35px; -} - -.jp-ViewModeSelected .jp-Button, -.jp-PauseOnExceptions.lm-mod-toggled { - background-color: var(--jp-inverse-layout-color3); -} - -.jp-ViewModeSelected .jp-Button:hover, -.jp-PauseOnExceptions.lm-mod-toggled:hover { - background-color: var(--jp-inverse-layout-color4); -} - -.jp-ViewModeSelected .jp-Button .jp-icon3[fill], -.jp-PauseOnExceptions.lm-mod-toggled .jp-icon3[fill] { - fill: var(--jp-layout-color1); -} diff --git a/packages/debugger/style/kernelSources.css b/packages/debugger/style/kernelSources.css index ac202c1ec012..b3b1fc913927 100644 --- a/packages/debugger/style/kernelSources.css +++ b/packages/debugger/style/kernelSources.css @@ -34,3 +34,19 @@ .jp-DebuggerKernelSource-filterBox-hidden { display: none; } + +.jp-DebuggerKernelSource-source { + display: flex; + align-items: center; + padding: 4px; + cursor: pointer; +} + +.jp-DebuggerKernelSource-source:hover { + background-color: var(--jp-layout-color2); +} + +.jp-DebuggerKernelSource-source > svg { + height: 16px; + width: 16px; +} diff --git a/packages/debugger/style/sidebar.css b/packages/debugger/style/sidebar.css index ca3df1505037..bebac8b8d1bb 100644 --- a/packages/debugger/style/sidebar.css +++ b/packages/debugger/style/sidebar.css @@ -10,3 +10,9 @@ margin: 0 auto 0 0; padding: 4px 10px; } + +.jp-DebuggerSidebar-body + .jp-AccordionPanel-title + jp-toolbar::part(positioning-region) { + flex-wrap: nowrap; +} diff --git a/packages/debugger/test/debugger.spec.ts b/packages/debugger/test/debugger.spec.ts index b237e47e600b..104b7209a171 100644 --- a/packages/debugger/test/debugger.spec.ts +++ b/packages/debugger/test/debugger.spec.ts @@ -3,6 +3,8 @@ import { act } from 'react-dom/test-utils'; +import { Button } from '@jupyter/web-components'; + import { CodeEditorWrapper } from '@jupyterlab/codeeditor'; import { @@ -40,6 +42,8 @@ import { IDebugger } from '../src/tokens'; const server = new JupyterServer(); +const emptyFn = () => undefined; + beforeAll(async () => { await server.start(); }, 30000); @@ -54,6 +58,14 @@ describe('Debugger', () => { const service = new DebuggerService({ specsManager, config }); const registry = new CommandRegistry(); const languages = new EditorLanguageRegistry(); + const callstackToolbarCommands = { + continue: 'continue', + terminate: 'terminate', + next: 'next', + stepIn: 'stepIn', + stepOut: 'stepOut', + evaluate: 'evaluate' + }; EditorLanguageRegistry.getDefaultLanguages() .filter(lang => ['Python'].includes(lang.name)) .forEach(lang => { @@ -101,16 +113,16 @@ describe('Debugger', () => { session = new Debugger.Session({ connection, config }); service.session = session; + // Populate the command registry with fake command to render the button. + Object.keys(callstackToolbarCommands).forEach(command => { + registry.addCommand(command, { execute: emptyFn }); + }); + sidebar = new Debugger.Sidebar({ service, callstackCommands: { registry, - continue: '', - terminate: '', - next: '', - stepIn: '', - stepOut: '', - evaluate: '' + ...callstackToolbarCommands }, breakpointsCommands: { registry, @@ -199,10 +211,10 @@ describe('Debugger', () => { expect(title[0].innerHTML).toContain('Variables'); }); it('should have two buttons', () => { - const buttons = toolbar.querySelectorAll('button'); + const buttons = toolbar.querySelectorAll('jp-button'); expect(buttons.length).toBe(2); - expect(buttons[0].title).toBe('Tree View'); - expect(buttons[1].title).toBe('Table View'); + expect((buttons[0] as Button).title).toBe('Tree View'); + expect((buttons[1] as Button).title).toBe('Table View'); }); }); describe('Callstack toolbar', () => { @@ -224,7 +236,7 @@ describe('Debugger', () => { expect(title[0].innerHTML).toContain('Callstack'); }); it('should have six buttons', () => { - const buttons = toolbar.querySelectorAll('button'); + const buttons = toolbar.querySelectorAll('jp-button'); expect(buttons.length).toBe(6); }); }); @@ -247,7 +259,7 @@ describe('Debugger', () => { expect(title[0].innerHTML).toContain('Breakpoints'); }); it('should have two buttons', () => { - const buttons = toolbar.querySelectorAll('button'); + const buttons = toolbar.querySelectorAll('jp-button'); expect(buttons.length).toBe(2); }); }); @@ -271,7 +283,7 @@ describe('Debugger', () => { }); it('should have one button', () => { - const buttons = toolbar.querySelectorAll('button'); + const buttons = toolbar.querySelectorAll('jp-button'); expect(buttons.length).toBe(1); }); }); diff --git a/packages/filebrowser/src/browser.ts b/packages/filebrowser/src/browser.ts index 37ffea0d2a16..cc280bd3133b 100644 --- a/packages/filebrowser/src/browser.ts +++ b/packages/filebrowser/src/browser.ts @@ -62,8 +62,6 @@ export class FileBrowser extends SidePanel { model.connectionFailure.connect(this._onConnectionFailure, this); this._manager = model.manager; - // a11y - this.toolbar.node.setAttribute('role', 'navigation'); this.toolbar.node.setAttribute( 'aria-label', this._trans.__('file browser') diff --git a/packages/filebrowser/style/base.css b/packages/filebrowser/style/base.css index 83a379e55cb1..8a69e1f9c1b2 100644 --- a/packages/filebrowser/style/base.css +++ b/packages/filebrowser/style/base.css @@ -23,14 +23,15 @@ } .jp-FileBrowser-toolbar.jp-Toolbar { - flex-wrap: wrap; - row-gap: 12px; border-bottom: none; height: auto; margin: 8px 12px 0; box-shadow: none; padding: 0; - justify-content: flex-start; +} + +.jp-FileBrowser-toolbar.jp-Toolbar::part(positioning-region) { + row-gap: 12px; } .jp-FileBrowser-Panel { @@ -71,7 +72,6 @@ .jp-FileBrowser-toolbar > .jp-Toolbar-item { flex: 0 0 auto; padding-left: 0; - padding-right: 2px; align-items: center; height: unset; } diff --git a/packages/filebrowser/test/browser.spec.ts b/packages/filebrowser/test/browser.spec.ts index 248f673ca21e..eeb89e5bfa15 100644 --- a/packages/filebrowser/test/browser.spec.ts +++ b/packages/filebrowser/test/browser.spec.ts @@ -75,7 +75,7 @@ describe('filebrowser/browser', () => { it('toolbar should have an aria label of file browser and a role of navigation', () => { const toolbar = fileBrowser.toolbar.node; expect(toolbar.getAttribute('aria-label')).toEqual('file browser'); - expect(toolbar.getAttribute('role')).toEqual('navigation'); + expect(toolbar.getAttribute('role')).toEqual('toolbar'); }); }); diff --git a/packages/notebook/style/executionindicator.css b/packages/notebook/style/executionindicator.css index b3dfc73644e4..d69a79d17585 100644 --- a/packages/notebook/style/executionindicator.css +++ b/packages/notebook/style/executionindicator.css @@ -16,8 +16,8 @@ .jp-Notebook-ExecutionIndicator { position: relative; display: inline-block; - height: 100%; z-index: 9997; + padding-top: 1px; } .jp-Notebook-ExecutionIndicator-tooltip { diff --git a/packages/notebook/style/toolbar.css b/packages/notebook/style/toolbar.css index 22fc2bd7eca5..c14228aaff39 100644 --- a/packages/notebook/style/toolbar.css +++ b/packages/notebook/style/toolbar.css @@ -37,25 +37,8 @@ display: block; } -.jp-Notebook-toolbarCellTypeDropdown span { - top: 5px !important; -} - -.jp-Toolbar-responsive-popup { - position: absolute; - height: fit-content; - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: flex-end; - border-bottom: var(--jp-border-width) solid var(--jp-toolbar-border-color); - box-shadow: var(--jp-toolbar-box-shadow); - background: var(--jp-toolbar-background); - min-height: var(--jp-toolbar-micro-height); - padding: var(--jp-notebook-toolbar-padding); - z-index: 1; - right: 0; - top: 0; +.jp-Notebook-toolbarCellTypeDropdown select:focus-visible { + outline-color: var(--accent-fill-focus); } .jp-Toolbar > .jp-Toolbar-responsive-opener { diff --git a/packages/running/src/index.tsx b/packages/running/src/index.tsx index 9a5c081299cf..376d97a07219 100644 --- a/packages/running/src/index.tsx +++ b/packages/running/src/index.tsx @@ -382,8 +382,8 @@ class Section extends PanelWithToolbar { const enabled = runningItems.length > 0; this._button = new ToolbarButton({ label: shutdownAllLabel, - className: `${SHUTDOWN_ALL_BUTTON_CLASS} jp-mod-styled ${ - !enabled && 'jp-mod-disabled' + className: `${SHUTDOWN_ALL_BUTTON_CLASS}${ + !enabled ? ' jp-mod-disabled' : '' }`, enabled, onClick: onShutdown @@ -412,9 +412,11 @@ class Section extends PanelWithToolbar { const button = this._button; button.enabled = this._manager.running().length > 0; if (button.enabled) { - button.node.querySelector('button')?.classList.remove('jp-mod-disabled'); + button.node + .querySelector('jp-button') + ?.classList.remove('jp-mod-disabled'); } else { - button.node.querySelector('button')?.classList.add('jp-mod-disabled'); + button.node.querySelector('jp-button')?.classList.add('jp-mod-disabled'); } } diff --git a/packages/running/style/base.css b/packages/running/style/base.css index 89e31d587fa5..a72affce4b34 100644 --- a/packages/running/style/base.css +++ b/packages/running/style/base.css @@ -28,7 +28,7 @@ font-size: var(--jp-ui-font-size1); } -.jp-RunningSessions > .jp-SidePanel-toolbar { +.jp-RunningSessions > .jp-SidePanel-toolbar::part(positioning-region) { justify-content: flex-end; } @@ -113,8 +113,7 @@ background: var(--jp-layout-color3); } -.jp-RunningSessions-shutdownAll.jp-mod-styled - > .jp-ToolbarButtonComponent-label { +.jp-RunningSessions-shutdownAll.jp-ToolbarButtonComponent { color: var(--jp-warn-color1); background-color: transparent; border-radius: 2px; @@ -123,24 +122,20 @@ text-overflow: ellipsis; } -.jp-RunningSessions-shutdownAll.jp-mod-styled:hover - > .jp-ToolbarButtonComponent-label { +.jp-RunningSessions-shutdownAll.jp-ToolbarButtonComponent:hover { background-color: var(--jp-layout-color2); } -.jp-RunningSessions-shutdownAll.jp-mod-styled:focus - > .jp-ToolbarButtonComponent-label { +.jp-RunningSessions-shutdownAll.jp-ToolbarButtonComponent:focus { border: none; box-shadow: none; background-color: var(--jp-layout-color2); } -.jp-RunningSessions-shutdownAll.jp-mod-styled.jp-mod-disabled - > .jp-ToolbarButtonComponent-label { +.jp-RunningSessions-shutdownAll.jp-ToolbarButtonComponent.jp-mod-disabled { color: var(--jp-ui-font-color2); } -.jp-RunningSessions-shutdownAll.jp-mod-styled.jp-mod-disabled:hover - > .jp-ToolbarButtonComponent-label { +.jp-RunningSessions-shutdownAll.jp-ToolbarButtonComponent.jp-mod-disabled:hover { background: none; } diff --git a/packages/statusbar/src/components/progressCircle.tsx b/packages/statusbar/src/components/progressCircle.tsx index 6ffa4d747843..5ea5273d015b 100644 --- a/packages/statusbar/src/components/progressCircle.tsx +++ b/packages/statusbar/src/components/progressCircle.tsx @@ -65,6 +65,7 @@ export function ProgressCircle(props: ProgressCircle.IProps): JSX.Element { fill="none" /> svg { display: block; margin: 0 auto; width: 16px; - height: 24px; align-self: normal; } -.jp-Statusbar-ProgressCircle path { +.jp-Statusbar-ProgressCircle .jp-Statusbar-ProgressCirclePath { fill: var(--jp-inverse-layout-color3); } diff --git a/packages/testing/src/jest-config.ts b/packages/testing/src/jest-config.ts index e291a3be8bf1..9c1c5ccaac85 100644 --- a/packages/testing/src/jest-config.ts +++ b/packages/testing/src/jest-config.ts @@ -7,7 +7,11 @@ import path from 'path'; const esModules = [ '@codemirror', + '@microsoft', + '@jupyter/react-components', + '@jupyter/web-components', '@jupyter/ydoc', + 'exenv-es6', 'lib0', 'nanoid', 'vscode-ws-jsonrpc', diff --git a/packages/toc-extension/style/base.css b/packages/toc-extension/style/base.css index 1b66edd7fd16..215ca5d2e743 100644 --- a/packages/toc-extension/style/base.css +++ b/packages/toc-extension/style/base.css @@ -2,15 +2,3 @@ | Copyright (c) Jupyter Development Team. | Distributed under the terms of the Modified BSD License. |----------------------------------------------------------------------------*/ - -.jp-toc-numberingButton:hover button.lm-mod-toggled.jp-Button.jp-mod-minimal { - background-color: var(--jp-inverse-layout-color4); -} - -.jp-toc-numberingButton button.lm-mod-toggled .jp-icon3[fill] { - fill: var(--jp-layout-color1); -} - -.jp-toc-numberingButton button.lm-mod-toggled.jp-Button.jp-mod-minimal { - background-color: var(--jp-inverse-layout-color3); -} diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 174de72a69c4..93fbdf8911ac 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -41,6 +41,8 @@ "watch": "tsc -b --watch" }, "dependencies": { + "@jupyter/react-components": "^0.13.3", + "@jupyter/web-components": "^0.13.3", "@jupyterlab/coreutils": "^6.1.0-alpha.3", "@jupyterlab/observables": "^5.1.0-alpha.3", "@jupyterlab/rendermime-interfaces": "^3.9.0-alpha.2", diff --git a/packages/ui-components/src/components/accordiontoolbar.ts b/packages/ui-components/src/components/accordiontoolbar.ts index 5273921b2c6d..5a8d3e4c7407 100644 --- a/packages/ui-components/src/components/accordiontoolbar.ts +++ b/packages/ui-components/src/components/accordiontoolbar.ts @@ -233,7 +233,7 @@ export namespace AccordionToolbar { * * #### Note * - * Default titleSpace is 29 px (default var(--jp-private-toolbar-height) - but not styled) + * Default titleSpace is 32 px (default var(--jp-private-toolbar-height) - but not styled) */ export function createLayout( options: AccordionPanel.IOptions @@ -245,7 +245,7 @@ export namespace AccordionToolbar { orientation: options.orientation, alignment: options.alignment, spacing: options.spacing, - titleSpace: options.titleSpace ?? 29 + titleSpace: options.titleSpace ?? 32 }) ); } diff --git a/packages/ui-components/src/components/htmlselect.tsx b/packages/ui-components/src/components/htmlselect.tsx index 2dc2eda8685c..ed9fcc594ede 100644 --- a/packages/ui-components/src/components/htmlselect.tsx +++ b/packages/ui-components/src/components/htmlselect.tsx @@ -63,6 +63,12 @@ export class HTMLSelect extends React.Component { className ); + // If the HTMLSelect is integrated to a toolbar, we avoid propagating the focus + // to the element with tabindex=0. + const handleFocus = (event: React.FocusEvent) => { + event.stopPropagation(); + }; + const optionChildren = options.map(option => { const props: IOptionProps = typeof option === 'object' ? option : { value: option }; @@ -76,6 +82,7 @@ export class HTMLSelect extends React.Component { return (
-
@@ -212,11 +216,15 @@ function BuildSettingForm(props: ISettingFormProps): JSX.Element {
-

+

+
{ this.props.updateSearchQuery(event)} placeholder={trans.__('Search…')} diff --git a/yarn.lock b/yarn.lock index 264b1721b0fb..3eab467b4321 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2506,6 +2506,7 @@ __metadata: dependencies: "@codemirror/state": ^6.2.0 "@jupyter/ydoc": ^1.1.1 + "@jupyterlab/apputils": ^4.2.0-alpha.4 "@jupyterlab/coreutils": ^6.1.0-alpha.4 "@jupyterlab/nbformat": ^4.1.0-alpha.4 "@jupyterlab/observables": ^5.1.0-alpha.4 @@ -3820,6 +3821,7 @@ __metadata: resolution: "@jupyterlab/lsp-extension@workspace:packages/lsp-extension" dependencies: "@jupyterlab/application": ^4.1.0-alpha.4 + "@jupyterlab/apputils": ^4.2.0-alpha.4 "@jupyterlab/lsp": ^4.1.0-alpha.4 "@jupyterlab/running": ^4.1.0-alpha.4 "@jupyterlab/settingregistry": ^4.1.0-alpha.4 From 179511b5cbe44d1e387b7535b9f9a0e4a7ce0928 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 15 Dec 2023 21:03:40 +0100 Subject: [PATCH 069/147] Update `actions/upload-artifact` and `action/download-artifact` (#15536) --- .github/workflows/benchmark.yml | 2 +- .github/workflows/check-release.yml | 2 +- .github/workflows/galata.yml | 8 ++++---- .github/workflows/linuxtests.yml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index af0f6a8b57c7..60cb28974081 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -190,7 +190,7 @@ jobs: - name: Upload Galata Test assets if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: benchmark-assets path: | diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index cb82187d1e87..37ef8dfcd612 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -26,7 +26,7 @@ jobs: version_spec: next - name: Upload Assets - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jupyterlab-releaser-dist-${{ github.run_number }} path: | diff --git a/.github/workflows/galata.yml b/.github/workflows/galata.yml index 1d2c032b9c82..a599c3531c6d 100644 --- a/.github/workflows/galata.yml +++ b/.github/workflows/galata.yml @@ -65,7 +65,7 @@ jobs: - name: Upload Galata Test assets if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jupyterlab-galata-test-assets path: | @@ -74,7 +74,7 @@ jobs: - name: Upload Galata Test report if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jupyterlab-galata-report path: | @@ -176,7 +176,7 @@ jobs: - name: Upload Galata Test assets if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jupyterlab-documentation-test-assets path: | @@ -184,7 +184,7 @@ jobs: - name: Upload Galata Test report if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jupyterlab-documentation-report path: | diff --git a/.github/workflows/linuxtests.yml b/.github/workflows/linuxtests.yml index 127d3d701316..4b4b57946d38 100644 --- a/.github/workflows/linuxtests.yml +++ b/.github/workflows/linuxtests.yml @@ -72,7 +72,7 @@ jobs: - name: Upload ${{ matrix.group }} results if: ${{ matrix.upload-output && always() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.group }} ${{ github.run_number }} path: | @@ -112,7 +112,7 @@ jobs: run: | pip install build python -m build --sdist - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: "sdist" path: dist/*.tar.gz @@ -126,7 +126,7 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Download sdist - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Install From SDist run: | set -ex From 8c5aa3aa1544020d1e4910825e358736b54fccdf Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Tue, 19 Dec 2023 09:52:13 +0100 Subject: [PATCH 070/147] Manually trigger benchmark tests instead of running them on `pull_request_review` (#15523) * Run benchmark tests on schedule instead of pr review * Trigger on "please run benchmarks" --- .github/workflows/benchmark.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 60cb28974081..61e94de14948 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,14 +1,13 @@ name: Benchmark Tests on: - # schedule: - # - cron: '12 1 * * 0' - pull_request_review: + issue_comment: + types: [created, edited] jobs: test: name: Execute benchmark tests - if: github.event_name == 'schedule' || (github.event_name == 'pull_request_review' && (github.event.review.state == 'approved' || contains(github.event.review.body, 'please run benchmark'))) + if: ${{ github.event.issue.pull_request && (contains(github.event.comment.body, 'please run benchmarks') }} runs-on: ubuntu-22.04 From cc800df59c9862521143cbf8b2bce8d092ffef64 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 19 Dec 2023 15:53:54 +0200 Subject: [PATCH 071/147] Fix WatchLabExtensionApp.aliases so `--help` works (#15542) Fixes #15540 --- jupyterlab/labextensions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jupyterlab/labextensions.py b/jupyterlab/labextensions.py index 071982c69606..e6471abcb9ab 100644 --- a/jupyterlab/labextensions.py +++ b/jupyterlab/labextensions.py @@ -308,9 +308,9 @@ class WatchLabExtensionApp(BaseExtensionApp): ) aliases = { - "development": "BuildLabExtensionApp.development", - "source-map": "BuildLabExtensionApp.source_map", - "core-path": "BuildLabExtensionApp.core_path", + "core-path": "WatchLabExtensionApp.core_path", + "development": "WatchLabExtensionApp.development", + "source-map": "WatchLabExtensionApp.source_map", } def run_task(self): From a2b5e714eb05550a5ab082cba8ca100d9a7f3276 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 20 Dec 2023 09:54:15 +0100 Subject: [PATCH 072/147] Fix `OSTYPE` check in `ci_install.sh` (#11801) * Fix `OSTYPE` check in `ci_install.sh` * debug * Update publish.ts * Update ci_install.sh * Replace `yarn` with `jlpm`? --- buildutils/src/local-repository.ts | 30 +++++++++++++++--------------- scripts/ci_install.sh | 3 +-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/buildutils/src/local-repository.ts b/buildutils/src/local-repository.ts index 928b88693fe6..e770767ca087 100644 --- a/buildutils/src/local-repository.ts +++ b/buildutils/src/local-repository.ts @@ -32,10 +32,10 @@ async function startLocalRegistry(out_dir: string, port = DEFAULT_PORT) { // Get current registry values let prev_npm = utils.run('npm config get registry', { stdio: 'pipe' }, true); - let prev_yarn = ''; + let prev_jlpm = ''; try { - prev_yarn = utils.run( - 'yarn config get npmRegistryServer', + prev_jlpm = utils.run( + 'jlpm config get npmRegistryServer', { stdio: 'pipe' }, true ); @@ -45,8 +45,8 @@ async function startLocalRegistry(out_dir: string, port = DEFAULT_PORT) { if (!prev_npm || prev_npm.indexOf('0.0.0.0') !== -1) { prev_npm = 'https://registry.npmjs.org/'; } - if (prev_yarn.indexOf('0.0.0.0') !== -1) { - prev_yarn = ''; + if (prev_jlpm.indexOf('0.0.0.0') !== -1) { + prev_jlpm = ''; } // write the config file @@ -126,7 +126,7 @@ packages: const info_file = path.join(out_dir, 'info.json'); const data = { prev_npm, - prev_yarn, + prev_jlpm, pid: subproc.pid }; utils.writeJSONFile(info_file, data); @@ -140,13 +140,13 @@ packages: local_registry ]); try { - child_process.execFileSync('yarn', [ + child_process.execFileSync('jlpm', [ 'config', 'set', 'npmRegistryServer', local_registry ]); - child_process.execFileSync('yarn', [ + child_process.execFileSync('jlpm', [ 'config', 'set', 'unsafeHttpWhitelist', @@ -154,7 +154,7 @@ packages: '["0.0.0.0"]' ]); } catch (e) { - // yarn not available + // jlpm not available } // Log in using cli and temp credentials @@ -238,17 +238,17 @@ async function stopLocalRegistry(out_dir: string) { } else { child_process.execSync(`npm config rm registry`); } - if (data.prev_yarn) { + if (data.prev_jlpm) { child_process.execSync( - `yarn config set npmRegistryServer ${data.prev_yarn}` + `jlpm config set npmRegistryServer ${data.prev_jlpm}` ); - child_process.execSync(`yarn config unset unsafeHttpWhitelist`); + child_process.execSync(`jlpm config unset unsafeHttpWhitelist`); } else { try { - child_process.execSync(`yarn config unset npmRegistryServer`); - child_process.execSync(`yarn config unset unsafeHttpWhitelist`); + child_process.execSync(`jlpm config unset npmRegistryServer`); + child_process.execSync(`jlpm config unset unsafeHttpWhitelist`); } catch (e) { - // yarn not available + // jlpm not available } } } diff --git a/scripts/ci_install.sh b/scripts/ci_install.sh index e53e1943fd6e..d0216f572311 100755 --- a/scripts/ci_install.sh +++ b/scripts/ci_install.sh @@ -14,7 +14,7 @@ export YARN_ENABLE_INLINE_BUILDS=1 # Building should work without yarn installed globally, so uninstall the # global yarn installed by default. -if [ $OSTYPE == "Linux" ]; then +if [ $OSTYPE == "linux-gnu" ]; then sudo rm -rf $(which yarn) ! yarn fi @@ -31,7 +31,6 @@ pip install -q --upgrade pip --user pip --version # Show a verbose install if the install fails, for debugging pip install -e ".[dev,test]" || pip install -v -e ".[dev,test]" -yarn --version node -p process.versions jlpm config From 6a86d8f163fe11cf7b1964b1ed54999ee2234c8c Mon Sep 17 00:00:00 2001 From: Afshin Taylor Darian Date: Wed, 20 Dec 2023 13:58:25 +0000 Subject: [PATCH 073/147] Add virtual scrollbar component to windowed lists. (#15533) * Add virtual scrollbar component to windowed lists. * Revert snapshot changes * Fix scroll past end * Fix scrollbar incorrectly appearing on blur * Reimplement virtual scrollbar positioning This ensures that overlay scrollbar does not show up, and when normal scrollbars are shown that the outer one is hidden, while also ensuring that toggling the virtual scrollbar restores the original styles. * Disable by virtual scrolling by default * Hold a reference to scrollbar element for convenience and for fewer query selector invocations * Use `jp-WindowedPanel-outer` in galata which now functions as the scroll container (rather than `jp-Notebook` as before) * Update extension migration to note changes caught by tests * Update Mermaid snapshots and command list --------- Co-authored-by: krassowski <5832902+krassowski@users.noreply.github.com> --- docs/source/developer/performance.rst | 2 +- docs/source/extension/extension_migration.rst | 5 + examples/notebook/src/index.ts | 7 +- galata/src/helpers/notebook.ts | 41 ++- .../commandsList-documentation-linux.json | 6 + ...un-cells-dark-mermaid-jupyterlab-linux.png | Bin 21019 -> 14827 bytes .../run-cells-mermaid-jupyterlab-linux.png | Bin 21070 -> 14894 bytes .../test/jupyterlab/notebook-scroll.test.ts | 11 +- .../test/jupyterlab/windowed-notebook.test.ts | 2 +- packages/lsp/src/adapters/tracker.ts | 1 - packages/notebook-extension/schema/panel.json | 7 +- packages/notebook-extension/src/index.ts | 53 ++- packages/notebook/src/searchprovider.ts | 2 +- packages/notebook/src/widget.ts | 53 ++- packages/notebook/src/windowing.ts | 4 +- packages/notebook/style/base.css | 24 +- packages/pluginmanager-extension/package.json | 1 - .../ui-components/src/components/toolbar.tsx | 32 ++ .../src/components/windowedlist.ts | 323 ++++++++++++++++-- packages/ui-components/style/windowedlist.css | 40 ++- 20 files changed, 537 insertions(+), 77 deletions(-) diff --git a/docs/source/developer/performance.rst b/docs/source/developer/performance.rst index 9476418b1382..dda70d326bad 100644 --- a/docs/source/developer/performance.rst +++ b/docs/source/developer/performance.rst @@ -15,7 +15,7 @@ This widget will have a DOM structure like this: