diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts
index 2da1e20df..2ad150784 100644
--- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts
+++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts
@@ -29,6 +29,7 @@ export class BurnBootloader extends CoreServiceContribution {
   }
 
   private async burnBootloader(): Promise<void> {
+    this.clearVisibleNotification();
     const options = await this.options();
     try {
       await this.doWithProgress({
diff --git a/arduino-ide-extension/src/browser/contributions/compiler-errors.ts b/arduino-ide-extension/src/browser/contributions/compiler-errors.ts
index b3946bfd2..58e8dcff1 100644
--- a/arduino-ide-extension/src/browser/contributions/compiler-errors.ts
+++ b/arduino-ide-extension/src/browser/contributions/compiler-errors.ts
@@ -4,11 +4,13 @@ import {
   Disposable,
   DisposableCollection,
   Emitter,
+  MaybeArray,
   MaybePromise,
   nls,
   notEmpty,
 } from '@theia/core';
 import { ApplicationShell, FrontendApplication } from '@theia/core/lib/browser';
+import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
 import URI from '@theia/core/lib/common/uri';
 import { inject, injectable } from '@theia/core/shared/inversify';
 import {
@@ -28,14 +30,15 @@ import * as monaco from '@theia/monaco-editor-core';
 import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
 import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
 import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
+import { OutputUri } from '@theia/output/lib/common/output-uri';
 import { CoreError } from '../../common/protocol/core-service';
 import { ErrorRevealStrategy } from '../arduino-preferences';
-import { InoSelector } from '../ino-selectors';
-import { fullRange } from '../utils/monaco';
+import { ArduinoOutputSelector, InoSelector } from '../selectors';
 import { Contribution } from './contribution';
 import { CoreErrorHandler } from './core-error-handler';
+import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
 
-interface ErrorDecoration {
+interface ErrorDecorationRef {
   /**
    * This is the unique ID of the decoration given by `monaco`.
    */
@@ -45,72 +48,89 @@ interface ErrorDecoration {
    */
   readonly uri: string;
 }
+export namespace ErrorDecorationRef {
+  export function is(arg: unknown): arg is ErrorDecorationRef {
+    if (typeof arg === 'object') {
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      const object = arg as any;
+      return (
+        'uri' in object &&
+        typeof object['uri'] === 'string' &&
+        'id' in object &&
+        typeof object['id'] === 'string'
+      );
+    }
+    return false;
+  }
+  export function sameAs(
+    left: ErrorDecorationRef,
+    right: ErrorDecorationRef
+  ): boolean {
+    return left.id === right.id && left.uri === right.uri;
+  }
+}
+
+interface ErrorDecoration extends ErrorDecorationRef {
+  /**
+   * The range of the error location the error in the compiler output from the CLI.
+   */
+  readonly rangesInOutput: monaco.Range[];
+}
 namespace ErrorDecoration {
   export function rangeOf(
-    { id, uri }: ErrorDecoration,
-    editorProvider: (uri: string) => Promise<MonacoEditor | undefined>
-  ): Promise<monaco.Range | undefined>;
-  export function rangeOf(
-    { id, uri }: ErrorDecoration,
-    editorProvider: MonacoEditor
+    editorOrModel: MonacoEditor | ITextModel | undefined,
+    decorations: ErrorDecoration
   ): monaco.Range | undefined;
   export function rangeOf(
-    { id, uri }: ErrorDecoration,
-    editorProvider:
-      | ((uri: string) => Promise<MonacoEditor | undefined>)
-      | MonacoEditor
-  ): MaybePromise<monaco.Range | undefined> {
-    if (editorProvider instanceof MonacoEditor) {
-      const control = editorProvider.getControl();
-      const model = control.getModel();
-      if (model) {
-        return control
-          .getDecorationsInRange(fullRange(model))
-          ?.find(({ id: candidateId }) => id === candidateId)?.range;
+    editorOrModel: MonacoEditor | ITextModel | undefined,
+    decorations: ErrorDecoration[]
+  ): (monaco.Range | undefined)[];
+  export function rangeOf(
+    editorOrModel: MonacoEditor | ITextModel | undefined,
+    decorations: ErrorDecoration | ErrorDecoration[]
+  ): MaybePromise<MaybeArray<monaco.Range | undefined>> {
+    if (editorOrModel) {
+      const allDecorations = getAllDecorations(editorOrModel);
+      if (allDecorations) {
+        if (Array.isArray(decorations)) {
+          return decorations.map(({ id: decorationId }) =>
+            findRangeOf(decorationId, allDecorations)
+          );
+        } else {
+          return findRangeOf(decorations.id, allDecorations);
+        }
       }
-      return undefined;
     }
-    return editorProvider(uri).then((editor) => {
-      if (editor) {
-        return rangeOf({ id, uri }, editor);
-      }
-      return undefined;
-    });
+    return Array.isArray(decorations)
+      ? decorations.map(() => undefined)
+      : undefined;
   }
-
-  // export async function rangeOf(
-  //   { id, uri }: ErrorDecoration,
-  //   editorProvider:
-  //     | ((uri: string) => Promise<MonacoEditor | undefined>)
-  //     | MonacoEditor
-  // ): Promise<monaco.Range | undefined> {
-  //   const editor =
-  //     editorProvider instanceof MonacoEditor
-  //       ? editorProvider
-  //       : await editorProvider(uri);
-  //   if (editor) {
-  //     const control = editor.getControl();
-  //     const model = control.getModel();
-  //     if (model) {
-  //       return control
-  //         .getDecorationsInRange(fullRange(model))
-  //         ?.find(({ id: candidateId }) => id === candidateId)?.range;
-  //     }
-  //   }
-  //   return undefined;
-  // }
-  export function sameAs(
-    left: ErrorDecoration,
-    right: ErrorDecoration
-  ): boolean {
-    return left.id === right.id && left.uri === right.uri;
+  function findRangeOf(
+    decorationId: string,
+    allDecorations: { id: string; range?: monaco.Range }[]
+  ): monaco.Range | undefined {
+    return allDecorations.find(
+      ({ id: candidateId }) => candidateId === decorationId
+    )?.range;
+  }
+  function getAllDecorations(
+    editorOrModel: MonacoEditor | ITextModel
+  ): { id: string; range?: monaco.Range }[] {
+    if (editorOrModel instanceof MonacoEditor) {
+      const model = editorOrModel.getControl().getModel();
+      if (!model) {
+        return [];
+      }
+      return model.getAllDecorations();
+    }
+    return editorOrModel.getAllDecorations();
   }
 }
 
 @injectable()
 export class CompilerErrors
   extends Contribution
-  implements monaco.languages.CodeLensProvider
+  implements monaco.languages.CodeLensProvider, monaco.languages.LinkProvider
 {
   @inject(EditorManager)
   private readonly editorManager: EditorManager;
@@ -119,11 +139,14 @@ export class CompilerErrors
   private readonly p2m: ProtocolToMonacoConverter;
 
   @inject(MonacoToProtocolConverter)
-  private readonly mp2: MonacoToProtocolConverter;
+  private readonly m2p: MonacoToProtocolConverter;
 
   @inject(CoreErrorHandler)
   private readonly coreErrorHandler: CoreErrorHandler;
 
+  private revealStrategy = ErrorRevealStrategy.Default;
+  private experimental = false;
+
   private readonly errors: ErrorDecoration[] = [];
   private readonly onDidChangeEmitter = new monaco.Emitter<this>();
   private readonly currentErrorDidChangEmitter = new Emitter<ErrorDecoration>();
@@ -131,8 +154,8 @@ export class CompilerErrors
     this.currentErrorDidChangEmitter.event;
   private readonly toDisposeOnCompilerErrorDidChange =
     new DisposableCollection();
+
   private shell: ApplicationShell | undefined;
-  private revealStrategy = ErrorRevealStrategy.Default;
   private currentError: ErrorDecoration | undefined;
   private get currentErrorIndex(): number {
     const current = this.currentError;
@@ -140,46 +163,75 @@ export class CompilerErrors
       return -1;
     }
     return this.errors.findIndex((error) =>
-      ErrorDecoration.sameAs(error, current)
+      ErrorDecorationRef.sameAs(error, current)
     );
   }
 
   override onStart(app: FrontendApplication): void {
     this.shell = app.shell;
     monaco.languages.registerCodeLensProvider(InoSelector, this);
+    monaco.languages.registerLinkProvider(ArduinoOutputSelector, this);
     this.coreErrorHandler.onCompilerErrorsDidChange((errors) =>
-      this.filter(errors).then(this.handleCompilerErrorsDidChange.bind(this))
+      this.handleCompilerErrorsDidChange(errors)
     );
     this.onCurrentErrorDidChange(async (error) => {
-      const range = await ErrorDecoration.rangeOf(error, (uri) =>
-        this.monacoEditor(uri)
-      );
-      if (!range) {
+      const monacoEditor = await this.monacoEditor(error.uri);
+      const monacoRange = ErrorDecoration.rangeOf(monacoEditor, error);
+      if (!monacoRange) {
         console.warn(
           'compiler-errors',
           `Could not find range of decoration: ${error.id}`
         );
         return;
       }
+      const range = this.m2p.asRange(monacoRange);
       const editor = await this.revealLocationInEditor({
         uri: error.uri,
-        range: this.mp2.asRange(range),
+        range,
       });
       if (!editor) {
         console.warn(
           'compiler-errors',
           `Failed to mark error ${error.id} as the current one.`
         );
+      } else {
+        const monacoEditor = this.monacoEditor(editor);
+        if (monacoEditor) {
+          monacoEditor.cursor = range.start;
+        }
       }
     });
+  }
+
+  override onReady(): MaybePromise<void> {
     this.preferences.ready.then(() => {
-      this.preferences.onPreferenceChanged(({ preferenceName, newValue }) => {
-        if (preferenceName === 'arduino.compile.revealRange') {
-          this.revealStrategy = ErrorRevealStrategy.is(newValue)
-            ? newValue
-            : ErrorRevealStrategy.Default;
+      this.experimental = Boolean(
+        this.preferences['arduino.compile.experimental']
+      );
+      const strategy = this.preferences['arduino.compile.revealRange'];
+      this.revealStrategy = ErrorRevealStrategy.is(strategy)
+        ? strategy
+        : ErrorRevealStrategy.Default;
+      this.preferences.onPreferenceChanged(
+        ({ preferenceName, newValue, oldValue }) => {
+          if (newValue === oldValue) {
+            return;
+          }
+          switch (preferenceName) {
+            case 'arduino.compile.revealRange': {
+              this.revealStrategy = ErrorRevealStrategy.is(newValue)
+                ? newValue
+                : ErrorRevealStrategy.Default;
+              return;
+            }
+            case 'arduino.compile.experimental': {
+              this.experimental = Boolean(newValue);
+              this.onDidChangeEmitter.fire(this);
+              return;
+            }
+          }
         }
-      });
+      );
     });
   }
 
@@ -196,9 +248,13 @@ export class CompilerErrors
         }
         const nextError =
           this.errors[index === this.errors.length - 1 ? 0 : index + 1];
-        this.markAsCurrentError(nextError);
+        return this.markAsCurrentError(nextError, {
+          forceReselect: true,
+          reveal: true,
+        });
       },
-      isEnabled: () => !!this.currentError && this.errors.length > 1,
+      isEnabled: () =>
+        this.experimental && !!this.currentError && this.errors.length > 1,
     });
     registry.registerCommand(CompilerErrors.Commands.PREVIOUS_ERROR, {
       execute: () => {
@@ -212,9 +268,24 @@ export class CompilerErrors
         }
         const previousError =
           this.errors[index === 0 ? this.errors.length - 1 : index - 1];
-        this.markAsCurrentError(previousError);
+        return this.markAsCurrentError(previousError, {
+          forceReselect: true,
+          reveal: true,
+        });
       },
-      isEnabled: () => !!this.currentError && this.errors.length > 1,
+      isEnabled: () =>
+        this.experimental && !!this.currentError && this.errors.length > 1,
+    });
+    registry.registerCommand(CompilerErrors.Commands.MARK_AS_CURRENT, {
+      execute: (arg: unknown) => {
+        if (ErrorDecorationRef.is(arg)) {
+          return this.markAsCurrentError(
+            { id: arg.id, uri: new URI(arg.uri).toString() }, // Make sure the URI fragments are encoded. On Windows, `C:` is encoded as `C%3A`.
+            { forceReselect: true, reveal: true }
+          );
+        }
+      },
+      isEnabled: () => !!this.errors.length,
     });
   }
 
@@ -229,13 +300,13 @@ export class CompilerErrors
   ): Promise<monaco.languages.CodeLensList> {
     const lenses: monaco.languages.CodeLens[] = [];
     if (
+      this.experimental &&
       this.currentError &&
       this.currentError.uri === model.uri.toString() &&
       this.errors.length > 1
     ) {
-      const range = await ErrorDecoration.rangeOf(this.currentError, (uri) =>
-        this.monacoEditor(uri)
-      );
+      const monacoEditor = await this.monacoEditor(model.uri);
+      const range = ErrorDecoration.rangeOf(monacoEditor, this.currentError);
       if (range) {
         lenses.push(
           {
@@ -268,14 +339,81 @@ export class CompilerErrors
     };
   }
 
+  async provideLinks(
+    model: monaco.editor.ITextModel,
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    _token: monaco.CancellationToken
+  ): Promise<monaco.languages.ILinksList> {
+    const links: monaco.languages.ILink[] = [];
+    if (
+      model.uri.scheme === OutputUri.SCHEME &&
+      model.uri.path === '/Arduino'
+    ) {
+      links.push(
+        ...this.errors
+          .filter((decoration) => !!decoration.rangesInOutput.length)
+          .map(({ rangesInOutput, id, uri }) =>
+            rangesInOutput.map(
+              (range) =>
+                <monaco.languages.ILink>{
+                  range,
+                  url: monaco.Uri.parse(`command://`).with({
+                    query: JSON.stringify({ id, uri }),
+                    path: CompilerErrors.Commands.MARK_AS_CURRENT.id,
+                  }),
+                  tooltip: nls.localize(
+                    'arduino/editor/revealError',
+                    'Reveal Error'
+                  ),
+                }
+            )
+          )
+          .reduce((acc, curr) => acc.concat(curr), [])
+      );
+    } else {
+      console.warn('unexpected URI: ' + model.uri.toString());
+    }
+    return { links };
+  }
+
+  async resolveLink(
+    link: monaco.languages.ILink,
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    _token: monaco.CancellationToken
+  ): Promise<monaco.languages.ILink | undefined> {
+    if (!this.experimental) {
+      return undefined;
+    }
+    const { url } = link;
+    if (url) {
+      const candidateUri = new URI(
+        typeof url === 'string' ? url : url.toString()
+      );
+      const candidateId = candidateUri.path.toString();
+      const error = this.errors.find((error) => error.id === candidateId);
+      if (error) {
+        const monacoEditor = await this.monacoEditor(error.uri);
+        const range = ErrorDecoration.rangeOf(monacoEditor, error);
+        if (range) {
+          return {
+            range,
+            url: monaco.Uri.parse(error.uri),
+          };
+        }
+      }
+    }
+    return undefined;
+  }
+
   private async handleCompilerErrorsDidChange(
     errors: CoreError.ErrorLocation[]
   ): Promise<void> {
     this.toDisposeOnCompilerErrorDidChange.dispose();
-    const compilerErrorsPerResource = this.groupByResource(
-      await this.filter(errors)
+    const groupedErrors = this.groupBy(
+      errors,
+      (error: CoreError.ErrorLocation) => error.location.uri
     );
-    const decorations = await this.decorateEditors(compilerErrorsPerResource);
+    const decorations = await this.decorateEditors(groupedErrors);
     this.errors.push(...decorations.errors);
     this.toDisposeOnCompilerErrorDidChange.pushAll([
       Disposable.create(() => (this.errors.length = 0)),
@@ -283,17 +421,17 @@ export class CompilerErrors
       ...(await Promise.all([
         decorations.dispose,
         this.trackEditors(
-          compilerErrorsPerResource,
+          groupedErrors,
           (editor) =>
-            editor.editor.onSelectionChanged((selection) =>
+            editor.onSelectionChanged((selection) =>
               this.handleSelectionChange(editor, selection)
             ),
           (editor) =>
-            editor.onDidDispose(() =>
-              this.handleEditorDidDispose(editor.editor.uri.toString())
+            editor.onDispose(() =>
+              this.handleEditorDidDispose(editor.uri.toString())
             ),
           (editor) =>
-            editor.editor.onDocumentContentChanged((event) =>
+            editor.onDocumentContentChanged((event) =>
               this.handleDocumentContentChange(editor, event)
             )
         ),
@@ -301,22 +439,11 @@ export class CompilerErrors
     ]);
     const currentError = this.errors[0];
     if (currentError) {
-      await this.markAsCurrentError(currentError);
-    }
-  }
-
-  private async filter(
-    errors: CoreError.ErrorLocation[]
-  ): Promise<CoreError.ErrorLocation[]> {
-    if (!errors.length) {
-      return [];
-    }
-    await this.preferences.ready;
-    if (this.preferences['arduino.compile.experimental']) {
-      return errors;
+      await this.markAsCurrentError(currentError, {
+        forceReselect: true,
+        reveal: true,
+      });
     }
-    // Always shows maximum one error; hence the code lens navigation is unavailable.
-    return [errors[0]];
   }
 
   private async decorateEditors(
@@ -342,11 +469,11 @@ export class CompilerErrors
     uri: string,
     errors: CoreError.ErrorLocation[]
   ): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
-    const editor = await this.editorManager.getByUri(new URI(uri));
+    const editor = await this.monacoEditor(uri);
     if (!editor) {
       return { dispose: Disposable.NULL, errors: [] };
     }
-    const oldDecorations = editor.editor.deltaDecorations({
+    const oldDecorations = editor.deltaDecorations({
       oldDecorations: [],
       newDecorations: errors.map((error) =>
         this.compilerErrorDecoration(error.location.range)
@@ -355,13 +482,19 @@ export class CompilerErrors
     return {
       dispose: Disposable.create(() => {
         if (editor) {
-          editor.editor.deltaDecorations({
+          editor.deltaDecorations({
             oldDecorations,
             newDecorations: [],
           });
         }
       }),
-      errors: oldDecorations.map((id) => ({ id, uri })),
+      errors: oldDecorations.map((id, index) => ({
+        id,
+        uri,
+        rangesInOutput: errors[index].rangesInOutput.map((range) =>
+          this.p2m.asRange(range)
+        ),
+      })),
     };
   }
 
@@ -371,7 +504,7 @@ export class CompilerErrors
       options: {
         isWholeLine: true,
         className: 'compiler-error',
-        stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
+        stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
       },
     };
   }
@@ -379,11 +512,10 @@ export class CompilerErrors
   /**
    * Tracks the selection in all editors that have an error. If the editor selection overlaps one of the compiler error's range, mark as current error.
    */
-  private handleSelectionChange(editor: EditorWidget, selection: Range): void {
-    const monacoEditor = this.monacoEditor(editor);
-    if (!monacoEditor) {
-      return;
-    }
+  private handleSelectionChange(
+    monacoEditor: MonacoEditor,
+    selection: Range
+  ): void {
     const uri = monacoEditor.uri.toString();
     const monacoSelection = this.p2m.asRange(selection);
     console.log(
@@ -418,12 +550,13 @@ export class CompilerErrors
       console.trace('No match');
       return undefined;
     };
-    const error = this.errors
-      .filter((error) => error.uri === uri)
-      .map((error) => ({
-        error,
-        range: ErrorDecoration.rangeOf(error, monacoEditor),
-      }))
+    const errorsPerResource = this.errors.filter((error) => error.uri === uri);
+    const rangesPerResource = ErrorDecoration.rangeOf(
+      monacoEditor,
+      errorsPerResource
+    );
+    const error = rangesPerResource
+      .map((range, index) => ({ error: errorsPerResource[index], range }))
       .map(({ error, range }) => {
         if (range) {
           const priority = calculatePriority(range, monacoSelection);
@@ -464,66 +597,77 @@ export class CompilerErrors
   }
 
   /**
-   * If a document change "destroys" the range of the decoration, the decoration must be removed.
+   * If the text document changes in the line where compiler errors are, the compiler errors will be removed.
    */
   private handleDocumentContentChange(
-    editor: EditorWidget,
+    monacoEditor: MonacoEditor,
     event: TextDocumentChangeEvent
   ): void {
-    const monacoEditor = this.monacoEditor(editor);
-    if (!monacoEditor) {
-      return;
-    }
-    // A decoration location can be "destroyed", hence should be deleted when:
-    // - deleting range (start != end AND text is empty)
-    // - inserting text into range (start != end AND text is not empty)
-    // Filter unrelated delta changes to spare the CPU.
-    const relevantChanges = event.contentChanges.filter(
-      ({ range: { start, end } }) =>
-        start.line !== end.line || start.character !== end.character
+    const errorsPerResource = this.errors.filter(
+      (error) => error.uri === event.document.uri
     );
-    if (!relevantChanges.length) {
-      return;
+    let editorOrModel: MonacoEditor | ITextModel = monacoEditor;
+    const doc = event.document;
+    if (doc instanceof MonacoEditorModel) {
+      editorOrModel = doc.textEditorModel;
     }
-
-    const resolvedMarkers = this.errors
-      .filter((error) => error.uri === event.document.uri)
-      .map((error, index) => {
-        const range = ErrorDecoration.rangeOf(error, monacoEditor);
-        if (range) {
-          return { error, range, index };
-        }
-        return undefined;
-      })
-      .filter(notEmpty);
-
-    const decorationIdsToRemove = relevantChanges
+    const rangesPerResource = ErrorDecoration.rangeOf(
+      editorOrModel,
+      errorsPerResource
+    );
+    const resolvedDecorations = rangesPerResource.map((range, index) => ({
+      error: errorsPerResource[index],
+      range,
+    }));
+    const decoratorsToRemove = event.contentChanges
       .map(({ range }) => this.p2m.asRange(range))
-      .map((changeRange) =>
-        resolvedMarkers.filter(({ range: decorationRange }) =>
-          changeRange.containsRange(decorationRange)
-        )
+      .map((changedRange) =>
+        resolvedDecorations
+          .filter(({ range: decorationRange }) => {
+            if (!decorationRange) {
+              return false;
+            }
+            const affects =
+              changedRange.startLineNumber <= decorationRange.startLineNumber &&
+              changedRange.endLineNumber >= decorationRange.endLineNumber;
+            console.log(
+              'compiler-errors',
+              `decoration range: ${decorationRange.toString()}, change range: ${changedRange.toString()}, affects: ${affects}`
+            );
+            return affects;
+          })
+          .map(({ error }) => {
+            const index = this.errors.findIndex((candidate) =>
+              ErrorDecorationRef.sameAs(candidate, error)
+            );
+            return index !== -1 ? { error, index } : undefined;
+          })
+          .filter(notEmpty)
       )
       .reduce((acc, curr) => acc.concat(curr), [])
-      .map(({ error, index }) => {
-        this.errors.splice(index, 1);
-        return error.id;
-      });
-    if (!decorationIdsToRemove.length) {
-      return;
+      .sort((left, right) => left.index - right.index); // highest index last
+
+    if (decoratorsToRemove.length) {
+      let i = decoratorsToRemove.length;
+      while (i--) {
+        this.errors.splice(decoratorsToRemove[i].index, 1);
+      }
+      monacoEditor.getControl().deltaDecorations(
+        decoratorsToRemove.map(({ error }) => error.id),
+        []
+      );
+      this.onDidChangeEmitter.fire(this);
     }
-    monacoEditor.getControl().deltaDecorations(decorationIdsToRemove, []);
-    this.onDidChangeEmitter.fire(this);
   }
 
   private async trackEditors(
     errors: Map<string, CoreError.ErrorLocation[]>,
-    ...track: ((editor: EditorWidget) => Disposable)[]
+    ...track: ((editor: MonacoEditor) => Disposable)[]
   ): Promise<Disposable> {
     return new DisposableCollection(
       ...(await Promise.all(
         Array.from(errors.keys()).map(async (uri) => {
-          const editor = await this.editorManager.getByUri(new URI(uri));
+          const editor = await this.monacoEditor(uri);
           if (!editor) {
             return Disposable.NULL;
           }
@@ -533,15 +677,18 @@ export class CompilerErrors
     );
   }
 
-  private async markAsCurrentError(error: ErrorDecoration): Promise<void> {
+  private async markAsCurrentError(
+    ref: ErrorDecorationRef,
+    options?: { forceReselect?: boolean; reveal?: boolean }
+  ): Promise<void> {
     const index = this.errors.findIndex((candidate) =>
-      ErrorDecoration.sameAs(candidate, error)
+      ErrorDecorationRef.sameAs(candidate, ref)
     );
     if (index < 0) {
       console.warn(
         'compiler-errors',
         `Failed to mark error ${
-          error.id
+          ref.id
         } as the current one. Error is unknown. Known errors are: ${this.errors.map(
           ({ id }) => id
         )}`
@@ -550,15 +697,18 @@ export class CompilerErrors
     }
     const newError = this.errors[index];
     if (
+      options?.forceReselect ||
       !this.currentError ||
-      !ErrorDecoration.sameAs(this.currentError, newError)
+      !ErrorDecorationRef.sameAs(this.currentError, newError)
     ) {
       this.currentError = this.errors[index];
       console.log(
         'compiler-errors',
         `Current error changed to ${this.currentError.id}`
       );
-      this.currentErrorDidChangEmitter.fire(this.currentError);
+      if (options?.reveal) {
+        this.currentErrorDidChangEmitter.fire(this.currentError);
+      }
       this.onDidChangeEmitter.fire(this);
     }
   }
@@ -598,27 +748,28 @@ export class CompilerErrors
     return undefined;
   }
 
-  private groupByResource(
-    errors: CoreError.ErrorLocation[]
-  ): Map<string, CoreError.ErrorLocation[]> {
-    return errors.reduce((acc, curr) => {
-      const {
-        location: { uri },
-      } = curr;
-      let errors = acc.get(uri);
-      if (!errors) {
-        errors = [];
-        acc.set(uri, errors);
+  private groupBy<K, V>(
+    elements: V[],
+    extractKey: (element: V) => K
+  ): Map<K, V[]> {
+    return elements.reduce((acc, curr) => {
+      const key = extractKey(curr);
+      let values = acc.get(key);
+      if (!values) {
+        values = [];
+        acc.set(key, values);
       }
-      errors.push(curr);
+      values.push(curr);
       return acc;
-    }, new Map<string, CoreError.ErrorLocation[]>());
+    }, new Map<K, V[]>());
   }
 
   private monacoEditor(widget: EditorWidget): MonacoEditor | undefined;
-  private monacoEditor(uri: string): Promise<MonacoEditor | undefined>;
   private monacoEditor(
-    uriOrWidget: string | EditorWidget
+    uri: string | monaco.Uri
+  ): Promise<MonacoEditor | undefined>;
+  private monacoEditor(
+    uriOrWidget: string | monaco.Uri | EditorWidget
   ): MaybePromise<MonacoEditor | undefined> {
     if (uriOrWidget instanceof EditorWidget) {
       const editor = uriOrWidget.editor;
@@ -646,5 +797,8 @@ export namespace CompilerErrors {
     export const PREVIOUS_ERROR: Command = {
       id: 'arduino-editor-previous-error',
     };
+    export const MARK_AS_CURRENT: Command = {
+      id: 'arduino-editor-mark-as-current-error',
+    };
   }
 }
diff --git a/arduino-ide-extension/src/browser/contributions/contribution.ts b/arduino-ide-extension/src/browser/contributions/contribution.ts
index d3ae285f7..309a72dbc 100644
--- a/arduino-ide-extension/src/browser/contributions/contribution.ts
+++ b/arduino-ide-extension/src/browser/contributions/contribution.ts
@@ -59,6 +59,8 @@ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
 import { ExecuteWithProgress } from '../../common/protocol/progressible';
 import { BoardsServiceProvider } from '../boards/boards-service-provider';
 import { BoardsDataStore } from '../boards/boards-data-store';
+import { NotificationManager } from '../theia/messages/notifications-manager';
+import { MessageType } from '@theia/core/lib/common/message-service-protocol';
 
 export {
   Command,
@@ -186,6 +188,22 @@ export abstract class CoreServiceContribution extends SketchContribution {
   @inject(ResponseServiceClient)
   private readonly responseService: ResponseServiceClient;
 
+  @inject(NotificationManager)
+  private readonly notificationManager: NotificationManager;
+
+  /**
+   * This is the internal (Theia) ID of the notification that is currently visible.
+   * It's stored here as a field to be able to close it before executing any new core command (such as verify, upload, etc.)
+   */
+  private visibleNotificationId: string | undefined;
+
+  protected clearVisibleNotification(): void {
+    if (this.visibleNotificationId) {
+      this.notificationManager.clear(this.visibleNotificationId);
+      this.visibleNotificationId = undefined;
+    }
+  }
+
   protected handleError(error: unknown): void {
     this.tryToastErrorMessage(error);
   }
@@ -208,6 +226,7 @@ export abstract class CoreServiceContribution extends SketchContribution {
         'arduino/coreContribution/copyError',
         'Copy error messages'
       );
+      this.visibleNotificationId = this.notificationId(message, copyAction);
       this.messageService.error(message, copyAction).then(async (action) => {
         if (action === copyAction) {
           const content = await this.outputChannelManager.contentOfChannel(
@@ -241,6 +260,14 @@ export abstract class CoreServiceContribution extends SketchContribution {
     });
     return result;
   }
+
+  private notificationId(message: string, ...actions: string[]): string {
+    return this.notificationManager.getMessageId({
+      text: message,
+      actions,
+      type: MessageType.Error,
+    });
+  }
 }
 
 export namespace Contribution {
diff --git a/arduino-ide-extension/src/browser/contributions/format.ts b/arduino-ide-extension/src/browser/contributions/format.ts
index e270181b0..c92741f1f 100644
--- a/arduino-ide-extension/src/browser/contributions/format.ts
+++ b/arduino-ide-extension/src/browser/contributions/format.ts
@@ -2,8 +2,7 @@ import { MaybePromise } from '@theia/core';
 import { inject, injectable } from '@theia/core/shared/inversify';
 import * as monaco from '@theia/monaco-editor-core';
 import { Formatter } from '../../common/protocol/formatter';
-import { InoSelector } from '../ino-selectors';
-import { fullRange } from '../utils/monaco';
+import { InoSelector } from '../selectors';
 import { Contribution, URI } from './contribution';
 
 @injectable()
@@ -40,7 +39,7 @@ export class Format
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     _token: monaco.CancellationToken
   ): Promise<monaco.languages.TextEdit[]> {
-    const range = fullRange(model);
+    const range = model.getFullModelRange();
     const text = await this.format(model, range, options);
     return [{ range, text }];
   }
diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts
index e15e612ad..d4bad139f 100644
--- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts
@@ -191,6 +191,7 @@ export class UploadSketch extends CoreServiceContribution {
       // uploadInProgress will be set to false whether the upload fails or not
       this.uploadInProgress = true;
       this.onDidChangeEmitter.fire();
+      this.clearVisibleNotification();
 
       const verifyOptions =
         await this.commandService.executeCommand<CoreService.Options.Compile>(
diff --git a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts
index 3fc2d53a0..fe4de6f6a 100644
--- a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts
+++ b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts
@@ -108,6 +108,7 @@ export class VerifySketch extends CoreServiceContribution {
         this.verifyInProgress = true;
         this.onDidChangeEmitter.fire();
       }
+      this.clearVisibleNotification();
       this.coreErrorHandler.reset();
 
       const options = await this.options(params?.exportBinaries);
diff --git a/arduino-ide-extension/src/browser/ino-selectors.ts b/arduino-ide-extension/src/browser/selectors.ts
similarity index 62%
rename from arduino-ide-extension/src/browser/ino-selectors.ts
rename to arduino-ide-extension/src/browser/selectors.ts
index e413dba26..f1f898013 100644
--- a/arduino-ide-extension/src/browser/ino-selectors.ts
+++ b/arduino-ide-extension/src/browser/selectors.ts
@@ -1,4 +1,5 @@
 import * as monaco from '@theia/monaco-editor-core';
+import { OutputUri } from '@theia/output/lib/common/output-uri';
 /**
  * Exclusive "ino" document selector for monaco.
  */
@@ -11,3 +12,11 @@ function selectorOf(
     exclusive: true, // <-- this should make sure the custom formatter has higher precedence over the LS formatter.
   }));
 }
+
+/**
+ * Selector for the `monaco` resource in the Arduino _Output_ channel.
+ */
+export const ArduinoOutputSelector: monaco.languages.LanguageSelector = {
+  scheme: OutputUri.SCHEME,
+  pattern: '**/Arduino',
+};
diff --git a/arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts b/arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts
index c38427508..bdc1ed35f 100644
--- a/arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts
+++ b/arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts
@@ -1,9 +1,10 @@
-import { injectable } from '@theia/core/shared/inversify';
 import { CancellationToken } from '@theia/core/lib/common/cancellation';
-import {
+import type {
+  Message,
   ProgressMessage,
   ProgressUpdate,
 } from '@theia/core/lib/common/message-service-protocol';
+import { injectable } from '@theia/core/shared/inversify';
 import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
 
 @injectable()
@@ -34,7 +35,9 @@ export class NotificationManager extends TheiaNotificationManager {
     this.fireUpdatedEvent();
   }
 
-  protected override toPlainProgress(update: ProgressUpdate): number | undefined {
+  protected override toPlainProgress(
+    update: ProgressUpdate
+  ): number | undefined {
     if (!update.work) {
       return undefined;
     }
@@ -43,4 +46,11 @@ export class NotificationManager extends TheiaNotificationManager {
     }
     return Math.min((update.work.done / update.work.total) * 100, 100);
   }
+
+  /**
+   * For `public` visibility.
+   */
+  override getMessageId(message: Message): string {
+    return super.getMessageId(message);
+  }
 }
diff --git a/arduino-ide-extension/src/browser/utils/monaco.ts b/arduino-ide-extension/src/browser/utils/monaco.ts
deleted file mode 100644
index 0e957a980..000000000
--- a/arduino-ide-extension/src/browser/utils/monaco.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as monaco from '@theia/monaco-editor-core';
-
-export function fullRange(model: monaco.editor.ITextModel): monaco.Range {
-  const lastLine = model.getLineCount();
-  const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
-  const end = new monaco.Position(lastLine, lastLineMaxColumn);
-  return monaco.Range.fromPositions(new monaco.Position(1, 1), end);
-}
diff --git a/arduino-ide-extension/src/common/protocol/core-service.ts b/arduino-ide-extension/src/common/protocol/core-service.ts
index 4c85ecc5c..a7124d865 100644
--- a/arduino-ide-extension/src/common/protocol/core-service.ts
+++ b/arduino-ide-extension/src/common/protocol/core-service.ts
@@ -1,5 +1,9 @@
 import { ApplicationError } from '@theia/core/lib/common/application-error';
-import type { Location } from '@theia/core/shared/vscode-languageserver-protocol';
+import type {
+  Location,
+  Range,
+  Position,
+} from '@theia/core/shared/vscode-languageserver-protocol';
 import type {
   BoardUserField,
   Port,
@@ -15,11 +19,41 @@ export const CompilerWarningLiterals = [
 ] as const;
 export type CompilerWarnings = typeof CompilerWarningLiterals[number];
 export namespace CoreError {
-  export interface ErrorLocation {
+  export interface ErrorLocationRef {
     readonly message: string;
     readonly location: Location;
     readonly details?: string;
   }
+  export namespace ErrorLocationRef {
+    export function equals(
+      left: ErrorLocationRef,
+      right: ErrorLocationRef
+    ): boolean {
+      return (
+        left.message === right.message &&
+        left.details === right.details &&
+        equalsLocation(left.location, right.location)
+      );
+    }
+    function equalsLocation(left: Location, right: Location): boolean {
+      return left.uri === right.uri && equalsRange(left.range, right.range);
+    }
+    function equalsRange(left: Range, right: Range): boolean {
+      return (
+        equalsPosition(left.start, right.start) &&
+        equalsPosition(left.end, right.end)
+      );
+    }
+    function equalsPosition(left: Position, right: Position): boolean {
+      return left.character === right.character && left.line === right.line;
+    }
+  }
+  export interface ErrorLocation extends ErrorLocationRef {
+    /**
+     * The range of the error location source from the CLI output.
+     */
+    readonly rangesInOutput: Range[]; // The same error might show up multiple times in the CLI output: https://github.com/arduino/arduino-cli/issues/1761
+  }
   export const Codes = {
     Verify: 4001,
     Upload: 4002,
diff --git a/arduino-ide-extension/src/node/cli-error-parser.ts b/arduino-ide-extension/src/node/cli-error-parser.ts
index 0f72ea71c..d60ecb88e 100644
--- a/arduino-ide-extension/src/node/cli-error-parser.ts
+++ b/arduino-ide-extension/src/node/cli-error-parser.ts
@@ -5,25 +5,41 @@ import {
   Range,
   Position,
 } from '@theia/core/shared/vscode-languageserver-protocol';
-import type { CoreError } from '../common/protocol';
+import { CoreError } from '../common/protocol';
 import { Sketch } from '../common/protocol/sketches-service';
 
-export interface ErrorSource {
+export interface OutputSource {
   readonly content: string | ReadonlyArray<Uint8Array>;
   readonly sketch?: Sketch;
 }
-
-export function tryParseError(source: ErrorSource): CoreError.ErrorLocation[] {
-  const { content, sketch } = source;
-  const err =
-    typeof content === 'string'
+export namespace OutputSource {
+  export function content(source: OutputSource): string {
+    const { content } = source;
+    return typeof content === 'string'
       ? content
       : Buffer.concat(content).toString('utf8');
+  }
+}
+
+export function tryParseError(source: OutputSource): CoreError.ErrorLocation[] {
+  const { sketch } = source;
+  const content = OutputSource.content(source);
   if (sketch) {
-    return tryParse(err)
+    return tryParse(content)
       .map(remapErrorMessages)
       .filter(isLocationInSketch(sketch))
-      .map(toErrorInfo);
+      .map(toErrorInfo)
+      .reduce((acc, curr) => {
+        const existingRef = acc.find((candidate) =>
+          CoreError.ErrorLocationRef.equals(candidate, curr)
+        );
+        if (existingRef) {
+          existingRef.rangesInOutput.push(...curr.rangesInOutput);
+        } else {
+          acc.push(curr);
+        }
+        return acc;
+      }, [] as CoreError.ErrorLocation[]);
   }
   return [];
 }
@@ -35,6 +51,7 @@ interface ParseResult {
   readonly errorPrefix: string;
   readonly error: string;
   readonly message?: string;
+  readonly rangeInOutput?: Range | undefined;
 }
 namespace ParseResult {
   export function keyOf(result: ParseResult): string {
@@ -64,6 +81,7 @@ function toErrorInfo({
   path,
   line,
   column,
+  rangeInOutput,
 }: ParseResult): CoreError.ErrorLocation {
   return {
     message: error,
@@ -72,6 +90,7 @@ function toErrorInfo({
       uri: FileUri.create(path).toString(),
       range: range(line, column),
     },
+    rangesInOutput: rangeInOutput ? [rangeInOutput] : [],
   };
 }
 
@@ -86,48 +105,50 @@ function range(line: number, column?: number): Range {
   };
 }
 
-export function tryParse(raw: string): ParseResult[] {
+function tryParse(content: string): ParseResult[] {
   // Shamelessly stolen from the Java IDE: https://github.com/arduino/Arduino/blob/43b0818f7fa8073301db1b80ac832b7b7596b828/arduino-core/src/cc/arduino/Compiler.java#L137
   const re = new RegExp(
     '(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*((fatal)?\\s*error:\\s*)(.*)\\s*',
     'gm'
   );
-  return [
-    ...new Map(
-      Array.from(raw.matchAll(re) ?? [])
-        .map((match) => {
-          const [, path, rawLine, rawColumn, errorPrefix, , error] = match.map(
-            (match) => (match ? match.trim() : match)
+  return Array.from(content.matchAll(re) ?? [])
+    .map((match) => {
+      const { index: start } = match;
+      const [, path, rawLine, rawColumn, errorPrefix, , error] = match.map(
+        (match) => (match ? match.trim() : match)
+      );
+      const line = Number.parseInt(rawLine, 10);
+      if (!Number.isInteger(line)) {
+        console.warn(
+          `Could not parse line number. Raw input: <${rawLine}>, parsed integer: <${line}>.`
+        );
+        return undefined;
+      }
+      let column: number | undefined = undefined;
+      if (rawColumn) {
+        const normalizedRawColumn = rawColumn.slice(-1); // trims the leading colon => `:3` will be `3`
+        column = Number.parseInt(normalizedRawColumn, 10);
+        if (!Number.isInteger(column)) {
+          console.warn(
+            `Could not parse column number. Raw input: <${normalizedRawColumn}>, parsed integer: <${column}>.`
           );
-          const line = Number.parseInt(rawLine, 10);
-          if (!Number.isInteger(line)) {
-            console.warn(
-              `Could not parse line number. Raw input: <${rawLine}>, parsed integer: <${line}>.`
-            );
-            return undefined;
-          }
-          let column: number | undefined = undefined;
-          if (rawColumn) {
-            const normalizedRawColumn = rawColumn.slice(-1); // trims the leading colon => `:3` will be `3`
-            column = Number.parseInt(normalizedRawColumn, 10);
-            if (!Number.isInteger(column)) {
-              console.warn(
-                `Could not parse column number. Raw input: <${normalizedRawColumn}>, parsed integer: <${column}>.`
-              );
-            }
-          }
-          return {
-            path,
-            line,
-            column,
-            errorPrefix,
-            error,
-          };
-        })
-        .filter(notEmpty)
-        .map((result) => [ParseResult.keyOf(result), result])
-    ).values(),
-  ];
+        }
+      }
+      const rangeInOutput = findRangeInOutput(
+        start,
+        { path, rawLine, rawColumn },
+        content
+      );
+      return {
+        path,
+        line,
+        column,
+        errorPrefix,
+        error,
+        rangeInOutput,
+      };
+    })
+    .filter(notEmpty);
 }
 
 /**
@@ -161,3 +182,47 @@ const KnownErrors: Record<string, { error: string; message?: string }> = {
     ),
   },
 };
+
+function findRangeInOutput(
+  startIndex: number | undefined,
+  groups: { path: string; rawLine: string; rawColumn: string | null },
+  content: string // TODO? lines: string[]? can this code break line on `\n`? const lines = content.split(/\r?\n/) ?? [];
+): Range | undefined {
+  if (startIndex === undefined) {
+    return undefined;
+  }
+  // /path/to/location/Sketch/Sketch.ino:36:42
+  const offset =
+    groups.path.length +
+    ':'.length +
+    groups.rawLine.length +
+    (groups.rawColumn ? groups.rawColumn.length : 0);
+  const start = toPosition(startIndex, content);
+  if (!start) {
+    return undefined;
+  }
+  const end = toPosition(startIndex + offset, content);
+  if (!end) {
+    return undefined;
+  }
+  return { start, end };
+}
+
+function toPosition(offset: number, content: string): Position | undefined {
+  let line = 0;
+  let character = 0;
+  const length = content.length;
+  for (let i = 0; i < length; i++) {
+    const c = content.charAt(i);
+    if (i === offset) {
+      return { line, character };
+    }
+    if (c === '\n') {
+      line++;
+      character = 0;
+    } else {
+      character++;
+    }
+  }
+  return undefined;
+}
diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts
index a7dde4f2b..e556048ce 100644
--- a/arduino-ide-extension/src/node/core-service-impl.ts
+++ b/arduino-ide-extension/src/node/core-service-impl.ts
@@ -86,7 +86,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
             reject(error);
           } else {
             const compilerErrors = tryParseError({
-              content: handler.stderr,
+              content: handler.content,
               sketch: options.sketch,
             });
             const message = nls.localize(
@@ -224,7 +224,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
                 errorCtor(
                   message,
                   tryParseError({
-                    content: handler.stderr,
+                    content: handler.content,
                     sketch: options.sketch,
                   })
                 )
@@ -291,7 +291,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
                     'Error while burning the bootloader: {0}',
                     error.details
                   ),
-                  tryParseError({ content: handler.stderr })
+                  tryParseError({ content: handler.content })
                 )
               );
             }
@@ -342,19 +342,15 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
     // TODO: why not creating a composite handler with progress, `build_path`, and out/err stream handlers?
     ...handlers: ((response: R) => void)[]
   ): Disposable & {
-    stderr: Buffer[];
+    content: Buffer[];
     onData: (response: R) => void;
   } {
-    const stderr: Buffer[] = [];
+    const content: Buffer[] = [];
     const buffer = new AutoFlushingBuffer((chunks) => {
-      Array.from(chunks.entries()).forEach(([severity, chunk]) => {
-        if (chunk) {
-          this.sendResponse(chunk, severity);
-        }
-      });
+      chunks.forEach(([severity, chunk]) => this.sendResponse(chunk, severity));
     });
     const onData = StreamingResponse.createOnDataHandler({
-      stderr,
+      content,
       onData: (out, err) => {
         buffer.addChunk(out);
         buffer.addChunk(err, OutputMessage.Severity.Error);
@@ -363,7 +359,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
     });
     return {
       dispose: () => buffer.dispose(),
-      stderr,
+      content,
       onData,
     };
   }
@@ -432,14 +428,19 @@ namespace StreamingResponse {
   ): (response: R) => void {
     return (response: R) => {
       const out = response.getOutStream_asU8();
+      if (out.length) {
+        options.content.push(out);
+      }
       const err = response.getErrStream_asU8();
-      options.stderr.push(err);
+      if (err.length) {
+        options.content.push(err);
+      }
       options.onData(out, err);
       options.handlers?.forEach((handler) => handler(response));
     };
   }
   export interface Options<R extends StreamingResponse> {
-    readonly stderr: Uint8Array[];
+    readonly content: Uint8Array[];
     readonly onData: (out: Uint8Array, err: Uint8Array) => void;
     /**
      * Additional request handlers.
diff --git a/arduino-ide-extension/src/node/utils/buffers.ts b/arduino-ide-extension/src/node/utils/buffers.ts
index e703e1bce..63ed1a97c 100644
--- a/arduino-ide-extension/src/node/utils/buffers.ts
+++ b/arduino-ide-extension/src/node/utils/buffers.ts
@@ -3,13 +3,13 @@ import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
 import { OutputMessage } from '../../common/protocol';
 
 export class AutoFlushingBuffer implements Disposable {
-  private readonly chunks = Chunks.create();
+  private readonly chunks: Array<[OutputMessage.Severity, Uint8Array]> = [];
   private readonly toDispose;
   private timer?: NodeJS.Timeout;
   private disposed = false;
 
   constructor(
-    onFlush: (chunks: Map<OutputMessage.Severity, string | undefined>) => void,
+    onFlush: (chunks: Array<[OutputMessage.Severity, string]>) => void,
     taskTimeout: number = AutoFlushingBuffer.DEFAULT_FLUSH_TIMEOUT_MS
   ) {
     const task = () => {
@@ -34,7 +34,9 @@ export class AutoFlushingBuffer implements Disposable {
     chunk: Uint8Array,
     severity: OutputMessage.Severity = OutputMessage.Severity.Info
   ): void {
-    this.chunks.get(severity)?.push(chunk);
+    if (chunk.length) {
+      this.chunks.push([severity, chunk]);
+    }
   }
 
   dispose(): void {
@@ -49,19 +51,10 @@ export namespace AutoFlushingBuffer {
   export const DEFAULT_FLUSH_TIMEOUT_MS = 32;
 }
 
-type Chunks = Map<OutputMessage.Severity, Uint8Array[]>;
+type Chunks = Array<[OutputMessage.Severity, Uint8Array]>;
 namespace Chunks {
-  export function create(): Chunks {
-    return new Map([
-      [OutputMessage.Severity.Error, []],
-      [OutputMessage.Severity.Warning, []],
-      [OutputMessage.Severity.Info, []],
-    ]);
-  }
   export function clear(chunks: Chunks): Chunks {
-    for (const chunk of chunks.values()) {
-      chunk.length = 0;
-    }
+    chunks.length = 0;
     return chunks;
   }
   export function isEmpty(chunks: Chunks): boolean {
@@ -69,12 +62,35 @@ namespace Chunks {
   }
   export function toString(
     chunks: Chunks
-  ): Map<OutputMessage.Severity, string | undefined> {
-    return new Map(
-      Array.from(chunks.entries()).map(([severity, buffers]) => [
-        severity,
-        buffers.length ? Buffer.concat(buffers).toString() : undefined,
-      ])
-    );
+  ): Array<[OutputMessage.Severity, string]> {
+    const result: Array<[OutputMessage.Severity, string]> = [];
+    let current:
+      | { severity: OutputMessage.Severity; buffers: Uint8Array[] }
+      | undefined = undefined;
+    const appendToResult = () => {
+      if (current && current.buffers) {
+        result.push([
+          current.severity,
+          Buffer.concat(current.buffers).toString('utf-8'),
+        ]);
+      }
+    };
+    for (const [severity, buffer] of chunks) {
+      if (!buffer.length) {
+        continue;
+      }
+      if (!current) {
+        current = { severity, buffers: [buffer] };
+      } else {
+        if (current.severity === severity) {
+          current.buffers.push(buffer);
+        } else {
+          appendToResult();
+          current = { severity, buffers: [buffer] };
+        }
+      }
+    }
+    appendToResult();
+    return result;
   }
 }
diff --git a/i18n/en.json b/i18n/en.json
index 88c997c90..cf0f73e6e 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -155,7 +155,8 @@
       "increaseFontSize": "Increase Font Size",
       "increaseIndent": "Increase Indent",
       "nextError": "Next Error",
-      "previousError": "Previous Error"
+      "previousError": "Previous Error",
+      "revealError": "Reveal Error"
     },
     "electron": {
       "couldNotSave": "Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.",