/** Export meshes suitable for graphics APIs from arbitrary geometry in elements in this IModelDb.
   *  * Requests can be slow when processing many elements so it is expected that this function be used on a dedicated backend.
   *  * Vertices are exported in the IModelDb's world coordinate system, which is right-handed with Z pointing up.
   *  * The results of changing [ExportGraphicsProps]($imodeljs-backend) during the [ExportGraphicsProps.onGraphics]($imodeljs-backend) callback are not defined.
   *
   * Example that prints the mesh for element 1 to stdout in [OBJ format](https://en.wikipedia.org/wiki/Wavefront_.obj_file)
   * ```
   * const onGraphics: ExportGraphicsFunction = (info: ExportGraphicsInfo) => {
   *   const mesh: ExportGraphicsMesh = info.mesh;
   *   for (let i = 0; i < mesh.points.length; i += 3) {
   *     process.stdout.write(`v ${mesh.points[i]} ${mesh.points[i + 1]} ${mesh.points[i + 2]}\n`);
   *     process.stdout.write(`vn ${mesh.normals[i]} ${mesh.normals[i + 1]} ${mesh.normals[i + 2]}\n`);
   *   }
   *
   *   for (let i = 0; i < mesh.params.length; i += 2) {
   *     process.stdout.write(`vt ${mesh.params[i]} ${mesh.params[i + 1]}\n`);
   *   }
   *
   *   for (let i = 0; i < mesh.indices.length; i += 3) {
   *     const p1 = mesh.indices[i];
   *     const p2 = mesh.indices[i + 1];
   *     const p3 = mesh.indices[i + 2];
   *     process.stdout.write(`f ${p1}/${p1}/${p1} ${p2}/${p2}/${p2} ${p3}/${p3}/${p3}\n`);
   *   }
   * };
   *
   * iModel.exportGraphics(({ onGraphics, elementIdArray: ["0x1"] }));
   * ```
   * @returns 0 if successful, status otherwise
   * @beta Waiting for feedback from community before finalizing.
   */
public exportGraphics(exportProps: ExportGraphicsProps): DbResult {
    return this.nativeDb.exportGraphics(exportProps);
  }


/** Get the Model with the specified identifier.
     * @param modelId The Model identifier.
     * @throws [[IModelError]]
     */
public getModelProps<T extends ModelProps>(modelId: Id64String): T {
      const json = this.getModelJson(JSON.stringify({ id: modelId.toString() }));
      return JSON.parse(json) as T;
    }


/** Get the Model with the specified identifier.
     * @param modelId The Model identifier.
     * @throws [[IModelError]]
     */
public getModel<T extends Model>(modelId: Id64String): T { return this._iModel.constructEntity<T>(this.getModelProps(modelId)); }


/**
     * Read the properties for a Model as a json string.
     * @param modelIdArg a json string with the identity of the model to load. Must have either "id" or "code".
     * @return a json string with the properties of the model.
     */
public getModelJson(modelIdArg: string): string {
      if (!this._iModel.briefcase) throw this._iModel.newNotOpenError();
      const val = this._iModel.nativeDb.getModel(modelIdArg);
      if (val.error)
        throw new IModelError(val.error.status, "Model=" + modelIdArg);
      return val.result!;
    }


/** Get the sub-model of the specified Element.
     * See [[IModelDb.Elements.queryElementIdByCode]] for more on how to find an element by Code.
     * @param modeledElementId Identifies the modeled element.
     * @throws [[IModelError]]
     */
public getSubModel<T extends Model>(modeledElementId: Id64String | GuidString | Code): T {
      const modeledElement = this._iModel.elements.getElement(modeledElementId);
      if (modeledElement.id === IModel.rootSubjectId)
        throw new IModelError(IModelStatus.NotFound, "Root subject does not have a sub-model", Logger.logWarning, loggerCategory);

      return this.getModel<T>(modeledElement.id);
    }


/** Create a new model in memory.
     * See the example in [[InformationPartitionElement]].
     * @param modelProps The properties to use when creating the model.
     * @throws [[IModelError]] if there is a problem creating the model.
     */
public createModel<T extends Model>(modelProps: ModelProps): T { return this._iModel.constructEntity<T>(modelProps); }


/** Insert a new model.
     * @param props The data for the new model.
     * @returns The newly inserted model's Id.
     * @throws [[IModelError]] if unable to insert the model.
     */
public insertModel(props: ModelProps): Id64String {
      if (props.isPrivate === undefined) // temporarily work around bug in addon
        props.isPrivate = false;

      const jsClass = this._iModel.getJsClass<typeof Model>(props.classFullName);
      if (IModelStatus.Success !== jsClass.onInsert(props))
        return Id64.invalid;

      const val = this._iModel.nativeDb.insertModel(JSON.stringify(props));
      if (val.error)
        throw new IModelError(val.error.status, "inserting model", Logger.logWarning, loggerCategory);

      props.id = Id64.fromJSON(JSON.parse(val.result!).id);
      jsClass.onInserted(props.id);
      return props.id;
    }


/** Update an existing model.
     * @param props the properties of the model to change
     * @throws [[IModelError]] if unable to update the model.
     */
public updateModel(props: ModelProps): void {
      const jsClass = this._iModel.getJsClass<typeof Model>(props.classFullName);
      if (IModelStatus.Success !== jsClass.onUpdate(props))
        return;

      const error = this._iModel.nativeDb.updateModel(JSON.stringify(props));
      if (error !== IModelStatus.Success)
        throw new IModelError(error, "updating model id=" + props.id, Logger.logWarning, loggerCategory);

      jsClass.onUpdated(props);
    }


/** Delete one or more existing models.
     * @param ids The Ids of the models to be deleted
     * @throws [[IModelError]]
     */
public deleteModel(ids: Id64Arg): void {
      Id64.toIdSet(ids).forEach((id) => {
        const props = this.getModelProps(id);
        const jsClass = this._iModel.getJsClass<typeof Model>(props.classFullName);
        if (IModelStatus.Success !== jsClass.onDelete(props))
          return;

        const error = this._iModel.nativeDb.deleteModel(id);
        if (error !== IModelStatus.Success)
          throw new IModelError(error, "", Logger.logWarning, loggerCategory);

        jsClass.onDeleted(props);
      });
    }


/**
     * Read element data from iModel as a json string
     * @param elementIdArg a json string with the identity of the element to load. Must have one of "id", "federationGuid", or "code".
     * @return a json string with the properties of the element.
     */
public getElementJson<T extends ElementProps>(elementIdArg: string): T {
      const val = this._iModel.nativeDb.getElement(elementIdArg);
      if (val.error)
        throw new IModelError(val.error.status, "reading element=" + elementIdArg, Logger.logWarning, loggerCategory);
      return val.result! as T;
    }


/**
     * Get properties of an Element by Id, FederationGuid, or Code
     * @throws [[IModelError]] if the element is not found.
     */
public getElementProps<T extends ElementProps>(elementId: Id64String | GuidString | Code | ElementLoadProps): T {
      if (typeof elementId === "string")
        elementId = Id64.isId64(elementId) ? { id: elementId } : { federationGuid: elementId };
      else if (elementId instanceof Code)
        elementId = { code: elementId };

      return this.getElementJson<T>(JSON.stringify(elementId));
    }


/**
     * Get an element by Id, FederationGuid, or Code
     * @param elementId either the element's Id, Code, or FederationGuid, or an ElementLoadProps
     * @throws [[IModelError]] if the element is not found.
     */
public getElement<T extends Element>(elementId: Id64String | GuidString | Code | ElementLoadProps): T {
      if (typeof elementId === "string")
        elementId = Id64.isId64(elementId) ? { id: elementId } : { federationGuid: elementId };
      else if (elementId instanceof Code)
        elementId = { code: elementId };

      return this._iModel.constructEntity<T>(this.getElementJson(JSON.stringify(elementId)));
    }


/**
     * Query for the Id of the element that has a specified code.
     * This method is for the case where you know the element's Code.
     * If you only know the code *value*, then in the simplest case, you can query on that
     * and filter the results.
     * In the simple case, call [[IModelDb.queryEntityIds]], specifying the code value in the where clause of the query params.
     * Or, you can execute an ECSQL select statement. See
     * [frequently used ECSQL queries]($docs/learning/backend/ECSQL-queries.md) for an example.
     * @param code The code to look for
     * @returns The element that uses the code or undefined if the code is not used.
     * @throws IModelError if the code is invalid
     */
public queryElementIdByCode(code: Code): Id64String | undefined {
      if (Id64.isInvalid(code.spec))
        throw new IModelError(IModelStatus.InvalidCodeSpec, "Invalid CodeSpec", Logger.logWarning, loggerCategory);

      if (code.value === undefined)
        throw new IModelError(IModelStatus.InvalidCode, "Invalid Code", Logger.logWarning, loggerCategory);

      return this._iModel.withPreparedStatement(`SELECT ECInstanceId FROM ${Element.classFullName} WHERE CodeSpec.Id=? AND CodeScope.Id=? AND CodeValue=?`, (stmt: ECSqlStatement) => {
        stmt.bindId(1, code.spec);
        stmt.bindId(2, Id64.fromString(code.scope));
        stmt.bindString(3, code.value!);
        if (DbResult.BE_SQLITE_ROW !== stmt.step())
          return undefined;

        return Id64.fromJSON(stmt.getRow().id);
      });
    }


/**
     * Create a new instance of an element.
     * @param elProps The properties of the new element.
     * @throws [[IModelError]] if there is a problem creating the element.
     */
public createElement<T extends Element>(elProps: ElementProps): T { return this._iModel.constructEntity<T>(elProps); }


/**
     * Insert a new element into the iModel.
     * @param elProps The properties of the new element.
     * @returns The newly inserted element's Id.
     * @throws [[IModelError]] if unable to insert the element.
     */
public insertElement(elProps: ElementProps): Id64String {
      const iModel = this._iModel;
      const jsClass = iModel.getJsClass(elProps.classFullName) as unknown as typeof Element;
      if (IModelStatus.Success !== jsClass.onInsert(elProps, iModel))
        return Id64.invalid;

      const val = iModel.nativeDb.insertElement(JSON.stringify(elProps));
      if (val.error)
        throw new IModelError(val.error.status, "Problem inserting element", Logger.logWarning, loggerCategory);

      elProps.id = Id64.fromJSON(JSON.parse(val.result!).id);
      jsClass.onInserted(elProps, iModel);
      return elProps.id;
    }


/** Update some properties of an existing element.
     * @param el the properties of the element to update.
     * @throws [[IModelError]] if unable to update the element.
     */
public updateElement(elProps: ElementProps): void {
      const iModel = this._iModel;
      const jsClass = iModel.getJsClass<typeof Element>(elProps.classFullName);
      if (IModelStatus.Success !== jsClass.onUpdate(elProps, iModel))
        return;

      const error = iModel.nativeDb.updateElement(JSON.stringify(elProps));
      if (error !== IModelStatus.Success)
        throw new IModelError(error, "", Logger.logWarning, loggerCategory);

      jsClass.onUpdated(elProps, iModel);
    }


/**
     * Delete one or more elements from this iModel.
     * @param ids The set of Ids of the element(s) to be deleted
     * @throws [[IModelError]]
     */
public deleteElement(ids: Id64Arg): void {
      const iModel = this._iModel;
      Id64.toIdSet(ids).forEach((id) => {
        const props = this.getElementProps(id);
        const jsClass = iModel.getJsClass<typeof Element>(props.classFullName);
        if (IModelStatus.Success !== jsClass.onDelete(props, iModel))
          return;

        const error = iModel.nativeDb.deleteElement(id);
        if (error !== IModelStatus.Success)
          throw new IModelError(error, "", Logger.logWarning, loggerCategory);

        jsClass.onDeleted(props, iModel);
      });
    }


/** Query for the child elements of the specified element.
     * @returns Returns an array of child element identifiers.
     * @throws [[IModelError]]
     */
public queryChildren(elementId: Id64String): Id64String[] {
      const rows: any[] = this._iModel.executeQuery(`SELECT ECInstanceId FROM ${Element.classFullName} WHERE Parent.Id=?`, [elementId]);
      const childIds: Id64String[] = [];
      for (const row of rows)
        childIds.push(Id64.fromJSON(row.id));
      return childIds;
    }


/** Get the root subject element. */
public getRootSubject(): Subject { return this.getElement(IModel.rootSubjectId); }


/** Query for aspects of a particular class (polymorphically) associated with this element.
     * @throws [[IModelError]]
     */
private _queryAspects(elementId: Id64String, aspectClassName: string): ElementAspect[] {
      const rows = this._iModel.executeQuery(`SELECT * FROM ${aspectClassName} WHERE Element.Id=?`, [elementId]);
      if (rows.length === 0) {
        return [];
      }
      const aspects: ElementAspect[] = [];
      for (const row of rows) {
        aspects.push(this._queryAspect(row.id, row.className.replace(".", ":")));
      }
      return aspects;
    }


/** Query for aspect by ECInstanceId
     * @throws [[IModelError]]
     */
private _queryAspect(aspectInstanceId: Id64String, aspectClassName: string): ElementAspect {
      const rows = this._iModel.executeQuery(`SELECT * FROM ${aspectClassName} WHERE ECInstanceId=?`, [aspectInstanceId]);
      if (rows.length !== 1) {
        throw new IModelError(IModelStatus.NotFound, "ElementAspect not found", Logger.logError, loggerCategory, () => ({ aspectInstanceId, aspectClassName }));
      }
      const aspectProps: ElementAspectProps = rows[0]; // start with everything that SELECT * returned
      aspectProps.classFullName = aspectProps.className.replace(".", ":"); // add in property required by EntityProps
      aspectProps.className = undefined; // clear property from SELECT * that we don't want in the final instance
      return this._iModel.constructEntity<ElementAspect>(aspectProps);
    }


/** Get the ElementAspect instances (by class name) that are related to the specified element.
     * @throws [[IModelError]]
     */
public getAspects(elementId: Id64String, aspectClassName: string): ElementAspect[] {
      const aspects: ElementAspect[] = this._queryAspects(elementId, aspectClassName);
      return aspects;
    }


/** Insert a new ElementAspect into the iModel.
     * @param aspectProps The properties of the new ElementAspect.
     * @throws [[IModelError]] if unable to insert the ElementAspect.
     */
public insertAspect(aspectProps: ElementAspectProps): void {
      if (!this._iModel.briefcase)
        throw this._iModel.newNotOpenError();

      const status = this._iModel.nativeDb.insertElementAspect(JSON.stringify(aspectProps));
      if (status !== IModelStatus.Success)
        throw new IModelError(status, "Error inserting ElementAspect", Logger.logWarning, loggerCategory);
    }


/** Update an exist ElementAspect within the iModel.
     * @param aspectProps The properties to use to update the ElementAspect.
     * @throws [[IModelError]] if unable to update the ElementAspect.
     */
public updateAspect(aspectProps: ElementAspectProps): void {
      if (!this._iModel.briefcase)
        throw this._iModel.newNotOpenError();

      const status = this._iModel.nativeDb.updateElementAspect(JSON.stringify(aspectProps));
      if (status !== IModelStatus.Success)
        throw new IModelError(status, "Error updating ElementAspect", Logger.logWarning, loggerCategory);
    }


/** Delete one or more ElementAspects from this iModel.
     * @param ids The set of Ids of the element(s) to be deleted
     * @throws [[IModelError]]
     */
public deleteAspect(ids: Id64Arg): void {
      Id64.toIdSet(ids).forEach((id) => {
        const status = this._iModel.nativeDb.deleteElementAspect(id);
        if (status !== IModelStatus.Success)
          throw new IModelError(status, "Error deleting ElementAspect", Logger.logWarning, loggerCategory);
      });
    }


/** Query for the array of ViewDefinitionProps of the specified class and matching the specified IsPrivate setting.
     * @param className Query for view definitions of this class.
     * @param wantPrivate If true, include private view definitions.
     */
public queryViewDefinitionProps(className: string = "BisCore.ViewDefinition", limit = IModelDb.defaultLimit, offset = 0, wantPrivate: boolean = false): ViewDefinitionProps[] {
      const where: string = (wantPrivate === false) ? "IsPrivate=FALSE" : "";
      const ids = this._iModel.queryEntityIds({ from: className, limit, offset, where });

      const props: ViewDefinitionProps[] = [];
      const imodel = this._iModel;
      ids.forEach((id) => {
        try {
          props.push(imodel.elements.getElementProps<ViewDefinitionProps>(id));
        } catch (err) { }
      });

      return props;
    }


/** Iterate all ViewDefinitions matching the supplied query.
     * @param params Specifies the query by which views are selected.
     * @param callback Function invoked for each ViewDefinition matching the query. Return false to terminate iteration, true to continue.
     * @return true if all views were iterated, false if iteration was terminated early due to callback returning false.
     *
     * **Example: Finding all views of a specific DrawingModel**
     * ``` ts
     * [[include:IModelDb.Views.iterateViews]]
     * ```
     */
public iterateViews(params: ViewQueryParams, callback: (view: ViewDefinition) => boolean): boolean {
      const ids = this._iModel.queryEntityIds(params);
      let finished = true;
      for (const id of ids) {
        try {
          const view = this._iModel.elements.getElement(id);
          if (undefined !== view && view instanceof ViewDefinition) {
            finished = callback(view);
            if (!finished)
              break;
          }
        } catch (err) { }
      }

      return finished;
    }


public getViewStateData(viewDefinitionId: string): ViewStateProps {
      const viewStateData: ViewStateProps = {} as any;
      const elements = this._iModel.elements;
      const viewDefinitionElement = elements.getElement<ViewDefinition>(viewDefinitionId);
      viewStateData.viewDefinitionProps = viewDefinitionElement.toJSON();
      viewStateData.categorySelectorProps = elements.getElementProps<CategorySelectorProps>(viewStateData.viewDefinitionProps.categorySelectorId);
      viewStateData.displayStyleProps = elements.getElementProps<DisplayStyleProps>(viewStateData.viewDefinitionProps.displayStyleId);
      if (viewStateData.viewDefinitionProps.modelSelectorId !== undefined)
        viewStateData.modelSelectorProps = elements.getElementProps<ModelSelectorProps>(viewStateData.viewDefinitionProps.modelSelectorId);
      else if (viewDefinitionElement instanceof SheetViewDefinition) {
        viewStateData.sheetProps = elements.getElementProps<SheetProps>(viewDefinitionElement.baseModelId);
        viewStateData.sheetAttachments = Array.from(this._iModel.queryEntityIds({
          from: "BisCore.ViewAttachment",
          where: "Model.Id=" + viewDefinitionElement.baseModelId,
        }));
      }
      return viewStateData;
    }


private getViewThumbnailArg(viewDefinitionId: Id64String): string {
      const viewProps: FilePropertyProps = { namespace: "dgn_View", name: "Thumbnail", id: viewDefinitionId };
      return JSON.stringify(viewProps);
    }


/** Get the thumbnail for a view.
     * @param viewDefinitionId The Id of the view for thumbnail
     * @return the ThumbnailProps, or undefined if no thumbnail exists.
     */
public getThumbnail(viewDefinitionId: Id64String): ThumbnailProps | undefined {
      const viewArg = this.getViewThumbnailArg(viewDefinitionId);
      const sizeProps = this._iModel.nativeDb.queryFileProperty(viewArg, true) as string;
      if (undefined === sizeProps)
        return undefined;

      const out = JSON.parse(sizeProps) as ThumbnailProps;
      out.image = this._iModel.nativeDb.queryFileProperty(viewArg, false) as Uint8Array;
      return out;
    }


/** Save a thumbnail for a view.
     * @param viewDefinitionId The Id of the view for thumbnail
     * @param thumbnail The thumbnail data.
     * @returns 0 if successful
     */
public saveThumbnail(viewDefinitionId: Id64String, thumbnail: ThumbnailProps): number {
      const viewArg = this.getViewThumbnailArg(viewDefinitionId);
      const props = { format: thumbnail.format, height: thumbnail.height, width: thumbnail.width };
      return this._iModel.nativeDb.saveFileProperty(viewArg, JSON.stringify(props), thumbnail.image);
    }


/** Set the default view property the iModel
     * @param viewId The Id of the ViewDefinition to use as the default
     */
public setDefaultViewId(viewId: Id64String): void {
      const spec = { namespace: "dgn_View", name: "DefaultView" };
      const blob32 = new Uint32Array(2);
      blob32[0] = Id64.getLowerUint32(viewId);
      blob32[1] = Id64.getUpperUint32(viewId);
      const blob8 = new Uint8Array(blob32.buffer);
      this._iModel.saveFileProperty(spec, undefined, blob8);
    }


/** @internal */
public async requestTileTreeProps(requestContext: ClientRequestContext, id: string): Promise<TileTreeProps> {
      requestContext.enter();
      if (!this._iModel.briefcase)
        throw this._iModel.newNotOpenError();

      return new Promise<TileTreeProps>((resolve, reject) => {
        requestContext.enter();
        this._iModel.nativeDb.getTileTree(id, (ret: IModelJsNative.ErrorStatusOrResult<IModelStatus, any>) => {
          if (undefined !== ret.error)
            reject(new IModelError(ret.error.status, "TreeId=" + id));
          else
            resolve(ret.result! as TileTreeProps);
        });
      });
    }


private pollTileContent(resolve: (arg0: Uint8Array) => void, reject: (err: Error) => void, treeId: string, tileId: string, requestContext: ClientRequestContext) {
      requestContext.enter();
      if (!this._iModel.briefcase) {
        reject(this._iModel.newNotOpenError());
        return;
      }

      const ret = this._iModel.nativeDb.pollTileContent(treeId, tileId);
      if (undefined !== ret.error) {
        reject(new IModelError(ret.error.status, "TreeId=" + treeId + " TileId=" + tileId));
      } else if (typeof ret.result !== "number") { // if type is not a number, it's the TileContent interface
        const res = ret.result as IModelJsNative.TileContent;
        const iModelGuid = this._iModel.getGuid();

        const tileSizeThreshold = IModelHost.logTileSizeThreshold;
        const tileSize = res.content.length;
        if (tileSize > tileSizeThreshold) {
          Logger.logWarning(loggerCategory, "Tile size (in bytes) larger than specified threshold", () => ({ tileSize, tileSizeThreshold, treeId, tileId, iModelGuid }));
        }

        const loadTimeThreshold = IModelHost.logTileLoadTimeThreshold;
        const loadTime = res.elapsedSeconds;
        if (loadTime > loadTimeThreshold) {
          Logger.logWarning(loggerCategory, "Tile load time (in seconds) greater than specified threshold", () => ({ loadTime, loadTimeThreshold, treeId, tileId, iModelGuid }));
        }

        resolve(res.content);
      } else { // if the type is a number, it's the TileContentState enum
        // ###TODO: Decide appropriate timeout interval. May want to switch on state (new vs loading vs pending)
        setTimeout(() => this.pollTileContent(resolve, reject, treeId, tileId, requestContext), 10);
      }
    }


/** @internal */
public async requestTileContent(requestContext: ClientRequestContext, treeId: string, tileId: string): Promise<Uint8Array> {
      requestContext.enter();
      if (!this._iModel.briefcase)
        throw this._iModel.newNotOpenError();

      return new Promise<Uint8Array>((resolve, reject) => {
        this.pollTileContent(resolve, reject, treeId, tileId, requestContext);
      });
    }


private _getElementClass(elClassName: string): typeof Element { return this._iModel.getJsClass(elClassName) as unknown as typeof Element; }


private _getRelationshipClass(relClassName: string): typeof Relationship { return this._iModel.getJsClass<typeof Relationship>(relClassName); }


/** @internal */
protected _onBeforeOutputsHandled(elClassName: string, elId: Id64String): void { this._getElementClass(elClassName).onBeforeOutputsHandled(elId, this._iModel); }


/** @internal */
protected _onAllInputsHandled(elClassName: string, elId: Id64String): void { this._getElementClass(elClassName).onAllInputsHandled(elId, this._iModel); }


/** @internal */
protected _onRootChanged(props: RelationshipProps): void { this._getRelationshipClass(props.classFullName).onRootChanged(props, this._iModel); }


/** @internal */
protected _onValidateOutput(props: RelationshipProps): void { this._getRelationshipClass(props.classFullName).onValidateOutput(props, this._iModel); }


/** @internal */
protected _onDeletedDependency(props: RelationshipProps): void { this._getRelationshipClass(props.classFullName).onDeletedDependency(props, this._iModel); }


/** @internal */
protected _onBeginValidate() { this.validationErrors.length = 0; }


/** @internal */
protected _onEndValidate() { }


/** Dependency handlers may call method this to report a validation error.
   * @param error The error. If error.fatal === true, the transaction will cancel rather than commit.
   */
public reportError(error: ValidationError) { this.validationErrors.push(error); this._nativeDb.logTxnError(error.fatal); }


/** Get the description of the operation that would be reversed by calling reverseTxns(1).
   * This is useful for showing the operation that would be undone, for example in a menu.
   */
public getUndoString(): string { return this._nativeDb.getUndoString(); }


/** Get a description of the operation that would be reinstated by calling reinstateTxn.
   * This is useful for showing the operation that would be redone, in a pull-down menu for example.
   */
public getRedoString(): string { return this._nativeDb.getRedoString(); }


/** Begin a new multi-Txn operation. This can be used to cause a series of Txns, that would normally
   * be considered separate actions for undo, to be grouped into a single undoable operation. This means that when reverseTxns(1) is called,
   * the entire group of changes are undone together. Multi-Txn operations can be nested, and until the outermost operation is closed,
   * all changes constitute a single operation.
   * @note This method must always be paired with a call to endMultiTxnAction.
   */
public beginMultiTxnOperation(): DbResult { return this._nativeDb.beginMultiTxnOperation(); }


/** End a multi-Txn operation */
public endMultiTxnOperation(): DbResult { return this._nativeDb.endMultiTxnOperation(); }


/** Return the depth of the multi-Txn stack. Generally for diagnostic use only. */
public getMultiTxnOperationDepth(): number { return this._nativeDb.getMultiTxnOperationDepth(); }


/** Reverse (undo) the most recent operation(s) to this IModelDb.
   * @param numOperations the number of operations to reverse. If this is greater than 1, the entire set of operations will
   *  be reinstated together when/if ReinstateTxn is called.
   * @note If there are any outstanding uncommitted changes, they are reversed.
   * @note The term "operation" is used rather than Txn, since multiple Txns can be grouped together via [[beginMultiTxnOperation]]. So,
   * even if numOperations is 1, multiple Txns may be reversed if they were grouped together when they were made.
   * @note If numOperations is too large only the operations are reversible are reversed.
   */
public reverseTxns(numOperations: number): IModelStatus { return this._nativeDb.reverseTxns(numOperations); }


/** Reverse the most recent operation. */
public reverseSingleTxn(): IModelStatus { return this.reverseTxns(1); }


/** Reverse all changes back to the beginning of the session. */
public reverseAll(): IModelStatus { return this._nativeDb.reverseAll(); }


/** Reverse all changes back to a previously saved TxnId.
   * @param txnId a TxnId obtained from a previous call to GetCurrentTxnId.
   * @returns Success if the transactions were reversed, error status otherwise.
   * @see  [[getCurrentTxnId]] [[cancelTo]]
   */
public reverseTo(txnId: TxnIdString) { return this._nativeDb.reverseTo(txnId); }


/** Reverse and then cancel (make non-reinstatable) all changes back to a previous TxnId.
   * @param txnId a TxnId obtained from a previous call to [[getCurrentTxnId]]
   * @returns Success if the transactions were reversed and cleared, error status otherwise.
   */
public cancelTo(txnId: TxnIdString) { return this._nativeDb.cancelTo(txnId); }


/** Reinstate the most recently reversed transaction. Since at any time multiple transactions can be reversed, it
   * may take multiple calls to this method to reinstate all reversed operations.
   * @returns Success if a reversed transaction was reinstated, error status otherwise.
   * @note If there are any outstanding uncommitted changes, they are reversed before the Txn is reinstated.
   */
public reinstateTxn(): IModelStatus { return this._nativeDb.reinstateTxn(); }


/** Get the Id of the first transaction, if any. */
public queryFirstTxnId(): TxnIdString { return this._nativeDb.queryFirstTxnId(); }


/** Get the successor of the specified TxnId */
public queryNextTxnId(txnId: TxnIdString): TxnIdString { return this._nativeDb.queryNextTxnId(txnId); }


/** Get the predecessor of the specified TxnId */
public queryPreviousTxnId(txnId: TxnIdString): TxnIdString { return this._nativeDb.queryPreviousTxnId(txnId); }


/** Get the Id of the current (tip) transaction.  */
public getCurrentTxnId(): TxnIdString { return this._nativeDb.getCurrentTxnId(); }


/** Get the description that was supplied when the specified transaction was saved. */
public getTxnDescription(txnId: TxnIdString): string { return this._nativeDb.getTxnDescription(txnId); }


/** Test if a TxnId is valid */
public isTxnIdValid(txnId: TxnIdString): boolean { return this._nativeDb.isTxnIdValid(txnId); }


/** Make a description of the changeset by combining all local txn comments. */
public describeChangeSet(endTxnId?: TxnIdString): string {
    if (endTxnId === undefined)
      endTxnId = this.getCurrentTxnId();

    const changes = [];
    const seen = new Set<string>();
    let txnId = this.queryFirstTxnId();

    while (this.isTxnIdValid(txnId)) {
      const txnDesc = this.getTxnDescription(txnId);
      if ((txnDesc.length === 0) || seen.has(txnDesc)) {
        txnId = this.queryNextTxnId(txnId);
        continue;
      }

      changes.push(txnDesc);
      seen.add(txnDesc);
      txnId = this.queryNextTxnId(txnId);
    }
    return JSON.stringify(changes);
  }


() => {
  return (
    <View style={styles.footerContainer}>
      <Button
        size='small'
        style={styles.footerControl}>
        Accept
      </Button>
      <Button
        size='small'
        status='basic'>
        Cancel
      </Button>
    </View>


() => (
  <CardHeader
    title='Title'


() => (
  <Layout style={styles.container}>
    <Card
      header


export function getActiveEditor(accessor: ServicesAccessor): IActiveCodeEditor | null {
	let activeTextEditorControl = accessor.get(IEditorService).activeTextEditorControl;

	if (isDiffEditor(activeTextEditorControl)) {
		if (activeTextEditorControl.getOriginalEditor().hasTextFocus()) {
			activeTextEditorControl = activeTextEditorControl.getOriginalEditor();
		} else {
			activeTextEditorControl = activeTextEditorControl.getModifiedEditor();
		}
	}

	if (!isCodeEditor(activeTextEditorControl) || !activeTextEditorControl.hasModel()) {
		return null;
	}

	return activeTextEditorControl;
}


range => {
				if ((range.startLineNumber <= hoverLine) && (range.endLineNumber >= hoverLine)) {
					const beforeRange = new Range(range.startLineNumber, 1, hoverLine, 1);
					const hoverRange = new Range(hoverLine, 1, hoverLine, 1);
					const afterRange = new Range(hoverLine, 1, range.endLineNumber, 1);
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true));
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, hoverRange, this.hoverDecorationOptions, info.commentingRanges, true));
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true));
				} else {
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, this.decorationOptions, info.commentingRanges));
				}
			}


(decoration, index) => decoration.id = this.decorationIds[index]


action => {
			return (action.range.startLineNumber <= commentRange.startLineNumber) && (commentRange.endLineNumber <= action.range.endLineNumber);
		}


actions => actions.action


count => {
			if (count === 0) {
				this.clearEditorListeners();
			} else if (!this._editorDisposables) {
				this.registerEditorListeners();
			}
		}


ownerId => {
			delete this._pendingCommentCache[ownerId];
			this.beginCompute();
		}


_ => this.beginCompute()


e => {
			const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri;
			if (editorURI && editorURI.toString() === e.resource.toString()) {
				this.setComments(e.commentInfos.filter(commentInfo => commentInfo !== null));
			}
		}


commentInfo => commentInfo !== null


e => this.onModelChanged(e)


e => this.onEditorMouseMove(e)


e => this.onEditorChangeCursorPosition(e.position)


() => this.onEditorChangeCursorPosition(this.editor.getPosition())


disposable => disposable.dispose()


token => {
			const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri;

			if (editorURI) {
				return this.commentService.getDocumentComments(editorURI);
			}

			return Promise.resolve([]);
		}


commentInfos => {
			this.setComments(coalesce(commentInfos));
			this._computePromise = null;
		}


() => {
				const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri;

				if (editorURI) {
					return this.commentService.getDocumentComments(editorURI);
				}

				return Promise.resolve([]);
			}


commentInfos => {
				const meaningfulCommentInfos = coalesce(commentInfos);
				this._commentingRangeDecorator.update(this.editor, meaningfulCommentInfos);
			}


(err) => {
				onUnexpectedError(err);
				return null;
			}


widget => widget.commentThread.threadId === threadId


_ => {
					this.revealCommentThread(threadId, commentUniqueId, false);
				}


(a, b) => {
			if (reverse) {
				const temp = a;
				a = b;
				b = temp;
			}
			if (a.commentThread.range.startLineNumber < b.commentThread.range.startLineNumber) {
				return -1;
			}

			if (a.commentThread.range.startLineNumber > b.commentThread.range.startLineNumber) {
				return 1;
			}

			if (a.commentThread.range.startColumn < b.commentThread.range.startColumn) {
				return -1;
			}

			if (a.commentThread.range.startColumn > b.commentThread.range.startColumn) {
				return 1;
			}

			return 0;
		}


widget => {
			let lineValueOne = reverse ? after.lineNumber : widget.commentThread.range.startLineNumber;
			let lineValueTwo = reverse ? widget.commentThread.range.startLineNumber : after.lineNumber;
			let columnValueOne = reverse ? after.column : widget.commentThread.range.startColumn;
			let columnValueTwo = reverse ? widget.commentThread.range.startColumn : after.column;
			if (lineValueOne > lineValueTwo) {
				return true;
			}

			if (lineValueOne < lineValueTwo) {
				return false;
			}

			if (columnValueOne > columnValueTwo) {
				return true;
			}
			return false;
		}


widget => widget.dispose()


e => this.onEditorMouseDown(e)


e => this.onEditorMouseUp(e)


() => {
				if (this._computeCommentingRangeScheduler) {
					this._computeCommentingRangeScheduler.cancel();
				}
				this._computeCommentingRangeScheduler = null;
			}


async () => {
			this.beginComputeCommentingRanges();
		}


async e => {
			const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri;
			if (!editorURI) {
				return;
			}

			if (this._computePromise) {
				await this._computePromise;
			}

			let commentInfo = this._commentInfos.filter(info => info.owner === e.owner);
			if (!commentInfo || !commentInfo.length) {
				return;
			}

			let added = e.added.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString());
			let removed = e.removed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString());
			let changed = e.changed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString());

			removed.forEach(thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== '');
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					let index = this._commentWidgets.indexOf(matchedZone);
					this._commentWidgets.splice(index, 1);
					matchedZone.dispose();
				}
			});

			changed.forEach(thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					matchedZone.update(thread);
				}
			});
			added.forEach(thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
				if (matchedZones.length) {
					return;
				}

				let matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && (zoneWidget.commentThread as any).commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range));

				if (matchedNewCommentThreadZones.length) {
					matchedNewCommentThreadZones[0].update(thread);
					return;
				}

				const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId!];
				this.displayCommentThread(e.owner, thread, pendingCommentText);
				this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread);
			});

		}


info => info.owner === e.owner


thread => thread.resource && thread.resource.toString() === editorURI.toString()


thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== '');
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					let index = this._commentWidgets.indexOf(matchedZone);
					this._commentWidgets.splice(index, 1);
					matchedZone.dispose();
				}
			}


zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== ''


thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					matchedZone.update(thread);
				}
			}


zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId


thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
				if (matchedZones.length) {
					return;
				}

				let matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && (zoneWidget.commentThread as any).commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range));

				if (matchedNewCommentThreadZones.length) {
					matchedNewCommentThreadZones[0].update(thread);
					return;
				}

				const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId!];
				this.displayCommentThread(e.owner, thread, pendingCommentText);
				this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread);
			}


zoneWidget => zoneWidget.owner === e.owner && (zoneWidget.commentThread as any).commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)


() => {
			if (this._commentWidgets.length
				&& (this.configurationService.getValue<ICommentsConfiguration>(COMMENTS_SECTION).openView === 'file')) {
				this.viewsService.openView(COMMENTS_VIEW_ID);
			}
		}


widget => widget.getGlyphPosition() === commentRange.endLineNumber


widget => widget.toggleExpand(commentRange.endLineNumber)


() => anchor


() => this.getContextMenuActions(newCommentInfos, range)


() => newCommentInfos.length ? newCommentInfos[0] : undefined


() => { this._addInProgress = false; }


pick => {
					if (!pick) {
						return;
					}

					const commentInfos = newCommentInfos.filter(info => info.ownerId === pick.id);

					if (commentInfos.length) {
						const { ownerId } = commentInfos[0];
						this.addCommentAtLine2(range, ownerId);
					}
				}


info => info.ownerId === pick.id


() => {
					this._addInProgress = false;
				}


(commentInfo) => {
			const { ownerId, extensionId, label } = commentInfo;

			return <IQuickPickItem>{
				label: label || extensionId,
				id: ownerId
			};
		}


commentInfo => {
			const { ownerId, extensionId, label } = commentInfo;

			actions.push(new Action(
				'addCommentThread',
				`${label || extensionId}`,
				undefined,
				true,
				() => {
					this.addCommentAtLine2(commentRange, ownerId);
					return Promise.resolve();
				}
			));
		}


() => {
					this.addCommentAtLine2(commentRange, ownerId);
					return Promise.resolve();
				}


info => Boolean(info.commentingRanges && (Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges.ranges).length)


info => {
			let providerCacheStore = this._pendingCommentCache[info.owner];
			info.threads = info.threads.filter(thread => !thread.isDisposed);
			info.threads.forEach(thread => {
				let pendingComment: string | null = null;
				if (providerCacheStore) {
					pendingComment = providerCacheStore[thread.threadId!];
				}

				if (pendingComment) {
					thread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded;
				}

				this.displayCommentThread(info.owner, thread, pendingComment);
			});
		}


thread => !thread.isDisposed


thread => {
				let pendingComment: string | null = null;
				if (providerCacheStore) {
					pendingComment = providerCacheStore[thread.threadId!];
				}

				if (pendingComment) {
					thread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded;
				}

				this.displayCommentThread(info.owner, thread, pendingComment);
			}


widget => widget.hide()


zone => {
				let pendingComment = zone.getPendingComment();
				let providerCacheStore = this._pendingCommentCache[zone.owner];

				if (pendingComment) {
					if (!providerCacheStore) {
						this._pendingCommentCache[zone.owner] = {};
					}

					this._pendingCommentCache[zone.owner][zone.commentThread.threadId!] = pendingComment;
				} else {
					if (providerCacheStore) {
						delete providerCacheStore[zone.commentThread.threadId!];
					}
				}

				zone.dispose();
			}


(accessor) => {
		const activeEditor = getActiveEditor(accessor);
		if (!activeEditor) {
			return Promise.resolve();
		}

		const controller = CommentController.get(activeEditor);
		if (!controller) {
			return Promise.resolve();
		}

		const position = activeEditor.getSelection();
		return controller.addOrToggleCommentAtLine(position, undefined);
	}


(accessor, args) => {
		const activeCodeEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
		if (activeCodeEditor instanceof SimpleCommentEditor) {
			activeCodeEditor.getParentThread().submitComment();
		}
	}


(accessor, args) => {
		const activeCodeEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
		if (activeCodeEditor instanceof SimpleCommentEditor) {
			activeCodeEditor.getParentThread().collapse();
		}
	}


(theme, collector) => {
	const peekViewBackground = theme.getColor(peekViewResultsBackground);
	if (peekViewBackground) {
		collector.addRule(
			`.monaco-editor .review-widget,` +
			`.monaco-editor .review-widget {` +
			`	background-color: ${peekViewBackground};` +
			`}`);
	}

	const monacoEditorBackground = theme.getColor(peekViewTitleBackground);
	if (monacoEditorBackground) {
		collector.addRule(
			`.review-widget .body .comment-form .review-thread-reply-button {` +
			`	background-color: ${monacoEditorBackground}` +
			`}`
		);
	}

	const monacoEditorForeground = theme.getColor(editorForeground);
	if (monacoEditorForeground) {
		collector.addRule(
			`.review-widget .body .monaco-editor {` +
			`	color: ${monacoEditorForeground}` +
			`}` +
			`.review-widget .body .comment-form .review-thread-reply-button {` +
			`	color: ${monacoEditorForeground};` +
			`	font-size: inherit` +
			`}`
		);
	}

	const selectionBackground = theme.getColor(peekViewResultsSelectionBackground);

	if (selectionBackground) {
		collector.addRule(
			`@keyframes monaco-review-widget-focus {` +
			`	0% { background: ${selectionBackground}; }` +
			`	100% { background: transparent; }` +
			`}` +
			`.review-widget .body .review-comment.focus {` +
			`	animation: monaco-review-widget-focus 3s ease 0s;` +
			`}`
		);
	}

	const commentingRangeForeground = theme.getColor(overviewRulerCommentingRangeForeground);
	if (commentingRangeForeground) {
		collector.addRule(`
			.monaco-editor .comment-diff-added {
				border-left: 3px solid ${commentingRangeForeground};
			}
			.monaco-editor .comment-diff-added:before {
				background: ${commentingRangeForeground};
			}
			.monaco-editor .comment-thread {
				border-left: 3px solid ${commentingRangeForeground};
			}
			.monaco-editor .comment-thread:before {
				background: ${commentingRangeForeground};
			}
		`);
	}

	const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
	if (statusBarItemHoverBackground) {
		collector.addRule(`.review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.active:hover { background-color: ${statusBarItemHoverBackground};}`);
	}

	const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
	if (statusBarItemActiveBackground) {
		collector.addRule(`.review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label:active { background-color: ${statusBarItemActiveBackground}; border: 1px solid transparent;}`);
	}
}


export class ReviewViewZone implements IViewZone {
	public readonly afterLineNumber: number;
	public readonly domNode: HTMLElement;
	private callback: (top: number) => void;

	constructor(afterLineNumber: number, onDomNodeTop: (top: number) => void) {
		this.afterLineNumber = afterLineNumber;
		this.callback = onDomNodeTop;

		this.domNode = $('.review-viewzone');
	}

	onDomNodeTop(top: number): void {
		this.callback(top);
	}
}


class CommentingRangeDecoration implements IModelDeltaDecoration {
	private _decorationId: string | undefined;
	private _startLineNumber: number;
	private _endLineNumber: number;

	public get id(): string | undefined {
		return this._decorationId;
	}

	public set id(id: string | undefined) {
		this._decorationId = id;
	}

	public get range(): IRange {
		return {
			startLineNumber: this._startLineNumber, startColumn: 1,
			endLineNumber: this._endLineNumber, endColumn: 1
		};
	}

	constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, public readonly options: ModelDecorationOptions, private commentingRangesInfo: languages.CommentingRanges, public readonly isHover: boolean = false) {
		this._startLineNumber = _range.startLineNumber;
		this._endLineNumber = _range.endLineNumber;
	}

	public getCommentAction(): CommentRangeAction {
		return {
			extensionId: this._extensionId,
			label: this._label,
			ownerId: this._ownerId,
			commentingRangesInfo: this.commentingRangesInfo
		};
	}

	public getOriginalRange() {
		return this._range;
	}

	public getActiveRange() {
		return this.id ? this._editor.getModel()!.getDecorationRange(this.id) : undefined;
	}
}


class CommentingRangeDecorator {
	public static description = 'commenting-range-decorator';
	private decorationOptions!: ModelDecorationOptions;
	private hoverDecorationOptions!: ModelDecorationOptions;
	private commentingRangeDecorations: CommentingRangeDecoration[] = [];
	private decorationIds: string[] = [];
	private _editor: ICodeEditor | undefined;
	private _infos: ICommentInfo[] | undefined;
	private _lastHover: number = -1;
	private _onDidChangeDecorationsCount: Emitter<number> = new Emitter();
	public readonly onDidChangeDecorationsCount = this._onDidChangeDecorationsCount.event;

	constructor() {
		const decorationOptions: IModelDecorationOptions = {
			description: CommentingRangeDecorator.description,
			isWholeLine: true,
			linesDecorationsClassName: 'comment-range-glyph comment-diff-added'
		};

		this.decorationOptions = ModelDecorationOptions.createDynamic(decorationOptions);

		const hoverDecorationOptions: IModelDecorationOptions = {
			description: CommentingRangeDecorator.description,
			isWholeLine: true,
			linesDecorationsClassName: `comment-range-glyph comment-diff-added line-hover`
		};

		this.hoverDecorationOptions = ModelDecorationOptions.createDynamic(hoverDecorationOptions);
	}

	public updateHover(hoverLine?: number) {
		if (this._editor && this._infos && (hoverLine !== this._lastHover)) {
			this._doUpdate(this._editor, this._infos, hoverLine);
		}
		this._lastHover = hoverLine ?? -1;
	}

	public update(editor: ICodeEditor, commentInfos: ICommentInfo[]) {
		this._editor = editor;
		this._infos = commentInfos;
		this._doUpdate(editor, commentInfos);
	}

	private _doUpdate(editor: ICodeEditor, commentInfos: ICommentInfo[], hoverLine: number = -1) {
		let model = editor.getModel();
		if (!model) {
			return;
		}

		let commentingRangeDecorations: CommentingRangeDecoration[] = [];
		for (const info of commentInfos) {
			info.commentingRanges.ranges.forEach(range => {
				if ((range.startLineNumber <= hoverLine) && (range.endLineNumber >= hoverLine)) {
					const beforeRange = new Range(range.startLineNumber, 1, hoverLine, 1);
					const hoverRange = new Range(hoverLine, 1, hoverLine, 1);
					const afterRange = new Range(hoverLine, 1, range.endLineNumber, 1);
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true));
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, hoverRange, this.hoverDecorationOptions, info.commentingRanges, true));
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true));
				} else {
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, this.decorationOptions, info.commentingRanges));
				}
			});
		}

		this.decorationIds = editor.deltaDecorations(this.decorationIds, commentingRangeDecorations);
		commentingRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]);

		const rangesDifference = this.commentingRangeDecorations.length - commentingRangeDecorations.length;
		this.commentingRangeDecorations = commentingRangeDecorations;
		if (rangesDifference) {
			this._onDidChangeDecorationsCount.fire(this.commentingRangeDecorations.length);
		}
	}

	public getMatchedCommentAction(commentRange: Range): CommentRangeAction[] {
		// keys is ownerId
		const foundHoverActions = new Map<string, { range: Range; action: CommentRangeAction }>();
		for (const decoration of this.commentingRangeDecorations) {
			const range = decoration.getActiveRange();
			if (range && ((range.startLineNumber <= commentRange.startLineNumber) || (commentRange.endLineNumber <= range.endLineNumber))) {
				// We can have 3 commenting ranges that match from the same owner because of how
				// the line hover decoration is done.
				// The 3 ranges must be merged so that we can see if the new commentRange fits within them.
				const action = decoration.getCommentAction();
				const alreadyFoundInfo = foundHoverActions.get(action.ownerId);
				if (alreadyFoundInfo?.action.commentingRangesInfo === action.commentingRangesInfo) {
					// Merge ranges.
					const newRange = new Range(
						range.startLineNumber < alreadyFoundInfo.range.startLineNumber ? range.startLineNumber : alreadyFoundInfo.range.startLineNumber,
						range.startColumn < alreadyFoundInfo.range.startColumn ? range.startColumn : alreadyFoundInfo.range.startColumn,
						range.endLineNumber > alreadyFoundInfo.range.endLineNumber ? range.endLineNumber : alreadyFoundInfo.range.endLineNumber,
						range.endColumn > alreadyFoundInfo.range.endColumn ? range.endColumn : alreadyFoundInfo.range.endColumn
					);
					foundHoverActions.set(action.ownerId, { range: newRange, action });
				} else {
					foundHoverActions.set(action.ownerId, { range, action });
				}
			}
		}

		return Array.from(foundHoverActions.values()).filter(action => {
			return (action.range.startLineNumber <= commentRange.startLineNumber) && (commentRange.endLineNumber <= action.range.endLineNumber);
		}).map(actions => actions.action);
	}

	public dispose(): void {
		this.commentingRangeDecorations = [];
	}
}


export class NextCommentThreadAction extends EditorAction {
	constructor() {
		super({
			id: 'editor.action.nextCommentThreadAction',
			label: nls.localize('nextCommentThreadAction', "Go to Next Comment Thread"),
			alias: 'Go to Next Comment Thread',
			precondition: undefined,
			kbOpts: {
				kbExpr: EditorContextKeys.focus,
				primary: KeyMod.Alt | KeyCode.F9,
				weight: KeybindingWeight.EditorContrib
			}
		});
	}

	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
		let controller = CommentController.get(editor);
		if (controller) {
			controller.nextCommentThread();
		}
	}
}


export class PreviousCommentThreadAction extends EditorAction {
	constructor() {
		super({
			id: 'editor.action.previousCommentThreadAction',
			label: nls.localize('previousCommentThreadAction', "Go to Previous Comment Thread"),
			alias: 'Go to Previous Comment Thread',
			precondition: undefined,
			kbOpts: {
				kbExpr: EditorContextKeys.focus,
				primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F9,
				weight: KeybindingWeight.EditorContrib
			}
		});
	}

	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
		let controller = CommentController.get(editor);
		if (controller) {
			controller.previousCommentThread();
		}
	}
}


interface CommentRangeAction {
	ownerId: string;
	extensionId: string | undefined;
	label: string | undefined;
	commentingRangesInfo: languages.CommentingRanges;
}


onDomNodeTop(top: number): void {
		this.callback(top);
	}


public getCommentAction(): CommentRangeAction {
		return {
			extensionId: this._extensionId,
			label: this._label,
			ownerId: this._ownerId,
			commentingRangesInfo: this.commentingRangesInfo
		};
	}


public getOriginalRange() {
		return this._range;
	}


public getActiveRange() {
		return this.id ? this._editor.getModel()!.getDecorationRange(this.id) : undefined;
	}


public updateHover(hoverLine?: number) {
		if (this._editor && this._infos && (hoverLine !== this._lastHover)) {
			this._doUpdate(this._editor, this._infos, hoverLine);
		}
		this._lastHover = hoverLine ?? -1;
	}


public update(editor: ICodeEditor, commentInfos: ICommentInfo[]) {
		this._editor = editor;
		this._infos = commentInfos;
		this._doUpdate(editor, commentInfos);
	}


private _doUpdate(editor: ICodeEditor, commentInfos: ICommentInfo[], hoverLine: number = -1) {
		let model = editor.getModel();
		if (!model) {
			return;
		}

		let commentingRangeDecorations: CommentingRangeDecoration[] = [];
		for (const info of commentInfos) {
			info.commentingRanges.ranges.forEach(range => {
				if ((range.startLineNumber <= hoverLine) && (range.endLineNumber >= hoverLine)) {
					const beforeRange = new Range(range.startLineNumber, 1, hoverLine, 1);
					const hoverRange = new Range(hoverLine, 1, hoverLine, 1);
					const afterRange = new Range(hoverLine, 1, range.endLineNumber, 1);
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true));
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, hoverRange, this.hoverDecorationOptions, info.commentingRanges, true));
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true));
				} else {
					commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, this.decorationOptions, info.commentingRanges));
				}
			});
		}

		this.decorationIds = editor.deltaDecorations(this.decorationIds, commentingRangeDecorations);
		commentingRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]);

		const rangesDifference = this.commentingRangeDecorations.length - commentingRangeDecorations.length;
		this.commentingRangeDecorations = commentingRangeDecorations;
		if (rangesDifference) {
			this._onDidChangeDecorationsCount.fire(this.commentingRangeDecorations.length);
		}
	}


public getMatchedCommentAction(commentRange: Range): CommentRangeAction[] {
		// keys is ownerId
		const foundHoverActions = new Map<string, { range: Range; action: CommentRangeAction }>();
		for (const decoration of this.commentingRangeDecorations) {
			const range = decoration.getActiveRange();
			if (range && ((range.startLineNumber <= commentRange.startLineNumber) || (commentRange.endLineNumber <= range.endLineNumber))) {
				// We can have 3 commenting ranges that match from the same owner because of how
				// the line hover decoration is done.
				// The 3 ranges must be merged so that we can see if the new commentRange fits within them.
				const action = decoration.getCommentAction();
				const alreadyFoundInfo = foundHoverActions.get(action.ownerId);
				if (alreadyFoundInfo?.action.commentingRangesInfo === action.commentingRangesInfo) {
					// Merge ranges.
					const newRange = new Range(
						range.startLineNumber < alreadyFoundInfo.range.startLineNumber ? range.startLineNumber : alreadyFoundInfo.range.startLineNumber,
						range.startColumn < alreadyFoundInfo.range.startColumn ? range.startColumn : alreadyFoundInfo.range.startColumn,
						range.endLineNumber > alreadyFoundInfo.range.endLineNumber ? range.endLineNumber : alreadyFoundInfo.range.endLineNumber,
						range.endColumn > alreadyFoundInfo.range.endColumn ? range.endColumn : alreadyFoundInfo.range.endColumn
					);
					foundHoverActions.set(action.ownerId, { range: newRange, action });
				} else {
					foundHoverActions.set(action.ownerId, { range, action });
				}
			}
		}

		return Array.from(foundHoverActions.values()).filter(action => {
			return (action.range.startLineNumber <= commentRange.startLineNumber) && (commentRange.endLineNumber <= action.range.endLineNumber);
		}).map(actions => actions.action);
	}


public dispose(): void {
		this.commentingRangeDecorations = [];
	}


private registerEditorListeners() {
		this._editorDisposables = [];
		this._editorDisposables.push(this.editor.onMouseMove(e => this.onEditorMouseMove(e)));
		this._editorDisposables.push(this.editor.onDidChangeCursorPosition(e => this.onEditorChangeCursorPosition(e.position)));
		this._editorDisposables.push(this.editor.onDidFocusEditorWidget(() => this.onEditorChangeCursorPosition(this.editor.getPosition())));
	}


private clearEditorListeners() {
		this._editorDisposables?.forEach(disposable => disposable.dispose());
		this._editorDisposables = undefined;
	}


private onEditorMouseMove(e: IEditorMouseEvent): void {
		this._commentingRangeDecorator.updateHover(e.target.position?.lineNumber);
	}


private onEditorChangeCursorPosition(e: Position | null) {
		const decorations = e ? this.editor.getDecorationsInRange(Range.fromPositions(e, { column: -1, lineNumber: e.lineNumber })) : undefined;
		let hasCommentingRange = false;
		if (decorations) {
			for (const decoration of decorations) {
				if (decoration.options.description === CommentGlyphWidget.description) {
					// We don't allow multiple comments on the same line.
					hasCommentingRange = false;
					break;
				} else if (decoration.options.description === CommentingRangeDecorator.description) {
					hasCommentingRange = true;
				}
			}
		}
		this._activeCursorHasCommentingRange.set(hasCommentingRange);
	}


private beginCompute(): Promise<void> {
		this._computePromise = createCancelablePromise(token => {
			const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri;

			if (editorURI) {
				return this.commentService.getDocumentComments(editorURI);
			}

			return Promise.resolve([]);
		});

		return this._computePromise.then(commentInfos => {
			this.setComments(coalesce(commentInfos));
			this._computePromise = null;
		}, error => console.log(error));
	}


private beginComputeCommentingRanges() {
		if (this._computeCommentingRangeScheduler) {
			if (this._computeCommentingRangePromise) {
				this._computeCommentingRangePromise.cancel();
				this._computeCommentingRangePromise = null;
			}

			this._computeCommentingRangeScheduler.trigger(() => {
				const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri;

				if (editorURI) {
					return this.commentService.getDocumentComments(editorURI);
				}

				return Promise.resolve([]);
			}).then(commentInfos => {
				const meaningfulCommentInfos = coalesce(commentInfos);
				this._commentingRangeDecorator.update(this.editor, meaningfulCommentInfos);
			}, (err) => {
				onUnexpectedError(err);
				return null;
			});
		}
	}


public static get(editor: ICodeEditor): CommentController | null {
		return editor.getContribution<CommentController>(ID);
	}


public revealCommentThread(threadId: string, commentUniqueId: number, fetchOnceIfNotExist: boolean): void {
		const commentThreadWidget = this._commentWidgets.filter(widget => widget.commentThread.threadId === threadId);
		if (commentThreadWidget.length === 1) {
			commentThreadWidget[0].reveal(commentUniqueId);
		} else if (fetchOnceIfNotExist) {
			if (this._computePromise) {
				this._computePromise.then(_ => {
					this.revealCommentThread(threadId, commentUniqueId, false);
				});
			} else {
				this.beginCompute().then(_ => {
					this.revealCommentThread(threadId, commentUniqueId, false);
				});
			}
		}
	}


public nextCommentThread(): void {
		this._findNearestCommentThread();
	}


private _findNearestCommentThread(reverse?: boolean): void {
		if (!this._commentWidgets.length || !this.editor.hasModel()) {
			return;
		}

		const after = this.editor.getSelection().getEndPosition();
		const sortedWidgets = this._commentWidgets.sort((a, b) => {
			if (reverse) {
				const temp = a;
				a = b;
				b = temp;
			}
			if (a.commentThread.range.startLineNumber < b.commentThread.range.startLineNumber) {
				return -1;
			}

			if (a.commentThread.range.startLineNumber > b.commentThread.range.startLineNumber) {
				return 1;
			}

			if (a.commentThread.range.startColumn < b.commentThread.range.startColumn) {
				return -1;
			}

			if (a.commentThread.range.startColumn > b.commentThread.range.startColumn) {
				return 1;
			}

			return 0;
		});

		let idx = findFirstInSorted(sortedWidgets, widget => {
			let lineValueOne = reverse ? after.lineNumber : widget.commentThread.range.startLineNumber;
			let lineValueTwo = reverse ? widget.commentThread.range.startLineNumber : after.lineNumber;
			let columnValueOne = reverse ? after.column : widget.commentThread.range.startColumn;
			let columnValueTwo = reverse ? widget.commentThread.range.startColumn : after.column;
			if (lineValueOne > lineValueTwo) {
				return true;
			}

			if (lineValueOne < lineValueTwo) {
				return false;
			}

			if (columnValueOne > columnValueTwo) {
				return true;
			}
			return false;
		});

		let nextWidget: ReviewZoneWidget;
		if (idx === this._commentWidgets.length) {
			nextWidget = this._commentWidgets[0];
		} else {
			nextWidget = sortedWidgets[idx];
		}
		this.editor.setSelection(nextWidget.commentThread.range);
		nextWidget.reveal(undefined, true);
	}


public previousCommentThread(): void {
		this._findNearestCommentThread(true);
	}


public dispose(): void {
		this.globalToDispose.dispose();
		this.localToDispose.dispose();
		this._editorDisposables?.forEach(disposable => disposable.dispose());

		this._commentWidgets.forEach(widget => widget.dispose());

		this.editor = null!; // Strict null override - nulling out in dispose
	}


public onModelChanged(e: IModelChangedEvent): void {
		this.localToDispose.clear();

		this.removeCommentWidgetsAndStoreCache();

		this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
		this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
		if (this._editorDisposables) {
			this.clearEditorListeners();
			this.registerEditorListeners();
		}

		this._computeCommentingRangeScheduler = new Delayer<ICommentInfo[]>(200);
		this.localToDispose.add({
			dispose: () => {
				if (this._computeCommentingRangeScheduler) {
					this._computeCommentingRangeScheduler.cancel();
				}
				this._computeCommentingRangeScheduler = null;
			}
		});
		this.localToDispose.add(this.editor.onDidChangeModelContent(async () => {
			this.beginComputeCommentingRanges();
		}));
		this.localToDispose.add(this.commentService.onDidUpdateCommentThreads(async e => {
			const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri;
			if (!editorURI) {
				return;
			}

			if (this._computePromise) {
				await this._computePromise;
			}

			let commentInfo = this._commentInfos.filter(info => info.owner === e.owner);
			if (!commentInfo || !commentInfo.length) {
				return;
			}

			let added = e.added.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString());
			let removed = e.removed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString());
			let changed = e.changed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString());

			removed.forEach(thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== '');
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					let index = this._commentWidgets.indexOf(matchedZone);
					this._commentWidgets.splice(index, 1);
					matchedZone.dispose();
				}
			});

			changed.forEach(thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					matchedZone.update(thread);
				}
			});
			added.forEach(thread => {
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
				if (matchedZones.length) {
					return;
				}

				let matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && (zoneWidget.commentThread as any).commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range));

				if (matchedNewCommentThreadZones.length) {
					matchedNewCommentThreadZones[0].update(thread);
					return;
				}

				const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId!];
				this.displayCommentThread(e.owner, thread, pendingCommentText);
				this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread);
			});

		}));

		this.beginCompute().then(() => {
			if (this._commentWidgets.length
				&& (this.configurationService.getValue<ICommentsConfiguration>(COMMENTS_SECTION).openView === 'file')) {
				this.viewsService.openView(COMMENTS_VIEW_ID);
			}
		});
	}


private displayCommentThread(owner: string, thread: languages.CommentThread, pendingComment: string | null): void {
		const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment);
		zoneWidget.display(thread.range.startLineNumber);
		this._commentWidgets.push(zoneWidget);
	}


private onEditorMouseDown(e: IEditorMouseEvent): void {
		this.mouseDownInfo = parseMouseDownInfoFromEvent(e);
	}


private onEditorMouseUp(e: IEditorMouseEvent): void {
		const matchedLineNumber = isMouseUpEventMatchMouseDown(this.mouseDownInfo, e);
		this.mouseDownInfo = null;

		if (matchedLineNumber === null || !e.target.element) {
			return;
		}

		if (e.target.element.className.indexOf('comment-diff-added') >= 0) {
			const lineNumber = e.target.position!.lineNumber;
			this.addOrToggleCommentAtLine(new Range(lineNumber, 1, lineNumber, 1), e);
		}
	}


public async addOrToggleCommentAtLine(commentRange: Range, e: IEditorMouseEvent | undefined): Promise<void> {
		// If an add is already in progress, queue the next add and process it after the current one finishes to
		// prevent empty comment threads from being added to the same line.
		if (!this._addInProgress) {
			this._addInProgress = true;
			// The widget's position is undefined until the widget has been displayed, so rely on the glyph position instead
			const existingCommentsAtLine = this._commentWidgets.filter(widget => widget.getGlyphPosition() === commentRange.endLineNumber);
			if (existingCommentsAtLine.length) {
				existingCommentsAtLine.forEach(widget => widget.toggleExpand(commentRange.endLineNumber));
				this.processNextThreadToAdd();
				return;
			} else {
				this.addCommentAtLine(commentRange, e);
			}
		} else {
			this._emptyThreadsToAddQueue.push([commentRange, e]);
		}
	}


private processNextThreadToAdd(): void {
		this._addInProgress = false;
		const info = this._emptyThreadsToAddQueue.shift();
		if (info) {
			this.addOrToggleCommentAtLine(info[0], info[1]);
		}
	}


public addCommentAtLine(range: Range, e: IEditorMouseEvent | undefined): Promise<void> {
		const newCommentInfos = this._commentingRangeDecorator.getMatchedCommentAction(range);
		if (!newCommentInfos.length || !this.editor.hasModel()) {
			return Promise.resolve();
		}

		if (newCommentInfos.length > 1) {
			if (e) {
				const anchor = { x: e.event.posx, y: e.event.posy };

				this.contextMenuService.showContextMenu({
					getAnchor: () => anchor,
					getActions: () => this.getContextMenuActions(newCommentInfos, range),
					getActionsContext: () => newCommentInfos.length ? newCommentInfos[0] : undefined,
					onHide: () => { this._addInProgress = false; }
				});

				return Promise.resolve();
			} else {
				const picks = this.getCommentProvidersQuickPicks(newCommentInfos);
				return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickCommentService', "Select Comment Provider"), matchOnDescription: true }).then(pick => {
					if (!pick) {
						return;
					}

					const commentInfos = newCommentInfos.filter(info => info.ownerId === pick.id);

					if (commentInfos.length) {
						const { ownerId } = commentInfos[0];
						this.addCommentAtLine2(range, ownerId);
					}
				}).then(() => {
					this._addInProgress = false;
				});
			}
		} else {
			const { ownerId } = newCommentInfos[0]!;
			this.addCommentAtLine2(range, ownerId);
		}

		return Promise.resolve();
	}


private getCommentProvidersQuickPicks(commentInfos: { ownerId: string; extensionId: string | undefined; label: string | undefined; commentingRangesInfo: languages.CommentingRanges | undefined }[]) {
		const picks: QuickPickInput[] = commentInfos.map((commentInfo) => {
			const { ownerId, extensionId, label } = commentInfo;

			return <IQuickPickItem>{
				label: label || extensionId,
				id: ownerId
			};
		});

		return picks;
	}


private getContextMenuActions(commentInfos: { ownerId: string; extensionId: string | undefined; label: string | undefined; commentingRangesInfo: languages.CommentingRanges }[], commentRange: Range): IAction[] {
		const actions: IAction[] = [];

		commentInfos.forEach(commentInfo => {
			const { ownerId, extensionId, label } = commentInfo;

			actions.push(new Action(
				'addCommentThread',
				`${label || extensionId}`,
				undefined,
				true,
				() => {
					this.addCommentAtLine2(commentRange, ownerId);
					return Promise.resolve();
				}
			));
		});
		return actions;
	}


public addCommentAtLine2(range: Range, ownerId: string) {
		this.commentService.createCommentThreadTemplate(ownerId, this.editor.getModel()!.uri, range);
		this.processNextThreadToAdd();
		return;
	}


private setComments(commentInfos: ICommentInfo[]): void {
		if (!this.editor) {
			return;
		}

		this._commentInfos = commentInfos;
		let lineDecorationsWidth: number = this.editor.getLayoutInfo().decorationsWidth;

		if (this._commentInfos.some(info => Boolean(info.commentingRanges && (Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges.ranges).length))) {
			if (!this._commentingRangeSpaceReserved) {
				this._commentingRangeSpaceReserved = true;
				let extraEditorClassName: string[] = [];
				const configuredExtraClassName = this.editor.getRawOptions().extraEditorClassName;
				if (configuredExtraClassName) {
					extraEditorClassName = configuredExtraClassName.split(' ');
				}

				const options = this.editor.getOptions();
				if (options.get(EditorOption.folding)) {
					lineDecorationsWidth -= 16;
				}
				lineDecorationsWidth += 9;
				extraEditorClassName.push('inline-comment');
				this.editor.updateOptions({
					extraEditorClassName: extraEditorClassName.join(' '),
					lineDecorationsWidth: lineDecorationsWidth
				});

				// we only update the lineDecorationsWidth property but keep the width of the whole editor.
				const originalLayoutInfo = this.editor.getLayoutInfo();

				this.editor.layout({
					width: originalLayoutInfo.width,
					height: originalLayoutInfo.height
				});
			}
		}

		// create viewzones
		this.removeCommentWidgetsAndStoreCache();

		this._commentInfos.forEach(info => {
			let providerCacheStore = this._pendingCommentCache[info.owner];
			info.threads = info.threads.filter(thread => !thread.isDisposed);
			info.threads.forEach(thread => {
				let pendingComment: string | null = null;
				if (providerCacheStore) {
					pendingComment = providerCacheStore[thread.threadId!];
				}

				if (pendingComment) {
					thread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded;
				}

				this.displayCommentThread(info.owner, thread, pendingComment);
			});
		});

		this._commentingRangeDecorator.update(this.editor, this._commentInfos);
	}


public closeWidget(): void {
		if (this._commentWidgets) {
			this._commentWidgets.forEach(widget => widget.hide());
		}

		this.editor.focus();
		this.editor.revealRangeInCenter(this.editor.getSelection()!);
	}


private removeCommentWidgetsAndStoreCache() {
		if (this._commentWidgets) {
			this._commentWidgets.forEach(zone => {
				let pendingComment = zone.getPendingComment();
				let providerCacheStore = this._pendingCommentCache[zone.owner];

				if (pendingComment) {
					if (!providerCacheStore) {
						this._pendingCommentCache[zone.owner] = {};
					}

					this._pendingCommentCache[zone.owner][zone.commentThread.threadId!] = pendingComment;
				} else {
					if (providerCacheStore) {
						delete providerCacheStore[zone.commentThread.threadId!];
					}
				}

				zone.dispose();
			});
		}

		this._commentWidgets = [];
	}


public hasComments(): boolean {
		return !!this._commentWidgets.length;
	}


public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
		let controller = CommentController.get(editor);
		if (controller) {
			controller.nextCommentThread();
		}
	}


public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
		let controller = CommentController.get(editor);
		if (controller) {
			controller.previousCommentThread();
		}
	}


({
  className,
  stop,
  color,
  onChange,
  onFocus,
  onRemove,
  globalMin,
  globalMax,
  localMin,
  localMax,
  min,
  max,
  isRangeMin = false,
  isRangeMax = false,
  parentRef,
  colorPickerMode,
  colorPickerShowAlpha,
  colorPickerSwatches,
  disabled,
  readOnly,
  isPopoverOpen,
  openPopover,
  closePopover,
  'data-index': dataIndex,
  'aria-valuetext': ariaValueText,
  valueInputProps = {},
}) => {
  const background = useMemo(() => {
    const chromaColor = getChromaColor(color, colorPickerShowAlpha);
    return chromaColor ? chromaColor.css() : undefined;
  }, [color, colorPickerShowAlpha]);
  const [hasFocus, setHasFocus] = useState(isPopoverOpen);
  const [colorIsInvalid, setColorIsInvalid] = useState(
    isColorInvalid(color, colorPickerShowAlpha)
  );
  const [stopIsInvalid, setStopIsInvalid] = useState(isStopInvalid(stop));
  const [numberInputRef, setNumberInputRef] = useState<HTMLInputElement | null>(
    null
  );
  const popoverRef = useRef<EuiPopover>(null);

  useEffect(() => {
    if (isPopoverOpen && popoverRef && popoverRef.current) {
      popoverRef.current.positionPopoverFixed();
    }
  }, [isPopoverOpen, stop]);

  const getStopFromMouseLocationFn = (location: { x: number; y: number }) => {
    // Guard against `null` ref in usage
    return getStopFromMouseLocation(location, parentRef!, globalMin, globalMax);
  };

  const getPositionFromStopFn = (stop: ColorStop['stop']) => {
    // Guard against `null` ref in usage
    return getPositionFromStop(stop, parentRef!, globalMin, globalMax);
  };

  const handleOnRemove = () => {
    if (onRemove) {
      closePopover();
      onRemove();
    }
  };

  const handleFocus = () => {
    setHasFocus(true);
    if (onFocus) {
      onFocus();
    }
  };

  const setHasFocusTrue = () => setHasFocus(true);
  const setHasFocusFalse = () => setHasFocus(false);

  const handleColorChange = (value: ColorStop['color']) => {
    setColorIsInvalid(isColorInvalid(value, colorPickerShowAlpha));
    onChange({ stop, color: value });
  };

  const handleStopChange = (value: ColorStop['stop']) => {
    const willBeInvalid = value > localMax || value < localMin;

    if (willBeInvalid) {
      if (value > localMax) {
        value = localMax;
      }
      if (value < localMin) {
        value = localMin;
      }
    }
    setStopIsInvalid(isStopInvalid(value));
    onChange({ stop: value, color });
  };

  const handleStopInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    let value = parseFloat(e.target.value);

    const willBeInvalid = value > globalMax || value < globalMin;

    if (willBeInvalid) {
      if (value > globalMax && max != null) {
        value = globalMax;
      }
      if (value < globalMin && min != null) {
        value = globalMin;
      }
    }

    setStopIsInvalid(isStopInvalid(value));
    onChange({ stop: value, color });
  };

  const handlePointerChange = (
    location: { x: number; y: number },
    isFirstInteraction?: boolean
  ) => {
    if (isFirstInteraction) return; // Prevents change on the initial MouseDown event
    if (parentRef == null) {
      return;
    }
    const newStop = getStopFromMouseLocationFn(location);
    handleStopChange(newStop);
  };

  const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
    switch (event.key) {
      case keys.ENTER:
        event.preventDefault();
        openPopover();
        break;

      case keys.ARROW_LEFT:
        event.preventDefault();
        if (readOnly) return;
        handleStopChange(stop - 1);
        break;

      case keys.ARROW_RIGHT:
        event.preventDefault();
        if (readOnly) return;
        handleStopChange(stop + 1);
        break;
    }
  };

  const [handleMouseDown, handleInteraction] = useMouseMove<HTMLButtonElement>(
    handlePointerChange
  );

  const handleOnMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
    if (!readOnly) {
      handleMouseDown(e);
    }
    openPopover();
  };

  const handleTouchInteraction = (e: React.TouchEvent<HTMLButtonElement>) => {
    if (!readOnly) {
      handleInteraction(e);
    }
  };

  const handleTouchStart = (e: React.TouchEvent<HTMLButtonElement>) => {
    handleTouchInteraction(e);
    if (!isPopoverOpen) {
      openPopover();
    }
  };

  const classes = classNames(
    'euiColorStopPopover',
    {
      'euiColorStopPopover-hasFocus': hasFocus || isPopoverOpen,
    },
    className
  );

  return (
    <EuiPopover
      ref={popoverRef}
      className={classes}
      anchorClassName="euiColorStopPopover__anchor"
      panelPaddingSize="s"
      isOpen={isPopoverOpen}
      closePopover={closePopover}
      initialFocus={numberInputRef || undefined}


() => {
    const chromaColor = getChromaColor(color, colorPickerShowAlpha);
    return chromaColor ? chromaColor.css() : undefined;
  }


() => {
    if (isPopoverOpen && popoverRef && popoverRef.current) {
      popoverRef.current.positionPopoverFixed();
    }
  }


(location: { x: number; y: number }) => {
    // Guard against `null` ref in usage
    return getStopFromMouseLocation(location, parentRef!, globalMin, globalMax);
  }


(stop: ColorStop['stop']) => {
    // Guard against `null` ref in usage
    return getPositionFromStop(stop, parentRef!, globalMin, globalMax);
  }


() => {
    if (onRemove) {
      closePopover();
      onRemove();
    }
  }


() => {
    setHasFocus(true);
    if (onFocus) {
      onFocus();
    }
  }


() => setHasFocus(true)


() => setHasFocus(false)


(value: ColorStop['color']) => {
    setColorIsInvalid(isColorInvalid(value, colorPickerShowAlpha));
    onChange({ stop, color: value });
  }


(value: ColorStop['stop']) => {
    const willBeInvalid = value > localMax || value < localMin;

    if (willBeInvalid) {
      if (value > localMax) {
        value = localMax;
      }
      if (value < localMin) {
        value = localMin;
      }
    }
    setStopIsInvalid(isStopInvalid(value));
    onChange({ stop: value, color });
  }


(e: React.ChangeEvent<HTMLInputElement>) => {
    let value = parseFloat(e.target.value);

    const willBeInvalid = value > globalMax || value < globalMin;

    if (willBeInvalid) {
      if (value > globalMax && max != null) {
        value = globalMax;
      }
      if (value < globalMin && min != null) {
        value = globalMin;
      }
    }

    setStopIsInvalid(isStopInvalid(value));
    onChange({ stop: value, color });
  }


(
    location: { x: number; y: number },
    isFirstInteraction?: boolean
  ) => {
    if (isFirstInteraction) return; // Prevents change on the initial MouseDown event
    if (parentRef == null) {
      return;
    }
    const newStop = getStopFromMouseLocationFn(location);
    handleStopChange(newStop);
  }


(event: React.KeyboardEvent<HTMLButtonElement>) => {
    switch (event.key) {
      case keys.ENTER:
        event.preventDefault();
        openPopover();
        break;

      case keys.ARROW_LEFT:
        event.preventDefault();
        if (readOnly) return;
        handleStopChange(stop - 1);
        break;

      case keys.ARROW_RIGHT:
        event.preventDefault();
        if (readOnly) return;
        handleStopChange(stop + 1);
        break;
    }
  }


(e: React.MouseEvent<HTMLButtonElement>) => {
    if (!readOnly) {
      handleMouseDown(e);
    }
    openPopover();
  }


(e: React.TouchEvent<HTMLButtonElement>) => {
    if (!readOnly) {
      handleInteraction(e);
    }
  }


(e: React.TouchEvent<HTMLButtonElement>) => {
    handleTouchInteraction(e);
    if (!isPopoverOpen) {
      openPopover();
    }
  }


export interface ColorStop {
  stop: number;
  color: string;
}


interface EuiColorStopThumbProps extends CommonProps, ColorStop {
  className?: string;
  onChange: (colorStop: ColorStop) => void;
  onFocus?: () => void;
  onRemove?: () => void;
  globalMin: number;
  globalMax: number;
  localMin: number;
  localMax: number;
  min?: number;
  max?: number;
  isRangeMin?: boolean;
  isRangeMax?: boolean;
  parentRef?: HTMLDivElement | null;
  colorPickerMode: EuiColorPickerProps['mode'];
  colorPickerShowAlpha?: EuiColorPickerProps['showAlpha'];
  colorPickerSwatches?: EuiColorPickerProps['swatches'];
  disabled?: boolean;
  readOnly?: boolean;
  isPopoverOpen: boolean;
  openPopover: () => void;
  closePopover: () => void;
  'data-index'?: string;
  'aria-valuetext'?: string;
  valueInputProps?: Partial<EuiFieldNumberProps>;
}


isStopInvalid(stop)


() => {
    return (
        <div css={containerStyles}>
            <Global styles={globalStyles} />
            <Router>
                <Redirect from="/" to="/page/0" />

                <MerchantListView path="/page/:pageId" />
                <MerchantView path="/merchant/:merchantId" />
                <AddMerchantView path="/add-merchant" />
            </Router>
        </div>


export enum Status {
    Online = "online",
    DoNotDisturb = "dnd",
    Idle = "idle",
    AFK = "idle",
    Invisible = "invisible",
    Offline = "offline",
}


(txtype:number, ...args:Array<any>):BaseTx => {
  if (txtype === AVMConstants.BASETX) {
    return new BaseTx(...args);
  } else if (txtype === AVMConstants.CREATEASSETTX) {
    return new CreateAssetTx(...args);
  } else if (txtype === AVMConstants.OPERATIONTX) {
    return new OperationTx(...args);
  } else if (txtype === AVMConstants.IMPORTTX) {
    return new ImportTx(...args);
  } else if (txtype === AVMConstants.EXPORTTX) {
    return new ExportTx(...args);
  }
  /* istanbul ignore next */
  throw new Error("Error - SelectTxClass: unknown txtype");
}


export class UnsignedTx extends StandardUnsignedTx<KeyPair, KeyChain, BaseTx> {
  protected _typeName = "UnsignedTx";
  protected _typeID = undefined;

  //serialize is inherited

  deserialize(fields:object, encoding:SerializedEncoding = "hex") {
    super.deserialize(fields, encoding);
    this.transaction = SelectTxClass(fields["transaction"]["_typeID"]);
    this.transaction.deserialize(fields["transaction"], encoding);
  }

  getTransaction():BaseTx{
    return this.transaction as BaseTx;
  }

  fromBuffer(bytes:Buffer, offset:number = 0):number {
    this.codecid = bintools.copyFrom(bytes, offset, offset + 2).readUInt16BE(0);
    offset += 2;
    const txtype:number = bintools.copyFrom(bytes, offset, offset + 4).readUInt32BE(0);
    offset += 4;
    this.transaction = SelectTxClass(txtype);
    return this.transaction.fromBuffer(bytes, offset);
  }

  /**
   * Signs this [[UnsignedTx]] and returns signed [[StandardTx]]
   *
   * @param kc An [[KeyChain]] used in signing
   *
   * @returns A signed [[StandardTx]]
   */
  sign(kc:KeyChain):Tx {
    const txbuff = this.toBuffer();
    const msg:Buffer = Buffer.from(createHash('sha256').update(txbuff).digest());
    const sigs:Array<Credential> = this.transaction.sign(msg, kc);
    return new Tx(this, sigs);
  }

}


export class Tx extends StandardTx<KeyPair, KeyChain, UnsignedTx> {
  protected _typeName = "Tx";
  protected _typeID = undefined;

  //serialize is inherited

  deserialize(fields:object, encoding:SerializedEncoding = "hex") {
    super.deserialize(fields, encoding);
    this.unsignedTx = new UnsignedTx();
    this.unsignedTx.deserialize(fields["unsignedTx"], encoding);
    this.credentials = [];
    for(let i = 0; i < fields["credentials"].length; i++){
      const cred:Credential = SelectCredentialClass(fields["credentials"][i]["_typeID"]);
      cred.deserialize(fields["credentials"][i], encoding);
      this.credentials.push(cred);
    }
  }

  /**
   * Takes a {@link https://github.com/feross/buffer|Buffer} containing an [[Tx]], parses it, populates the class, and returns the length of the Tx in bytes.
   *
   * @param bytes A {@link https://github.com/feross/buffer|Buffer} containing a raw [[Tx]]
   * @param offset A number representing the starting point of the bytes to begin parsing
   *
   * @returns The length of the raw [[Tx]]
   */
  fromBuffer(bytes:Buffer, offset:number = 0):number {
    this.unsignedTx = new UnsignedTx();
    offset = this.unsignedTx.fromBuffer(bytes, offset);
    const numcreds:number = bintools.copyFrom(bytes, offset, offset + 4).readUInt32BE(0);
    offset += 4;
    this.credentials = [];
    for (let i = 0; i < numcreds; i++) {
      const credid:number = bintools.copyFrom(bytes, offset, offset + 4).readUInt32BE(0);
      offset += 4;
      const cred:Credential = SelectCredentialClass(credid);
      offset = cred.fromBuffer(bytes, offset);
      this.credentials.push(cred);
    }
    return offset;
  }

}


//serialize is inherited
deserialize(fields:object, encoding:SerializedEncoding = "hex") {
    super.deserialize(fields, encoding);
    this.transaction = SelectTxClass(fields["transaction"]["_typeID"]);
    this.transaction.deserialize(fields["transaction"], encoding);
  }


getTransaction():BaseTx{
    return this.transaction as BaseTx;
  }


fromBuffer(bytes:Buffer, offset:number = 0):number {
    this.codecid = bintools.copyFrom(bytes, offset, offset + 2).readUInt16BE(0);
    offset += 2;
    const txtype:number = bintools.copyFrom(bytes, offset, offset + 4).readUInt32BE(0);
    offset += 4;
    this.transaction = SelectTxClass(txtype);
    return this.transaction.fromBuffer(bytes, offset);
  }


/**
   * Signs this [[UnsignedTx]] and returns signed [[StandardTx]]
   *
   * @param kc An [[KeyChain]] used in signing
   *
   * @returns A signed [[StandardTx]]
   */
sign(kc:KeyChain):Tx {
    const txbuff = this.toBuffer();
    const msg:Buffer = Buffer.from(createHash('sha256').update(txbuff).digest());
    const sigs:Array<Credential> = this.transaction.sign(msg, kc);
    return new Tx(this, sigs);
  }


//serialize is inherited
deserialize(fields:object, encoding:SerializedEncoding = "hex") {
    super.deserialize(fields, encoding);
    this.unsignedTx = new UnsignedTx();
    this.unsignedTx.deserialize(fields["unsignedTx"], encoding);
    this.credentials = [];
    for(let i = 0; i < fields["credentials"].length; i++){
      const cred:Credential = SelectCredentialClass(fields["credentials"][i]["_typeID"]);
      cred.deserialize(fields["credentials"][i], encoding);
      this.credentials.push(cred);
    }
  }


/**
   * Takes a {@link https://github.com/feross/buffer|Buffer} containing an [[Tx]], parses it, populates the class, and returns the length of the Tx in bytes.
   *
   * @param bytes A {@link https://github.com/feross/buffer|Buffer} containing a raw [[Tx]]
   * @param offset A number representing the starting point of the bytes to begin parsing
   *
   * @returns The length of the raw [[Tx]]
   */
fromBuffer(bytes:Buffer, offset:number = 0):number {
    this.unsignedTx = new UnsignedTx();
    offset = this.unsignedTx.fromBuffer(bytes, offset);
    const numcreds:number = bintools.copyFrom(bytes, offset, offset + 4).readUInt32BE(0);
    offset += 4;
    this.credentials = [];
    for (let i = 0; i < numcreds; i++) {
      const credid:number = bintools.copyFrom(bytes, offset, offset + 4).readUInt32BE(0);
      offset += 4;
      const cred:Credential = SelectCredentialClass(credid);
      offset = cred.fromBuffer(bytes, offset);
      this.credentials.push(cred);
    }
    return offset;
  }


(predeterminado: 127.0.0.1)


(predeterminado: 100)


/**
 * Returns whether the given Space is reserved or not.
 *
 * @param space the space
 * @returns boolean
 */
export function isReservedSpace(space?: Partial<Space> | null): boolean {
  return get(space, '_reserved', false);
}


() => {
			const visibleEditor = _editors.getVisibleTextEditors();
			for (const value of this._insets.values()) {
				if (visibleEditor.indexOf(value.editor) < 0) {
					value.inset.dispose(); // will remove from `this._insets`
				}
			}
		}


value => value.inset.dispose()


export class ExtHostEditorInsets implements ExtHostEditorInsetsShape {

	private _handlePool = 0;
	private _disposables = new DisposableStore();
	private _insets = new Map<number, { editor: vscode.TextEditor, inset: vscode.WebviewEditorInset, onDidReceiveMessage: Emitter<any> }>();

	constructor(
		private readonly _proxy: MainThreadEditorInsetsShape,
		private readonly _editors: ExtHostEditors,
		private readonly _initData: WebviewInitData
	) {

		// dispose editor inset whenever the hosting editor goes away
		this._disposables.add(_editors.onDidChangeVisibleTextEditors(() => {
			const visibleEditor = _editors.getVisibleTextEditors();
			for (const value of this._insets.values()) {
				if (visibleEditor.indexOf(value.editor) < 0) {
					value.inset.dispose(); // will remove from `this._insets`
				}
			}
		}));
	}

	dispose(): void {
		this._insets.forEach(value => value.inset.dispose());
		this._disposables.dispose();
	}

	createWebviewEditorInset(editor: vscode.TextEditor, line: number, height: number, options: vscode.WebviewOptions | undefined, extension: IExtensionDescription): vscode.WebviewEditorInset {

		let apiEditor: ExtHostTextEditor | undefined;
		for (const candidate of this._editors.getVisibleTextEditors(true)) {
			if (candidate.value === editor) {
				apiEditor = <ExtHostTextEditor>candidate;
				break;
			}
		}
		if (!apiEditor) {
			throw new Error('not a visible editor');
		}

		const that = this;
		const handle = this._handlePool++;
		const onDidReceiveMessage = new Emitter<any>();
		const onDidDispose = new Emitter<void>();

		const webview = new class implements vscode.Webview {

			private readonly _uuid = generateUuid();
			private _html: string = '';
			private _options: vscode.WebviewOptions = Object.create(null);

			asWebviewUri(resource: vscode.Uri): vscode.Uri {
				const remoteAuthority = that._initData.remote.isRemote
					? that._initData.remote.authority
					: undefined;
				return asWebviewUri(this._uuid, resource, remoteAuthority);
			}

			get cspSource(): string {
				return webviewGenericCspSource;
			}

			set options(value: vscode.WebviewOptions) {
				this._options = value;
				that._proxy.$setOptions(handle, value);
			}

			get options(): vscode.WebviewOptions {
				return this._options;
			}

			set html(value: string) {
				this._html = value;
				that._proxy.$setHtml(handle, value);
			}

			get html(): string {
				return this._html;
			}

			get onDidReceiveMessage(): vscode.Event<any> {
				return onDidReceiveMessage.event;
			}

			postMessage(message: any): Thenable<boolean> {
				return that._proxy.$postMessage(handle, message);
			}
		};

		const inset = new class implements vscode.WebviewEditorInset {

			readonly editor: vscode.TextEditor = editor;
			readonly line: number = line;
			readonly height: number = height;
			readonly webview: vscode.Webview = webview;
			readonly onDidDispose: vscode.Event<void> = onDidDispose.event;

			dispose(): void {
				if (that._insets.has(handle)) {
					that._insets.delete(handle);
					that._proxy.$disposeEditorInset(handle);
					onDidDispose.fire();

					// final cleanup
					onDidDispose.dispose();
					onDidReceiveMessage.dispose();
				}
			}
		};

		this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.value.document.uri, line + 1, height, options || {}, extension.identifier, extension.extensionLocation);
		this._insets.set(handle, { editor, inset, onDidReceiveMessage });

		return inset;
	}

	$onDidDispose(handle: number): void {
		const value = this._insets.get(handle);
		if (value) {
			value.inset.dispose();
		}
	}

	$onDidReceiveMessage(handle: number, message: any): void {
		const value = this._insets.get(handle);
		if (value) {
			value.onDidReceiveMessage.fire(message);
		}
	}
}


dispose(): void {
		this._insets.forEach(value => value.inset.dispose());
		this._disposables.dispose();
	}


createWebviewEditorInset(editor: vscode.TextEditor, line: number, height: number, options: vscode.WebviewOptions | undefined, extension: IExtensionDescription): vscode.WebviewEditorInset {

		let apiEditor: ExtHostTextEditor | undefined;
		for (const candidate of this._editors.getVisibleTextEditors(true)) {
			if (candidate.value === editor) {
				apiEditor = <ExtHostTextEditor>candidate;
				break;
			}
		}
		if (!apiEditor) {
			throw new Error('not a visible editor');
		}

		const that = this;
		const handle = this._handlePool++;
		const onDidReceiveMessage = new Emitter<any>();
		const onDidDispose = new Emitter<void>();

		const webview = new class implements vscode.Webview {

			private readonly _uuid = generateUuid();
			private _html: string = '';
			private _options: vscode.WebviewOptions = Object.create(null);

			asWebviewUri(resource: vscode.Uri): vscode.Uri {
				const remoteAuthority = that._initData.remote.isRemote
					? that._initData.remote.authority
					: undefined;
				return asWebviewUri(this._uuid, resource, remoteAuthority);
			}

			get cspSource(): string {
				return webviewGenericCspSource;
			}

			set options(value: vscode.WebviewOptions) {
				this._options = value;
				that._proxy.$setOptions(handle, value);
			}

			get options(): vscode.WebviewOptions {
				return this._options;
			}

			set html(value: string) {
				this._html = value;
				that._proxy.$setHtml(handle, value);
			}

			get html(): string {
				return this._html;
			}

			get onDidReceiveMessage(): vscode.Event<any> {
				return onDidReceiveMessage.event;
			}

			postMessage(message: any): Thenable<boolean> {
				return that._proxy.$postMessage(handle, message);
			}
		};

		const inset = new class implements vscode.WebviewEditorInset {

			readonly editor: vscode.TextEditor = editor;
			readonly line: number = line;
			readonly height: number = height;
			readonly webview: vscode.Webview = webview;
			readonly onDidDispose: vscode.Event<void> = onDidDispose.event;

			dispose(): void {
				if (that._insets.has(handle)) {
					that._insets.delete(handle);
					that._proxy.$disposeEditorInset(handle);
					onDidDispose.fire();

					// final cleanup
					onDidDispose.dispose();
					onDidReceiveMessage.dispose();
				}
			}
		};

		this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.value.document.uri, line + 1, height, options || {}, extension.identifier, extension.extensionLocation);
		this._insets.set(handle, { editor, inset, onDidReceiveMessage });

		return inset;
	}


asWebviewUri(resource: vscode.Uri): vscode.Uri {
				const remoteAuthority = that._initData.remote.isRemote
					? that._initData.remote.authority
					: undefined;
				return asWebviewUri(this._uuid, resource, remoteAuthority);
			}


postMessage(message: any): Thenable<boolean> {
				return that._proxy.$postMessage(handle, message);
			}