enable find in cell output when output is a data stream (#17759)

* initial changes

* add isCodeOutput and apply decorations on output

* Add extension gallery update reminder action (#17644)

* Fix extensionsGallery.json name (#17646)

* Remove galleries list action (#17648)

* multi-level table support (#17638)

* multi-level table support

* comments

* address comments

* add period to end of sentence.

* Show connection string example for add sql binding quickpick (#17650)

* add connection string example

* reorder

* Resolves same-origin-policy violation when ADS web is running in a container (#17555)

* Stops appending port to authority for web mode

* Clarifies comment

* Adds missing sql carbon edit tag

* vbump STS (#17653)

* use latest STS (#17658)

* Refactoring readProjFile() (#17637)

* move reading project parts to different helper functions

* cleanup

* remove comment

* addressing comments

* Corrects Web Build Pipeline ENOENT Error (#17656)

* Checks for successful directory creation

* Revert "Checks for successful directory creation"

This reverts commit 372409ef323f0d82e11992bc7bc33d607a7d5581.

* Checks for the existence of the logs directory before accessing.

* Adds SQL carbon edit comment

* Removing call to copy from non-existing directory.

* Removes unneeded import

* Checks for file existence before copying.

* Provides explanation for modification

* Replaces file existence check with exception handling.

* Bump tools service (#17671)

* Apply changes from Remote Database to sqlproj - mssql changes (#17655)

* update project from database

* update project from database

* update project from database

* Re-adding schemaComparePublishChanges for temporary backcompat

* Adding comment for keeping enum values in sync

* Correcting enum value

Co-authored-by: Noureldine Yehia <t-nyehia@microsoft.com>

* update add file/folder for msbuild sdk style projects (#17660)

* update add file for msbuild sdk style projects

* also handle add folder

* fix comment

* fix issue reported by component governance (#17678)

* update json-schema version

* remove unused packages

* update package.json

* SQL Binding: Give default connection setting name (#17659)

* show sqlconnectionstring in quickpick

* fix duplicate sqlconnectionstring setting

* add (new)

* add sqlconnectionstring as default setting name

* check if sqlconnectionstring already exists

* Provide aria-labels for node checkboxes in the tree view. (#17676)

During accessibility testing, it was discovered that screen reader does not announce what checkboxes in the tree view represent. It was merely announcing "checkbox unchecked", so it was not clear without visuals which checkbox the focus is on.

This change sets an `aria-label` of the checkbox elements to match the label of the owning tree node. This way the announcement becomes "My Node; checkbox; unchecked". This is fine as a quick solution to the problem, but in the future we may want to consider adding additional checkbox label property to the nodes exposed by the tree provider, so that each checkbox can announce additional information, if needed.

* Respect ARIA label specified int he tree component options. (#17674)

During accessibility testing it was discovered that tree view in our wizard reads "Tree Node tree view" instead of the proper label that is specified. It turned out to be the problem with the tree component, where `ariaLabel` was hardcoded to "Tree Node", instead of the one provided in the options.

This change addresses the problem by passing through `ariaLabel` from the options object to the underlying tree control. I also removed the default `Tree Node` hardcoded label, as it didn't make much sense. This does mean that all tree-views that do not explicitly specify their aria-label will now get an empty label. I think this is better than having unrelated, unlocalized `Tree Node`.

I'm also worried about changes to the `ariaLabel` property after the component was initialized. I updated the code to propagate the value to the underlying tree view in the `setProperties` override of the tree component and hope that it will take care of it.

* Fix sql projects net6 warnings (#17673)

* fix .net 6 error showing on startup

* fix double warning

* addressing comments

* update key string

* undo adding space in net core sdk location setting (#17684)

* update names for msbuild sdk style projects (#17677)

* update names for msbuild sdk style projects

* remove msbuild from names

* update comments

* Designer: property descriptions (#17668)

* format

* added strings

* format doc

* use codicon instead

* show descriptions in property pane only

* fix ssdt string bug

* fix overflow option

* review comments

* review comments

* changes

* sts 156 vbump (#17683)

* Sql Binding: Add "Check out pane for more details" when nuget package download fails (#17680)

* check output for more details

* detail error

* wait for result from showerrormessage

* [Loc] Update to sql-database-projects and sql.xlf (#17687)

* update the vmImage for build jobs (#17689)

* update vmImage for windows build

* update tar command

* use specific macos version

* support building msbuild sdk style projects (#17675)

* support building msbuild sdk style projects

* fixes after merge

* add foreign keys and constraints (#17697)

* foreign keys and constraints

* refactoring

* fix issues

* properties pane improvements (#17700)

* [Loc] update to tabledesignercomponentinput (#17704)

* Add instructions for developing VS Code version of sql-database-projects (#17705)

* Adding UI for deploying a db proj to docker (#17495)

* code refactoring (#17706)

* Improve accessibility for wizard steps navigation (#17669)

Our extension is relying on the wizard dialog. During accessibility testing it was discovered that wizard step buttons are being reported as links by the screen reader (NVDA, JAWS). Claimed expected behavior by the tester is that they should be announced as buttons.

I discussed this issue with accessibility SMEs and they said it is perfectly fine to keep them as links. They did mention that they would probably design the UX differently from the start, but given that we already have it this way, links are fine. They did suggest to add few additional ARIA attributes to the link elements:

- `aria-current="step"` if the link is for the currently active step. This literally just announces "current step" at the end, when you focus on a link
- `aria-disabled="true"` makes it say "**unavailable**; link; *<step number>*" when in NVDA "browse" mode and move to the grayed-out link.

So this change implements the said improvements.

* bump sts version to 159 (#17709)

* LEGO: check in for main to temporary branch. (#17699)

* LEGO: check in for main to temporary branch. (#17702)

Co-authored-by: Alex Ma <alma1@microsoft.com>

* [Loc] update to sql-database-projects (#17713)

* LEGO: check in for main to temporary branch. (#17715)

* Register additional editor overrides when adding new notebook file types (#17708)

* Also standardized file extension contributions to always start with a period, and to always do lower case string comparisons for file extensions.

* input width in designer (#17714)

* Add additional properties to wizard page navigation events (#17716)

* Fix open external not working (#17717)

* save password checkbox fix (#17718)

* save password checkbox fix

* remove code to reset the checkbox value

* allow build to continue when cache task fails (#17720)

* cache task should not fail the build

* update cachesalt

* Fix language flavor change on connection when in sqlcmd mode (#17719)

* Fix language flavor change on connection when in sqlcmd mode

* comment + fix

* LEGO: check in for main to temporary branch. (#17722)

* LEGO: check in for main to temporary branch. (#17724)

Co-authored-by: Alex Ma <alma1@microsoft.com>

* Update one more variable name for sdk style projects (#17710)

* LEGO: check in for main to temporary branch. (#17725)

Co-authored-by: Alex Ma <alma1@microsoft.com>

* LEGO: check in for main to temporary branch. (#17726)

Co-authored-by: Alex Ma <alma1@microsoft.com>

* LEGO: check in for main to temporary branch. (#17730)

* Fix "unsupported version" error when adding sql binding package (#17721)

* Apply changes from remote database to sqlproj - schema-compare changes (#17679)

* update project from database

* update project from database

* Merge from main

* Removing dupe test stub

* PR feedback

* cleanup

* PR feedback

* Fixing tests, adding stubs to update sqlproj as schema compare target

* updating code comment

Co-authored-by: Noureldine Yehia <t-nyehia@microsoft.com>

* [Loc] Update to schema-compare XLF (#17733)

* Enabled deployment of Azure Arc data controllers in directly connected mode (#17707)

* Added fields for connectivity mode, custom loc, auto-metrics, auto-logs, and the dynamic enablement of such fields.

* Changed the description of the data controller details page.

* Change notebook params to work for direct mode

* Added login to dc deployment notebooks

* Fixed auto upload metrics and logs true/false, separated login into another cell.

* Removed localization of indirect and direct connectivity labels.

* Fix ordering of reading sqlproj Build Includes and Removes (#17712)

* evaluate includes and removes in order in sqlproj

* fix after merge

* fix comment

* update comment

* Add resource deployment samples for (#17734)

* Update remove file for sdk style sql projects (#17688)

* add support for removing file in new style project

* fix test

* only load files, not whole project when checking if a <Build Remove> needs to be added

* merge changes

* fixes after merge

* [Loc] update to Arc XLF (#17737)

* Bump to latest version of azdata (#17735)

* Remove .net 6 version cutoff for building sql projects (#17736)

* remove .net 6 version cutoff for building sql projects

* Revert "Warning when .NET 6 SDK is detected (#17422)"

This reverts commit 2ed8aeb565.

* add back skipVersionSupportedCheck

* add back return false

* addressing comments

* [Loc] update to sql-database-projects xlf (#17743)

* Add additional notebook tests for handling relative links. (#17739)

* [Loc] Fix for duplicate strings in LCL files (#17756)

* WIP spanish lcl duplicate removal

* Revert "WIP spanish lcl duplicate removal"

This reverts commit 5f943153ec8980849a045c8bf7256d852571a778.

* fix for duplicate strings

* removed spaces

* Fixes ADS Web bug around copying user codes and opening a browser tab when adding an Azure Account. (#17760)

* Fixes bug around copying user codes and opening a browser tab.

* Code review changes

* Additional review changes.

* Unnecessary import removed

* Editing pipeline ACR service connection endpoint to the latest one created  (#17767)

* using the new registry endpoint

* updating the service connection

* Changing the name of acr service endpoint to SqlToolsContainer

* Updating web build acr endpoint as well.

* Notebooks: Add Tooltips for Link/Image Buttons on Markdown Toolbar (#17763)

* Add title for tooltips

* Tweak tooltips

* Added UI for user to accept EULA when deploying sql proj to docker container (#17762)

* fix sorting bug (#17769)

* fix sorting bug

* comments

* Set default radio button selection, evaluate default component popula… (#17764)

* Set default radio button selection, evaluate default component population off of selection instead of unprocessed input

* rename var

* if -> switches

* Remove project radio buttons because they require commands that aren't yet checked in (and fail as a result)

* Added fix for Publish Target Label Position (#17771)

* Added fix for Publish Target Label Position

* renamed checkbox

* LEGO: check in for main to temporary branch. (#17775)

* LEGO: check in for main to temporary branch. (#17778)

Co-authored-by: Alex Ma <alma1@microsoft.com>

* update dashboard taskbar separator (#17779)

* remove comments

* fix applying decorations

* add test

* enable find in sql result set

* add tests and update sql results highlight logic

* calculate the outputComponent index

* fix editor issues later

* remove newline replace on search

* address comments

* fix highlight issue

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
Co-authored-by: Alan Ren <alanren@microsoft.com>
Co-authored-by: Lucy Zhang <luczhan@microsoft.com>
Co-authored-by: Lewis Sanchez <87730006+lewis-sanchez@users.noreply.github.com>
Co-authored-by: Kim Santiago <31145923+kisantia@users.noreply.github.com>
Co-authored-by: Benjin Dubishar <benjin.dubishar@gmail.com>
Co-authored-by: Noureldine Yehia <t-nyehia@microsoft.com>
Co-authored-by: Alexander Ivanov <nahk-ivanov@users.noreply.github.com>
Co-authored-by: Aditya Bist <adbist@microsoft.com>
Co-authored-by: Sai Avishkar Sreerama <74571829+ssreerama@users.noreply.github.com>
Co-authored-by: Alex Ma <alma1@microsoft.com>
Co-authored-by: Leila Lali <llali@microsoft.com>
Co-authored-by: csigs <csigs@users.noreply.github.com>
Co-authored-by: Cory Rivera <corivera@microsoft.com>
Co-authored-by: Candice Ye <candiceye@berkeley.edu>
Co-authored-by: Aasim Khan <aasimkhan30@gmail.com>
Co-authored-by: Chris LaFreniere <40371649+chlafreniere@users.noreply.github.com>
This commit is contained in:
Maddy
2021-12-21 11:56:33 -08:00
committed by GitHub
parent 443257e699
commit 33ba586475
15 changed files with 592 additions and 188 deletions

View File

@@ -91,7 +91,6 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
private _editor: QueryTextEditor;
private _editorInput: UntitledTextEditorInput;
private _editorModel: ITextModel;
private _model: NotebookModel;
private _activeCellId: string;
private _layoutEmitter = new Emitter<void>();

View File

@@ -4,14 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import { nb } from 'azdata';
import { OnInit, Component, Input, Inject, forwardRef, ChangeDetectorRef, SimpleChange, OnChanges, HostListener, ViewChildren, QueryList } from '@angular/core';
import { OnInit, Component, Input, Inject, forwardRef, ChangeDetectorRef, SimpleChange, OnChanges, HostListener, ViewChildren, QueryList, ViewChild } from '@angular/core';
import { CellView } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces';
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
import { Deferred } from 'sql/base/common/promise';
import { ICellEditorProvider } from 'sql/workbench/services/notebook/browser/notebookService';
import { CodeComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/code.component';
import { OutputComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/output.component';
import { OutputAreaComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/outputArea.component';
export const CODE_SELECTOR: string = 'code-cell-component';
@@ -23,7 +23,7 @@ export const CODE_SELECTOR: string = 'code-cell-component';
export class CodeCellComponent extends CellView implements OnInit, OnChanges {
@ViewChildren(CodeComponent) private codeCells: QueryList<ICellEditorProvider>;
@ViewChildren(OutputComponent) private outputCells: QueryList<ICellEditorProvider>;
@ViewChild(OutputAreaComponent) private outputAreaCell: OutputAreaComponent;
@Input() cellModel: ICellModel;
@Input() set model(value: NotebookModel) {
this._model = value;
@@ -38,7 +38,6 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
this._model.updateActiveCell(undefined);
}
private _model: NotebookModel;
private _activeCellId: string;
public inputDeferred: Deferred<string>;
@@ -82,8 +81,8 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
if (this.codeCells) {
editors.push(...this.codeCells.toArray());
}
if (this.outputCells) {
editors.push(...this.outputCells.toArray());
if (this.outputAreaCell) {
editors.push(...this.outputAreaCell.cellEditors);
}
return editors;
}

View File

@@ -3,15 +3,30 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OnDestroy } from '@angular/core';
import { OnDestroy, ElementRef } from '@angular/core';
import * as Mark from 'mark.js';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { ICellEditorProvider, NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService';
import { ICellEditorProvider, INotebookService, NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService';
import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
import { nb } from 'azdata';
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
export const findHighlightClass = 'rangeHighlight';
export const findRangeSpecificClass = 'rangeSpecificHighlight';
export abstract class CellView extends AngularDisposable implements OnDestroy, ICellEditorProvider {
protected isFindActive: boolean = false;
protected highlightRange: NotebookRange;
protected output: ElementRef;
protected notebookService: INotebookService;
protected _model: NotebookModel;
isCellOutput: boolean = false;
protected searchTerm: string;
constructor() {
super();
}
@@ -29,8 +44,122 @@ export abstract class CellView extends AngularDisposable implements OnDestroy, I
public abstract cellGuid(): string;
public deltaDecorations(newDecorationsRange: NotebookRange | NotebookRange[], oldDecorationsRange: NotebookRange | NotebookRange[]): void {
if (newDecorationsRange) {
this.isFindActive = true;
if (Array.isArray(newDecorationsRange)) {
this.highlightAllMatches();
} else {
this.highlightRange = newDecorationsRange;
this.addDecoration(newDecorationsRange);
}
}
if (oldDecorationsRange) {
if (Array.isArray(oldDecorationsRange)) {
this.removeDecoration();
this.isFindActive = false;
} else {
this.highlightRange = oldDecorationsRange === this.highlightRange ? undefined : this.highlightRange;
this.removeDecoration(oldDecorationsRange);
}
}
}
protected addDecoration(range?: NotebookRange): void {
range = range ?? this.highlightRange;
if (this.output && this.output.nativeElement) {
this.highlightAllMatches();
if (range) {
let elements = this.getHtmlElements();
if (elements?.length >= range.startLineNumber) {
let elementContainingText = elements[range.startLineNumber - 1];
let markCurrent = new Mark(elementContainingText); // to highlight the current item of them all.
markCurrent.markRanges([{
start: range.startColumn - 1, //subtracting 1 since markdown html is 0 indexed.
length: range.endColumn - range.startColumn
}], {
className: findRangeSpecificClass,
each: function (node, range) {
// node is the marked DOM element
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}
}
}
}
protected highlightAllMatches(): void {
if (this.output && this.output.nativeElement) {
let markAllOccurances = new Mark(this.output.nativeElement); // to highlight all occurances in the element.
let editor = this.notebookService.findNotebookEditor(this._model.notebookUri);
if (editor) {
let findModel = (editor.notebookParams.input as NotebookInput).notebookFindModel;
if (findModel?.findMatches?.length > 0) {
let searchString = findModel.findExpression;
markAllOccurances.mark(searchString, {
className: findHighlightClass
});
}
}
}
}
protected removeDecoration(range?: NotebookRange): void {
if (this.output && this.output.nativeElement) {
if (range) {
let elements = this.getHtmlElements();
let elementContainingText = elements[range.startLineNumber - 1];
let markCurrent = new Mark(elementContainingText);
markCurrent.unmark({ acrossElements: true, className: findRangeSpecificClass });
} else {
let markAllOccurances = new Mark(this.output.nativeElement);
markAllOccurances.unmark({ acrossElements: true, className: findHighlightClass });
markAllOccurances.unmark({ acrossElements: true, className: findRangeSpecificClass });
this.highlightRange = undefined;
}
}
}
protected getHtmlElements(): any[] {
let hostElem = this.output?.nativeElement;
let children = [];
if (hostElem) {
for (let element of hostElem.children) {
if (element.nodeName.toLowerCase() === 'table') {
// add table header and table rows.
if (element.children.length > 0) {
children.push(element.children[0]);
if (element.children.length > 1) {
for (let trow of element.children[1].children) {
children.push(trow);
}
}
}
} else if (element.children.length > 1) {
children = children.concat(this.getChildren(element));
} else {
children.push(element);
}
}
}
return children;
}
protected getChildren(parent: any): any[] {
let children: any = [];
if (parent.children.length > 1 && parent.nodeName.toLowerCase() !== 'li' && parent.nodeName.toLowerCase() !== 'p') {
for (let child of parent.children) {
children = children.concat(this.getChildren(child));
}
} else {
return [parent];
}
return children;
}
}
export interface IMarkdownStringWithCellAttachments extends IMarkdownString {

View File

@@ -6,6 +6,7 @@ import 'vs/css!./code';
import 'vs/css!./media/output';
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, SimpleChange, AfterViewInit, forwardRef, ChangeDetectorRef, ComponentRef, ComponentFactoryResolver } from '@angular/core';
import * as Mark from 'mark.js';
import { Event } from 'vs/base/common/event';
import { nb } from 'azdata';
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
@@ -19,11 +20,13 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { localize } from 'vs/nls';
import * as types from 'vs/base/common/types';
import { getErrorMessage } from 'vs/base/common/errors';
import { CellView } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces';
import { CellView, findHighlightClass, findRangeSpecificClass } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces';
import { INotebookService, NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService';
import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
export const OUTPUT_SELECTOR: string = 'output-component';
const USER_SELECT_CLASS = 'actionselect';
const GRID_CLASS = '[class="grid-canvas"]';
const componentRegistry = <IMimeComponentRegistry>Registry.as(Extensions.MimeComponentContribution);
@Component({
@@ -31,7 +34,7 @@ const componentRegistry = <IMimeComponentRegistry>Registry.as(Extensions.MimeCom
templateUrl: decodeURI(require.toUrl('./output.component.html'))
})
export class OutputComponent extends CellView implements OnInit, AfterViewInit {
@ViewChild('output', { read: ElementRef }) private outputElement: ElementRef;
@ViewChild('output', { read: ElementRef }) override output: ElementRef;
@ViewChild(ComponentHostDirective) componentHost: ComponentHostDirective;
@Input() cellOutput: nb.ICellOutput;
@Input() cellModel: ICellModel;
@@ -46,7 +49,8 @@ export class OutputComponent extends CellView implements OnInit, AfterViewInit {
@Inject(IThemeService) private _themeService: IThemeService,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeref: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _ref: ElementRef,
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver,
@Inject(INotebookService) override notebookService: INotebookService
) {
super();
}
@@ -84,7 +88,7 @@ export class OutputComponent extends CellView implements OnInit, AfterViewInit {
}
private get nativeOutputElement() {
return this.outputElement ? this.outputElement.nativeElement : undefined;
return this.output ? this.output.nativeElement : undefined;
}
public layout(): void {
@@ -187,4 +191,117 @@ export class OutputComponent extends CellView implements OnInit, AfterViewInit {
public cellGuid(): string {
return this.cellModel.cellGuid;
}
override isCellOutput = true;
getCellModel(): ICellModel {
return this.cellModel;
}
protected override addDecoration(range?: NotebookRange): void {
range = range ?? this.highlightRange;
if (this.output && this.output.nativeElement) {
this.highlightAllMatches();
if (range) {
let elements = this.getHtmlElements();
if (elements.length === 1 && elements[0].nodeName === 'MIME-OUTPUT') {
let markCurrent = new Mark(elements[0]);
markCurrent.markRanges([{
start: range.startColumn - 1, //subtracting 1 since markdown html is 0 indexed.
length: range.endColumn - range.startColumn
}], {
className: findRangeSpecificClass,
each: function (node, range) {
// node is the marked DOM element
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
} else if (elements?.length >= range.startLineNumber) {
let elementContainingText = elements[range.startLineNumber - 1];
let markCurrent = new Mark(elementContainingText); // to highlight the current item of them all.
if (elementContainingText.children.length > 0) {
markCurrent = new Mark(elementContainingText.children[range.startColumn]);
markCurrent?.mark(this.searchTerm, {
className: findRangeSpecificClass,
each: function (node, range) {
// node is the marked DOM element
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}
}
}
}
}
protected override highlightAllMatches(): void {
if (this.output && this.output.nativeElement) {
let markAllOccurances = new Mark(this.output.nativeElement); // to highlight all occurances in the element.
if (!this._model) {
this._model = this.getCellModel().notebookModel;
}
let editor = this.notebookService.findNotebookEditor(this._model?.notebookUri);
if (editor) {
let findModel = (editor.notebookParams.input as NotebookInput).notebookFindModel;
if (findModel?.findMatches?.length > 0) {
this.searchTerm = findModel.findExpression;
markAllOccurances.mark(this.searchTerm, {
className: findHighlightClass,
separateWordSearch: true,
});
// if there are grids
let grids = document.querySelectorAll(GRID_CLASS);
grids?.forEach(g => {
markAllOccurances = new Mark(g);
markAllOccurances.mark(this.searchTerm, {
className: findHighlightClass
});
});
}
}
}
}
protected override removeDecoration(range?: NotebookRange): void {
if (this.output && this.output.nativeElement) {
if (range) {
let elements = this.getHtmlElements();
let elementContainingText = elements[range.startLineNumber - 1];
if (elements.length === 1 && elements[0].nodeName === 'MIME-OUTPUT') {
elementContainingText = elements[0];
}
let markCurrent = new Mark(elementContainingText);
markCurrent.unmark({ acrossElements: true, className: findRangeSpecificClass });
} else {
let markAllOccurances = new Mark(this.output.nativeElement);
markAllOccurances.unmark({ acrossElements: true, className: findHighlightClass });
markAllOccurances.unmark({ acrossElements: true, className: findRangeSpecificClass });
this.highlightRange = undefined;
// if there is a grid
let grids = document.querySelectorAll(GRID_CLASS);
grids?.forEach(g => {
markAllOccurances = new Mark(g);
markAllOccurances.unmark({ acrossElements: true, className: findHighlightClass });
markAllOccurances.unmark({ acrossElements: true, className: findRangeSpecificClass });
});
}
}
}
protected override getHtmlElements(): any[] {
let children = [];
let slickGrids = this.output.nativeElement.querySelectorAll(GRID_CLASS);
if (slickGrids.length > 0) {
slickGrids.forEach(grid => {
children.push(...grid.children);
});
} else {
// if the decoration range belongs to code cell output and output is a stream of data
// it's in <mime-output> tag of the output.
let outputMessages = this.output.nativeElement.querySelectorAll('mime-output');
children.push(...outputMessages);
}
return children;
}
}

View File

@@ -4,13 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./code';
import 'vs/css!./outputArea';
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, forwardRef, ChangeDetectorRef } from '@angular/core';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, forwardRef, ChangeDetectorRef, ViewChildren, QueryList } from '@angular/core';
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import * as themeColors from 'vs/workbench/common/theme';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { URI } from 'vs/base/common/uri';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
import { CellView } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces';
import { OutputComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/output.component';
import { ICellEditorProvider } from 'sql/workbench/services/notebook/browser/notebookService';
export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
@@ -18,8 +20,10 @@ export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
selector: OUTPUT_AREA_SELECTOR,
templateUrl: decodeURI(require.toUrl('./outputArea.component.html'))
})
export class OutputAreaComponent extends AngularDisposable implements OnInit {
export class OutputAreaComponent extends CellView implements OnInit {
@ViewChild('outputarea', { read: ElementRef }) private outputArea: ElementRef;
@ViewChildren(OutputComponent) private outputCells: QueryList<ICellEditorProvider>;
@Input() cellModel: ICellModel;
private _activeCellId: string;
@@ -75,4 +79,19 @@ export class OutputAreaComponent extends AngularDisposable implements OnInit {
let outputElement = <HTMLElement>this.outputArea.nativeElement;
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
public cellGuid(): string {
return this.cellModel.cellGuid;
}
public layout() {
}
public get cellEditors(): ICellEditorProvider[] {
let editors: ICellEditorProvider[] = [];
if (this.outputCells) {
editors.push(...this.outputCells.toArray());
}
return editors;
}
}

View File

@@ -25,8 +25,6 @@ export class PlaceholderCellComponent extends CellView implements OnInit, OnChan
this._model = value;
}
private _model: NotebookModel;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
) {

View File

@@ -8,7 +8,6 @@ import 'vs/css!./media/highlight';
import * as DOM from 'vs/base/browser/dom';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnChanges, SimpleChange, HostListener, ViewChildren, QueryList } from '@angular/core';
import * as Mark from 'mark.js';
import { localize } from 'vs/nls';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
@@ -27,23 +26,21 @@ import { ICaretPosition, CellEditModes, ICellModel } from 'sql/workbench/service
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
import { ISanitizer, defaultSanitizer } from 'sql/workbench/services/notebook/browser/outputs/sanitizer';
import { CodeComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/code.component';
import { NotebookRange, ICellEditorProvider, INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { ICellEditorProvider, INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { HTMLMarkdownConverter } from 'sql/workbench/contrib/notebook/browser/htmlMarkdownConverter';
import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
import { highlightSelectedText } from 'sql/workbench/contrib/notebook/browser/utils';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
export const TEXT_SELECTOR: string = 'text-cell-component';
const USER_SELECT_CLASS = 'actionselect';
const findHighlightClass = 'rangeHighlight';
const findRangeSpecificClass = 'rangeSpecificHighlight';
@Component({
selector: TEXT_SELECTOR,
templateUrl: decodeURI(require.toUrl('./textCell.component.html'))
})
export class TextCellComponent extends CellView implements OnInit, OnChanges {
@ViewChild('preview', { read: ElementRef }) private output: ElementRef;
@ViewChild('preview', { read: ElementRef }) override output: ElementRef;
@ViewChildren(CodeComponent) private markdowncodeCell: QueryList<CodeComponent>;
@Input() cellModel: ICellModel;
@@ -115,7 +112,6 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
private _previewMode: boolean = true;
private _markdownMode: boolean;
private _sanitizer: ISanitizer;
private _model: NotebookModel;
private _activeCellId: string;
private readonly _onDidClickLink = this._register(new Emitter<URI>());
private markdownRenderer: NotebookMarkdownRenderer;
@@ -125,8 +121,6 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
public readonly onDidClickLink = this._onDidClickLink.event;
public previewFeaturesEnabled: boolean = false;
public doubleClickEditEnabled: boolean;
private _highlightRange: NotebookRange;
private _isFindActive: boolean = false;
private _editorHeight: number;
private readonly _markdownMaxHeight = 4000;
@@ -138,7 +132,7 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IConfigurationService) private _configurationService: IConfigurationService,
@Inject(INotebookService) private _notebookService: INotebookService
@Inject(INotebookService) override notebookService: INotebookService
) {
super();
this.markdownRenderer = this._instantiationService.createInstance(NotebookMarkdownRenderer);
@@ -342,7 +336,7 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
outputElement.style.lineHeight = this.markdownPreviewLineHeight.toString();
this.cellModel.renderedOutputTextContent = this.getRenderedTextOutput();
outputElement.focus();
if (this._isFindActive) {
if (this.isFindActive) {
this.addDecoration();
}
}
@@ -545,121 +539,6 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
return this.cellModel && this.cellModel.id === this.activeCellId;
}
public override deltaDecorations(newDecorationsRange: NotebookRange | NotebookRange[], oldDecorationsRange: NotebookRange | NotebookRange[]): void {
if (newDecorationsRange) {
this._isFindActive = true;
if (Array.isArray(newDecorationsRange)) {
this.highlightAllMatches();
} else {
this._highlightRange = newDecorationsRange;
this.addDecoration(newDecorationsRange);
}
}
if (oldDecorationsRange) {
if (Array.isArray(oldDecorationsRange)) {
this.removeDecoration();
this._isFindActive = false;
} else {
this._highlightRange = oldDecorationsRange === this._highlightRange ? undefined : this._highlightRange;
this.removeDecoration(oldDecorationsRange);
}
}
}
private addDecoration(range?: NotebookRange): void {
range = range ?? this._highlightRange;
if (this.output && this.output.nativeElement) {
this.highlightAllMatches();
if (range) {
let elements = this.getHtmlElements();
if (elements?.length >= range.startLineNumber) {
let elementContainingText = elements[range.startLineNumber - 1];
let markCurrent = new Mark(elementContainingText); // to highlight the current item of them all.
markCurrent.markRanges([{
start: range.startColumn - 1, //subtracting 1 since markdown html is 0 indexed.
length: range.endColumn - range.startColumn
}], {
className: findRangeSpecificClass,
each: function (node, range) {
// node is the marked DOM element
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}
}
}
}
private highlightAllMatches(): void {
if (this.output && this.output.nativeElement) {
let markAllOccurances = new Mark(this.output.nativeElement); // to highlight all occurances in the element.
let editor = this._notebookService.findNotebookEditor(this.model.notebookUri);
if (editor) {
let findModel = (editor.notebookParams.input as NotebookInput).notebookFindModel;
if (findModel?.findMatches?.length > 0) {
let searchString = findModel.findExpression;
markAllOccurances.mark(searchString, {
className: findHighlightClass
});
}
}
}
}
private removeDecoration(range?: NotebookRange): void {
if (this.output && this.output.nativeElement) {
if (range) {
let elements = this.getHtmlElements();
let elementContainingText = elements[range.startLineNumber - 1];
let markCurrent = new Mark(elementContainingText);
markCurrent.unmark({ acrossElements: true, className: findRangeSpecificClass });
} else {
let markAllOccurances = new Mark(this.output.nativeElement);
markAllOccurances.unmark({ acrossElements: true, className: findHighlightClass });
markAllOccurances.unmark({ acrossElements: true, className: findRangeSpecificClass });
this._highlightRange = undefined;
}
}
}
private getHtmlElements(): any[] {
let hostElem = this.output?.nativeElement;
let children = [];
if (hostElem) {
for (let element of hostElem.children) {
if (element.nodeName.toLowerCase() === 'table') {
// add table header and table rows.
if (element.children.length > 0) {
children.push(element.children[0]);
if (element.children.length > 1) {
for (let trow of element.children[1].children) {
children.push(trow);
}
}
}
} else if (element.children.length > 1) {
children = children.concat(this.getChildren(element));
} else {
children.push(element);
}
}
}
return children;
}
private getChildren(parent: any): any[] {
let children: any = [];
if (parent.children.length > 1 && parent.nodeName.toLowerCase() !== 'li' && parent.nodeName.toLowerCase() !== 'p') {
for (let child of parent.children) {
children = children.concat(this.getChildren(child));
}
} else {
return parent;
}
return children;
}
private getRenderedTextOutput(): string[] {
let textOutput: string[] = [];
let elements = this.getHtmlElements();

View File

@@ -52,7 +52,7 @@ export class NotebookFindDecorations implements IDisposable {
}
public getCount(): number {
return this._decorations.length;
return this._findScopeDecorationIds.length;
}
public getFindScope(): NotebookRange | null {
@@ -125,6 +125,16 @@ export class NotebookFindDecorations implements IDisposable {
break;
}
}
if (matchPosition === 0) {
for (let i = 0, len = this._findScopeDecorationIds.length; i < len; i++) {
let range = this._editor.notebookFindModel.getDecorationRange(this._findScopeDecorationIds[i]);
if (nextMatch.equalsRange(range)) {
newCurrentDecorationId = this._findScopeDecorationIds[i];
matchPosition = (i + 1);
break;
}
}
}
}
if (this._highlightedDecorationId !== null || newCurrentDecorationId !== null) {
@@ -164,14 +174,14 @@ export class NotebookFindDecorations implements IDisposable {
private removeLastDecoration(): void {
if (this._currentMatch && this._currentMatch.cell) {
let prevEditor = this._currentMatch.cell.cellType === 'markdown' && !this._currentMatch.isMarkdownSourceCell ? undefined : this._editor.getCellEditor(this._currentMatch.cell.cellGuid);
let prevEditor = (this._currentMatch.cell.cellType === 'markdown' && !this._currentMatch.isMarkdownSourceCell) || this._currentMatch.outputComponentIndex >= 0 ? undefined : this._editor.getCellEditor(this._currentMatch.cell.cellGuid);
if (prevEditor) {
prevEditor.getControl().changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
changeAccessor.removeDecoration(this._rangeHighlightDecorationId);
this._rangeHighlightDecorationId = null;
});
} else {
if (this._currentMatch.cell.cellType === 'markdown') {
if (this._currentMatch.cell.cellType === 'markdown' || this._currentMatch.outputComponentIndex >= 0) {
this._editor.updateDecorations(undefined, this._currentMatch);
}
}
@@ -189,43 +199,53 @@ export class NotebookFindDecorations implements IDisposable {
}
public checkValidEditor(range: NotebookRange): boolean {
return range && range.cell && !!(this._editor.getCellEditor(range.cell.cellGuid)) && (range.cell.cellType === 'code' || range.isMarkdownSourceCell);
return range && range.cell && range.outputComponentIndex === -1 && !!(this._editor.getCellEditor(range.cell.cellGuid)) && (range.cell.cellType === 'code' || range.isMarkdownSourceCell);
}
public set(findMatches: NotebookFindMatch[], findScopes: NotebookRange[] | null): void {
if (findScopes) {
this._editor.updateDecorations(findScopes, undefined);
this._editor.changeDecorations((accessor) => {
let findMatchesOptions = NotebookFindDecorations._FIND_MATCH_NO_OVERVIEW_DECORATION;
// Find matches
let newFindMatchesDecorations: IModelDeltaDecoration[] = new Array<IModelDeltaDecoration>(findMatches.length);
for (let i = 0, len = findMatches.length; i < len; i++) {
newFindMatchesDecorations[i] = {
range: findMatches[i].range,
options: findMatchesOptions
};
}
this._decorations = accessor.deltaDecorations(this._decorations, newFindMatchesDecorations);
// Find scope
if (this._findScopeDecorationIds.length) {
this._findScopeDecorationIds.forEach(findScopeDecorationId => accessor.removeDecoration(findScopeDecorationId));
this._findScopeDecorationIds = [];
}
if (findScopes.length) {
this._findScopeDecorationIds = findScopes.map(findScope => accessor.addDecoration(findScope, NotebookFindDecorations._FIND_SCOPE_DECORATION));
}
let markdownFindScopes = findScopes.filter((c, i, ranges) => {
return ranges.indexOf(ranges.find(t => t.cell.cellGuid === c.cell.cellGuid && (t.cell.cellType === 'markdown' || t.outputComponentIndex >= 0))) === i;
});
this._editor.updateDecorations(markdownFindScopes, undefined);
let codeCellFindScopes = findScopes.filter((c, i, ranges) => {
return ranges.indexOf(ranges.find(t => t.cell.cellGuid === c.cell.cellGuid && t.cell.cellType === 'code' && t.outputComponentIndex === -1)) === i;
});
if (codeCellFindScopes) {
this._editor.changeDecorations((accessor) => {
let findMatchesOptions = NotebookFindDecorations._FIND_MATCH_NO_OVERVIEW_DECORATION;
// filter code cell find matches
findMatches = findMatches.filter(m => m.range.cell.cellType === 'code' && m.range.outputComponentIndex === -1);
let newFindMatchesDecorations: IModelDeltaDecoration[] = new Array<IModelDeltaDecoration>(findMatches.length);
for (let i = 0, len = findMatches.length; i < len; i++) {
newFindMatchesDecorations[i] = {
range: findMatches[i].range,
options: findMatchesOptions
};
}
this._decorations = accessor.deltaDecorations(this._decorations, newFindMatchesDecorations);
// Find scope
if (this._findScopeDecorationIds.length) {
this._findScopeDecorationIds.forEach(findScopeDecorationId => accessor.removeDecoration(findScopeDecorationId));
this._findScopeDecorationIds = [];
}
if (findScopes.length) {
this._findScopeDecorationIds = findScopes.map(findScope => accessor.addDecoration(findScope, NotebookFindDecorations._FIND_SCOPE_DECORATION));
}
});
this.setCodeCellDecorations(findMatches, codeCellFindScopes);
}
this.setCodeCellDecorations(findMatches, findScopes);
}
}
private setCodeCellDecorations(findMatches: NotebookFindMatch[], findScopes: NotebookRange[] | null): void {
//get all code cells which have matches
const codeCellsFindMatches = findScopes.filter((c, i, ranges) => {
return ranges.indexOf(ranges.find(t => t.cell.cellGuid === c.cell.cellGuid && t.cell.cellType === 'code')) === i;
return ranges.indexOf(ranges.find(t => t.cell.cellGuid === c.cell.cellGuid && t.cell.cellType === 'code' && t.outputComponentIndex === -1)) === i;
});
codeCellsFindMatches.forEach(findMatch => {
this._editor.getCellEditor(findMatch.cell.cellGuid)?.getControl().changeDecorations((accessor) => {
@@ -233,7 +253,7 @@ export class NotebookFindDecorations implements IDisposable {
let findMatchesOptions: ModelDecorationOptions = NotebookFindDecorations._RANGE_HIGHLIGHT_DECORATION;
let newOverviewRulerApproximateDecorations: IModelDeltaDecoration[] = [];
let cellFindScopes = findScopes.filter(f => f.cell.cellGuid === findMatch.cell.cellGuid);
let cellFindScopes = findScopes.filter(f => f.cell.cellGuid === findMatch.cell.cellGuid && f.outputComponentIndex === -1);
let findMatchesInCell = findMatches?.filter(m => m.range.cell.cellGuid === findMatch.cell.cellGuid) || [];
let _cellFindScopeDecorationIds: string[] = [];
if (findMatchesInCell.length > 1000) {

View File

@@ -25,6 +25,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ActiveEditorContext } from 'vs/workbench/common/editor';
import { NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService';
import { nb } from 'azdata';
function _normalizeOptions(options: model.IModelDecorationOptions): ModelDecorationOptions {
if (options instanceof ModelDecorationOptions) {
@@ -570,6 +571,66 @@ export class NotebookFindModel extends Disposable implements INotebookFindModel
}
}
}
if (cell.cellType === 'code' && cell.outputs.length > 0) {
// i = output element index.
let i: number = 0;
cell.outputs.forEach(output => {
let findStartResults: number[] = [];
switch (output.output_type) {
case 'stream':
let cellValFormatted = output as nb.IStreamResult;
findStartResults = this.search(cellValFormatted.text.toString(), exp, matchCase, wholeWord, maxMatches - findResults.length);
findStartResults?.forEach(start => {
let range = new NotebookRange(cell, i + 1, start, i + 1, start + exp.length, false, i);
findResults.push(range);
});
i++;
break;
case 'error':
let error = output as nb.IErrorResult;
let errorValue = error.traceback?.length > 0 ? error.traceback.toString() : error.evalue;
findStartResults = this.search(errorValue, exp, matchCase, wholeWord, maxMatches - findResults.length);
findStartResults.forEach(start => {
let range = new NotebookRange(cell, i + 1, start, i + 1, start + exp.length, false, i);
findResults.push(range);
});
i++;
break;
case 'display_data':
let displayValue = output as nb.IDisplayData;
findStartResults = this.search(JSON.parse(JSON.stringify(displayValue.data))['text/html'], exp, matchCase, wholeWord, maxMatches - findResults.length);
findStartResults.forEach(start => {
let range = new NotebookRange(cell, i + 1, start, i + 1, start + exp.length, false, i);
findResults.push(range);
});
i++;
break;
case 'execute_result':
// When result is a table
let executeResult = output as nb.IExecuteResult;
const result = JSON.parse(JSON.stringify(executeResult.data));
const data = result['application/vnd.dataresource+json'].data;
if (data.length > 0) {
for (let row = 0; row < data.length; row++) {
let rowData = data[row];
let j: number = 0;
for (const key in rowData) {
let findStartResults = this.search(rowData[key].toString(), exp, matchCase, wholeWord, maxMatches - findResults.length);
if (findStartResults.length) {
let range = new NotebookRange(cell, row + 1, j + 1, row + 1, j + 1, false, i);
findResults.push(range);
}
j++;
}
}
i++;
}
break;
default: i++;
break;
}
});
}
return findResults;
}

View File

@@ -195,15 +195,23 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
public deltaDecorations(newDecorationsRange: NotebookRange | NotebookRange[], oldDecorationsRange: NotebookRange | NotebookRange[]): void {
if (oldDecorationsRange) {
if (Array.isArray(oldDecorationsRange)) {
// markdown cells
let cells = [...new Set(oldDecorationsRange.map(item => item.cell))].filter(c => c.cellType === 'markdown');
cells.forEach(cell => {
let cellOldDecorations = oldDecorationsRange.filter(r => r.cell === cell);
let cellEditor = this.cellEditors.find(c => c.cellGuid() === cell.cellGuid);
cellEditor.deltaDecorations(undefined, cellOldDecorations);
});
// code cell outputs
let codeCells = [...new Set(oldDecorationsRange.map(item => item.cell))].filter(c => c.cellType === 'code');
codeCells.forEach(cell => {
let cellOldDecorations = oldDecorationsRange.filter(r => r.outputComponentIndex >= 0 && cell.cellGuid === r.cell.cellGuid);
let cellEditors = this.cellEditors.filter(c => c.cellGuid() === cell.cellGuid && c.isCellOutput);
cellEditors.forEach(cellEditor => cellEditor.deltaDecorations(undefined, cellOldDecorations));
});
} else {
if (oldDecorationsRange.cell.cellType === 'markdown') {
let cell = this.cellEditors.find(c => c.cellGuid() === oldDecorationsRange.cell.cellGuid);
if (oldDecorationsRange.cell.cellType === 'markdown' || oldDecorationsRange.outputComponentIndex >= 0) {
let cell = oldDecorationsRange.outputComponentIndex >= 0 ? this.cellEditors.filter(c => c.cellGuid() === oldDecorationsRange.cell.cellGuid && c.isCellOutput)[oldDecorationsRange.outputComponentIndex] : this.cellEditors.find(c => c.cellGuid() === oldDecorationsRange.cell.cellGuid);
cell.deltaDecorations(undefined, oldDecorationsRange);
}
}
@@ -216,9 +224,16 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
let cellEditor = this.cellEditors.find(c => c.cellGuid() === cell.cellGuid);
cellEditor.deltaDecorations(cellNewDecorations, undefined);
});
// code cell outputs
let codeCells = [...new Set(newDecorationsRange.map(item => item.cell))].filter(c => c.cellType === 'code');
codeCells.forEach(cell => {
let cellNewDecorations = newDecorationsRange.filter(r => r.outputComponentIndex >= 0 && cell.cellGuid === r.cell.cellGuid);
let cellEditors = this.cellEditors.filter(c => c.cellGuid() === cell.cellGuid && c.isCellOutput);
cellEditors.forEach(cellEditor => cellEditor.deltaDecorations(cellNewDecorations, undefined));
});
} else {
if (newDecorationsRange.cell.cellType === 'markdown') {
let cell = this.cellEditors.find(c => c.cellGuid() === newDecorationsRange.cell.cellGuid);
if (newDecorationsRange.cell.cellType === 'markdown' || newDecorationsRange.outputComponentIndex >= 0) {
let cell = newDecorationsRange.outputComponentIndex >= 0 ? this.cellEditors.filter(c => c.cellGuid() === newDecorationsRange.cell.cellGuid && c.isCellOutput)[newDecorationsRange.outputComponentIndex] : this.cellEditors.find(c => c.cellGuid() === newDecorationsRange.cell.cellGuid);
cell.deltaDecorations(newDecorationsRange, undefined);
}
}

View File

@@ -84,7 +84,7 @@ export class NotebookEditor extends EditorPane implements IFindNotebookControlle
}
private get _findDecorations(): NotebookFindDecorations {
return this.notebookInput.notebookFindModel.findDecorations;
return this.notebookInput?.notebookFindModel?.findDecorations;
}
public getPosition(): NotebookRange {
@@ -432,9 +432,11 @@ export class NotebookEditor extends EditorPane implements IFindNotebookControlle
public async findNext(): Promise<void> {
try {
const p = await this.notebookFindModel.findNext();
this.setSelection(p);
this._updateFinderMatchState();
this._setCurrentFindMatch(p);
if (p !== this._currentMatch) {
this.setSelection(p);
this._updateFinderMatchState();
this._setCurrentFindMatch(p);
}
} catch (er) {
onUnexpectedError(er);
}
@@ -443,9 +445,11 @@ export class NotebookEditor extends EditorPane implements IFindNotebookControlle
public async findPrevious(): Promise<void> {
try {
const p = await this.notebookFindModel.findPrevious();
this.setSelection(p);
this._updateFinderMatchState();
this._setCurrentFindMatch(p);
if (p !== this._currentMatch) {
this.setSelection(p);
this._updateFinderMatchState();
this._setCurrentFindMatch(p);
}
} catch (er) {
onUnexpectedError(er);
}

View File

@@ -250,7 +250,7 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf
collector.addRule(`.notebook-preview .rangeHighlight { background-color: ${notebookFindMatchHighlightColor};}`);
}
if (notebookFindRangeHighlightColor) {
collector.addRule(`.notebook-preview .rangeSpecificHighlight { background-color: ${notebookFindRangeHighlightColor}!important;}`);
collector.addRule(`mark .rangeSpecificHighlight { background-color: ${notebookFindRangeHighlightColor}!important;}`);
}
});
}

View File

@@ -401,6 +401,166 @@ suite('Notebook Find Model', function (): void {
assert.strictEqual(notebookFindModel.findMatches.length, 2, 'Find failed on markdown edit');
});
test('Should find results in the output of the code cell when output is stream', async function (): Promise<void> {
let codeCellOutput: nb.IStreamResult = {
output_type: 'stream',
name: 'stdout',
text: 'trace\nhello world\n.local\n'
};
let cellContent: nb.INotebookContents = {
cells: [{
cell_type: CellTypes.Markdown,
source: ['Hello World'],
metadata: { language: 'python' },
execution_count: 1
},
{
cell_type: 'code',
source: [
'print(\'trace\')\n',
'print(\'hello world\')\n',
'print(\'.local\')'
],
metadata: { language: 'python' },
outputs: [
codeCellOutput
],
execution_count: 1
}],
metadata: {
kernelspec: {
name: 'mssql',
language: 'sql',
display_name: 'SQL'
}
},
nbformat: 4,
nbformat_minor: 5
};
await initNotebookModel(cellContent);
// Need to set rendered text content for 1st cell
setRenderedTextContent(0);
let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('trace', false, false, max_find_count);
assert.strictEqual(notebookFindModel.findMatches.length, 2, 'Find failed on code cell and its output');
await notebookFindModel.find('hello', false, false, max_find_count);
assert.strictEqual(notebookFindModel.findMatches.length, 3, 'Find failed on code cell output');
});
test('Should find results in the output of the code cell when output is executeResult', async function (): Promise<void> {
let codeCellOutput: nb.IExecuteResult = {
output_type: 'execute_result',
execution_count: null,
data: {
'application/vnd.dataresource+json': {
'schema': {
'fields': [
{
'name': 'ContactTypeID'
},
{
'name': 'Name'
},
{
'name': 'ModifiedDate'
}
]
},
'data': [
{
'0': '1',
'1': 'Accounting Manager',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '2',
'1': 'Assistant Sales Agent',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '3',
'1': 'Assistant Sales Representative',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '4',
'1': 'Coordinator Foreign Markets',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '5',
'1': 'Export Administrator',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '6',
'1': 'International Marketing Manager',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '7',
'1': 'Marketing Assistant',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '8',
'1': 'Marketing Manager',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '9',
'1': 'Marketing Representative',
'2': '2008-04-30 00:00:00.000'
},
{
'0': '10',
'1': 'Order Administrator',
'2': '2008-04-30 00:00:00.000'
}
]
}
}
};
let cellContent: nb.INotebookContents = {
cells: [
{
cell_type: 'code',
source: [
'Select top 10 * from Person.ContactType\n', ' -- Assistant'
],
metadata: { language: 'sql' },
outputs: [
codeCellOutput
],
execution_count: 1
}],
metadata: {
kernelspec: {
name: 'mssql',
language: 'sql',
display_name: 'SQL'
}
},
nbformat: 4,
nbformat_minor: 5
};
max_find_count = 4;
await initNotebookModel(cellContent);
// Need to set rendered text content for 1st cell
setRenderedTextContent(0);
let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('Assistant', false, false, max_find_count);
assert.strictEqual(notebookFindModel.getFindCount(), 4, 'Find failed on executed code cell output');
});
test('Find next/previous should return the correct find index', async function (): Promise<void> {
// Need to set rendered text content for 2nd cell
setRenderedTextContent(1);

View File

@@ -751,6 +751,7 @@ export class NotebookEditorStub implements INotebookEditor {
}
export class CellEditorProviderStub implements ICellEditorProvider {
isCellOutput = false;
hasEditor(): boolean {
throw new Error('Method not implemented.');
}

View File

@@ -188,6 +188,7 @@ export interface INotebookSection {
export interface ICellEditorProvider {
hasEditor(): boolean;
isCellOutput: boolean;
cellGuid(): string;
getEditor(): BaseTextEditor;
deltaDecorations(newDecorationsRange: NotebookRange | NotebookRange[], oldDecorationsRange: NotebookRange | NotebookRange[]): void;
@@ -199,11 +200,13 @@ export class NotebookRange extends Range {
}
cell: ICellModel;
isMarkdownSourceCell: boolean;
outputComponentIndex: number;
constructor(cell: ICellModel, startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, markdownEditMode?: boolean) {
constructor(cell: ICellModel, startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, markdownEditMode?: boolean, outputIndex?: number) {
super(startLineNumber, startColumn, endLineNumber, endColumn);
this.updateActiveCell(cell);
this.isMarkdownSourceCell = markdownEditMode ? markdownEditMode : false;
this.outputComponentIndex = outputIndex >= 0 ? outputIndex : -1;
}
}