diff --git a/ivette/src/dome/renderer/text/editor.tsx b/ivette/src/dome/renderer/text/editor.tsx
index 587aded505b38fed77c44cd045c22b139fd20e78..188789ffcbaf7df48cb2817750f90db55194aef0 100644
--- a/ivette/src/dome/renderer/text/editor.tsx
+++ b/ivette/src/dome/renderer/text/editor.tsx
@@ -375,6 +375,26 @@ function createSelectionField(): Field<EditorSelection> {
   return { ...field, set, extension: [field.extension, updater] };
 }
 
+export type RangeCallback = (offset: number, endOffset: number) => void;
+export const OnSelection = createOnSelectionField();
+function createOnSelectionField(): Field<RangeCallback|null> {
+  const field = createField<RangeCallback|null>(null);
+  const set: Set<RangeCallback|null> = (view, fn) => {
+    field.set(view, fn);
+  };
+  const updater = EditorView.updateListener.of((update) => {
+    if (update.selectionSet) {
+      const view = update.view;
+      const fn = field.get(view.state);
+      if (fn !== null) {
+        const { from: offset, to: endOffset } = view.state.selection.main;
+        fn(offset, endOffset);
+      }
+    }
+  });
+  return { ...field, set, extension: [field.extension, updater] };
+}
+
 export const Document = createDocumentField();
 function createDocumentField(): Field<Text> {
   const field = createField<Text>(Text.empty);
diff --git a/ivette/src/frama-c/kernel/SourceCode.tsx b/ivette/src/frama-c/kernel/SourceCode.tsx
index 4b1de459df7ef913508013b05d0574faa450b6a7..ab59483b78641a4cccc8a2884be9aa046deffc3e 100644
--- a/ivette/src/frama-c/kernel/SourceCode.tsx
+++ b/ivette/src/frama-c/kernel/SourceCode.tsx
@@ -21,7 +21,6 @@
 /* ************************************************************************ */
 
 import React from 'react';
-import * as Path from 'path';
 
 import * as Dome from 'dome';
 import * as System from 'dome/system';
@@ -32,39 +31,34 @@ import * as Buttons from 'dome/controls/buttons';
 import * as Dialogs from 'dome/dialogs';
 import * as Toolbars from 'dome/frame/toolbars';
 
-import { Selection, Document } from 'dome/text/editor';
+import * as Ivette from 'ivette';
+import * as Preferences from 'ivette/prefs';
 
-import * as Server from 'frama-c/server';
 import * as States from 'frama-c/states';
 import * as Status from 'frama-c/kernel/Status';
 import * as Ast from 'frama-c/kernel/api/ast';
 
-import * as Ivette from 'ivette';
-import * as Preferences from 'ivette/prefs';
-
-
-
 // -----------------------------------------------------------------------------
 //  Utilitary types and functions
 // -----------------------------------------------------------------------------
 
-// Recovering the cursor position as a line and a column.
 interface Position { line: number, column: number }
+
+// Recovering the cursor position as a line and a column.
 function getCursorPosition(view: Editor.View): Position {
   const pos = view?.state.selection.main;
   if (!view || !pos) return { line: 1, column: 1 };
   const line = view.state.doc.lineAt(pos.from);
-  const column = pos.from - line.from + 1;
+  const column = pos.from - line.from;
   return { line: line.number, column };
 }
 
-// Error messages.
-function setError(text: string): void {
-  Status.setMessage({ text, kind: 'error' });
-}
-
 // Function launching the external editor at the currently selected position.
-async function edit(file: string, pos: Position, cmd: string): Promise<void> {
+async function editSourceFile(
+  cmd: string,
+  file: string,
+  pos: Position
+): Promise<void> {
   if (file === '') return;
   const args = cmd
     .replace('%s', file)
@@ -72,156 +66,90 @@ async function edit(file: string, pos: Position, cmd: string): Promise<void> {
     .replace('%c', pos.column.toString())
     .split(' ');
   const prog = args.shift(); if (!prog) return;
-  const text = `An error has occured when opening the external editor ${prog}`;
-  System.spawn(prog, args).catch(() => setError(text));
+  System.spawn(prog, args).catch(() => {
+    const text = 'Editing Failed';
+    const title = `Command ${prog} ${args} failed.`;
+    Status.setMessage({ text, title, kind: 'error' });
+  });
 }
 
-// -----------------------------------------------------------------------------
-
+// Editor Help popup
+async function displayShortcuts(): Promise<void> {
+  await Dialogs.showMessageBox({
+    buttons: [{ label: "Ok" }],
+    details: (
+      'Ctrl+click: open file in an external editor at the selected location.\n'
+      + 'Alt+f: search for text or regexp.\n'
+      + 'Alt+g: go to line.'
+    ),
+    message: 'Useful shortcuts',
+  });
+}
 
+// Toplevel Declaration Markers
+function isToplevelDecl(kind: Ast.markerKind): boolean {
+  switch(kind) {
+    case 'DFUN':
+    case 'DECLARATION':
+      return true;
+    default:
+      return false;
+  }
+}
 
 // -----------------------------------------------------------------------------
-//  Fields declarations
+//  Selection Events
 // -----------------------------------------------------------------------------
 
-// Those fields contain the source code and the file name.
-const Source = Editor.createTextField<string>('', (s) => s);
-const File = Editor.createField<string>('');
+type SourceCursor = { file: string, line: number, column: number };
+type SourceCallback = ((pos: Position) => void) | null;
 
-// This field contains the command use to start the external editor.
-const Command = Editor.createField<string>('');
+const noCursor: SourceCursor = { file: '', line: 0, column: 0 };
+const OnSelection = Editor.OnSelection;
+const OnControlClick = Editor.createField<SourceCallback>(null);
+const OnContextMenu = Editor.createField<SourceCallback>(null);
 
-// These field contains a callback that returns the source of a location.
-type GetSource = (loc: States.Location) => Ast.source | undefined;
-const GetSource = Editor.createField<GetSource>(() => undefined);
-
-// We keep track of the cursor location (i.e the location that is under the
-// current CodeMirror cursor) and the ivette location (i.e the locations as
-// seen by the outside world). Keeping track of both of them together force
-// their update in one dispatch, which prevents strange bugs.
-interface Locations { cursor?: States.Location, current: States.Location }
-
-// The Locations field. When given Locations, it will update the cursor field if
-// and only if the new cursor location is not undefined. It simplifies the
-// update in the SourceCode component itself.
-const Locations = createLocationsField();
-function createLocationsField(): Editor.Field<Locations> {
-  const noField = { cursor: undefined, current: {} };
-  const field = Editor.createField<Locations>(noField);
-  const set: Editor.Set<Locations> = (view, toBe) => {
-    const hasBeen = field.get(view?.state);
-    const cursor = toBe.cursor ?? hasBeen.cursor;
-    field.set(view, { cursor, current: toBe.current });
+// This events handler takes care of mouse events
+const EventHandlers = createEventHandlers();
+function createEventHandlers(): Editor.Extension {
+  const deps = {
+    onControlClick: OnControlClick,
+    onContextMenu: OnContextMenu
   };
-  return { ...field, set };
-}
-
-// -----------------------------------------------------------------------------
-
-
-
-// -----------------------------------------------------------------------------
-//  Synchronisation with the outside world
-// -----------------------------------------------------------------------------
-
-// Update the outside world when the user click somewhere in the source code.
-const SyncOnUserSelection = createSyncOnUserSelection();
-function createSyncOnUserSelection(): Editor.Extension {
-  const actions = { cmd: Command };
-  const deps = { file: File, selection: Selection, doc: Document, ...actions };
   return Editor.createEventHandler(deps, {
-    mouseup: async ({ file, cmd }, view, event) => {
-      if (!view || file === '') return;
-      const pos = getCursorPosition(view);
-      const cursor = { file, line: pos.line, column: pos.column };
-      try {
-        const marker = await Server.send(Ast.getMarkerAt, cursor);
-        if (marker) {
-          // The forced reload should NOT be necessary but... It is...
-          await Server.send(Ast.reloadMarkerAttributes, null);
-          States.setSelected(marker);
-          const scope = States.getMarker(marker)?.scope;
-          const location = { marker, scope };
-          Locations.set(view, { cursor: location, current: location });
-        }
-      } catch {
-        setError('Failure');
+    // Control Click
+    mouseup: ({ onControlClick }, view, event) => {
+      if (event.ctrlKey && onControlClick !== null) {
+        const pos = getCursorPosition(view);
+        onControlClick(pos);
+      }
+    },
+    // Context Menu
+    contextmenu: ({ onContextMenu }, view) => {
+      if (onContextMenu !== null) {
+        const pos = getCursorPosition(view);
+        onContextMenu(pos);
       }
-      if (event.ctrlKey) edit(file, pos, cmd);
     }
   });
 }
 
-// Update the cursor position when the outside world changes the selected
-// location.
-const SyncOnOutsideSelection = createSyncOnOutsideSelection();
-function createSyncOnOutsideSelection(): Editor.Extension {
-  const deps = { locations: Locations, get: GetSource };
-  return Editor.createViewUpdater(deps, ({ locations, get }, view) => {
-    const { cursor, current } = locations;
-    if (current === undefined || current === cursor) return;
-    const source = get(current); if (!source) return;
-    const newDecl =
-      current.scope !== cursor?.scope && current.marker === undefined;
-    const onTop = cursor === undefined || newDecl;
-    Locations.set(view, { cursor: current, current });
-    Editor.selectLine(view, source.line, onTop, false);
-  });
-}
-
 // -----------------------------------------------------------------------------
-
-
-
-// -----------------------------------------------------------------------------
-//  Context menu
-// -----------------------------------------------------------------------------
-
-// This events handler takes care of the context menu.
-const ContextMenu = createContextMenu();
-function createContextMenu(): Editor.Extension {
-  const deps = { file: File, command: Command };
-  return Editor.createEventHandler(deps, {
-    contextmenu: ({ file, command }, view) => {
-      if (file === '') return;
-      const label = 'Open file in an external editor';
-      const pos = getCursorPosition(view);
-      Dome.popupMenu([ { label, onClick: () => edit(file, pos, command) } ]);
-    },
-  });
-}
-
+//  Source File Contents
 // -----------------------------------------------------------------------------
 
+const Source = Editor.createTextField<string>('', (s: string) => s);
 
-
-// -----------------------------------------------------------------------------
-//  Server requests
-// -----------------------------------------------------------------------------
-
-// Server request handler returning the source code.
-function useFctSource(file: string): string {
-  const req = React.useMemo(() => System.readFile(file), [file]);
+// System request to read the source file.
+function useSourceFileContents(file: string | undefined): string {
+  const req = React.useMemo(() => {
+    if (!file) return Promise.resolve('');
+    return System.readFile(file);
+  }, [file]);
   const { result } = Dome.usePromise(req);
   return result ?? '';
 }
 
-// Build a callback that retrieves a location's source information.
-function useSourceGetter(): GetSource {
-  const getAttr = States.useSyncArrayGetter(Ast.markerAttributes);
-  const getDecl = States.useSyncArrayGetter(Ast.declAttributes);
-  return React.useCallback(({ scope, marker }) => {
-    const { sloc, scope: markerDecl } = getAttr(marker) ?? {};
-    if (sloc) return sloc;
-    const { source } = getDecl(scope ?? markerDecl) ?? {};
-    return source;
-  }, [getAttr, getDecl]);
-}
-
-// -----------------------------------------------------------------------------
-
-
-
 // -----------------------------------------------------------------------------
 //  Source Code component
 // -----------------------------------------------------------------------------
@@ -235,10 +163,10 @@ const extensions: Editor.Extension[] = [
   Editor.LineNumbers,
   Editor.LanguageHighlighter,
   Editor.HighlightActiveLine,
-  SyncOnUserSelection,
-  SyncOnOutsideSelection,
-  ContextMenu,
-  Locations.structure,
+  OnSelection,
+  OnContextMenu,
+  OnControlClick,
+  EventHandlers,
 ];
 
 // The component in itself.
@@ -246,52 +174,71 @@ export default function SourceCode(): JSX.Element {
   const [fontSize] = Settings.useGlobalSettings(Preferences.EditorFontSize);
   const [command] = Settings.useGlobalSettings(Preferences.EditorCommand);
   const { view, Component } = Editor.Editor(extensions);
-  const current = States.useCurrentLocation();
-  const getSource = useSourceGetter();
-  const file = getSource(current)?.file ?? '';
-  const filename = Path.parse(file).base;
-  const pos = getCursorPosition(view);
-  const source = useFctSource(file);
+  const selectedMarker = States.useSelected();
+  const { sloc, kind } = States.useMarker(selectedMarker);
+  const [floc, setFloc] = React.useState<Ast.source | undefined>();
+  const isTop = isToplevelDecl(kind);
+  React.useEffect(() => { if (sloc) setFloc(sloc); }, [sloc]);
+  const file = floc?.file;
+  const filename = floc?.base;
+  const selectedMarkerLine = floc?.line ?? 0;
+  const source = useSourceFileContents(file);
+  const [cursor, setCursor] = React.useState<SourceCursor>(noCursor);
+  const markerAtCursor = States.useRequest(Ast.getMarkerAt, cursor);
+  const { sloc: slocAtCursor } = States.useMarker(markerAtCursor);
+
+  const editFile = React.useCallback(() => {
+    if (file) editSourceFile(command, file, getCursorPosition(view));
+  }, [ command, file, view ]
+  );
 
-  React.useEffect(() => GetSource.set(view, getSource), [view, getSource]);
-  React.useEffect(() => File.set(view, file), [view, file]);
+  const menuPopup = React.useCallback(() => {
+    if (file && command)
+      Dome.popupMenu([{
+        label: 'Edit file in editor',
+        onClick: editFile,
+      }]);
+  }, [file, command, editFile] );
+
+  const onSelect = React.useCallback((offset, endOffset) => {
+    if (!view || !file || endOffset !== offset) return;
+    const theLine = view.state.doc.lineAt(offset);
+    const line = theLine.number;
+    const column = offset - theLine.from;
+    setCursor({ file, line, column });
+  }, [view, file]);
+
+  React.useEffect(() => { if (!file) setCursor(noCursor); }, [file]);
   React.useEffect(() => Source.set(view, source), [view, source]);
-  React.useEffect(() => Command.set(view, command), [view, command]);
-  React.useEffect(() => Locations.set(view, { current }), [view, current]);
+  React.useEffect(() => OnSelection.set(view, onSelect), [view, onSelect]);
+  React.useEffect(() => OnContextMenu.set(view, menuPopup), [view, menuPopup]);
+  React.useEffect(() => OnControlClick.set(view, editFile), [view, editFile]);
 
-  const externalEditorTitle =
-    'Open the source file in an external editor.';
+  React.useEffect(() => {
+    if (selectedMarkerLine > 0)
+      Editor.selectLine(view, selectedMarkerLine, isTop, true);
+  }, [view, isTop, selectedMarkerLine]);
 
-  const shortcuts =
-    'Ctrl+click: open file in an external editor at the selected location.\n'
-    + 'Alt+f: search for text or regexp.\n'
-    + 'Alt+g: go to line.';
-
-  const shortcutsTitle = 'Useful shortcuts:\n' + shortcuts;
-
-  async function displayShortcuts(): Promise<void> {
-    await Dialogs.showMessageBox({
-      buttons: [{ label: "Ok" }],
-      details: shortcuts,
-      message: 'Useful shortcuts'
-    });
-  }
+  React.useEffect(() => {
+    if (cursor.line === slocAtCursor?.line)
+      States.setSelected(markerAtCursor);
+  }, [cursor, markerAtCursor, slocAtCursor]);
 
   return (
     <>
       <Ivette.TitleBar>
         <Buttons.IconButton
           icon="DUPLICATE"
-          visible={file !== ''}
-          onClick={() => edit(file, pos, command)}
-          title={externalEditorTitle}
+          visible={!file}
+          onClick={editFile}
+          title='externalEditorTitle'
         />
         <Labels.Code title={file}>{filename}</Labels.Code>
         <Toolbars.Filler />
         <Buttons.IconButton
           icon="HELP"
           onClick={displayShortcuts}
-          title={shortcutsTitle}
+          title='Useful shortcuts'
         />
         <Toolbars.Inset/>
       </Ivette.TitleBar>
diff --git a/ivette/src/frama-c/kernel/api/ast/index.ts b/ivette/src/frama-c/kernel/api/ast/index.ts
index a279ce2d235862096fdea3a5fad367c46fceb442..749c722b9e3696443416bc200738568f601d9ff8 100644
--- a/ivette/src/frama-c/kernel/api/ast/index.ts
+++ b/ivette/src/frama-c/kernel/api/ast/index.ts
@@ -558,7 +558,7 @@ const getMarkerAt_internal: Server.GetRequest<
   output: Json.jOption(jMarker),
   signals: [ { name: 'kernel.ast.changed' } ],
 };
-/** Returns the marker and function at a source file position, if any. Input: file path, line and column. */
+/** Returns the marker and function at a source file position, if any. Input: file path, line and column. File can be empty, in case no marker is returned. */
 export const getMarkerAt: Server.GetRequest<
   { file: string, line: number, column: number },
   marker |
diff --git a/src/plugins/server/kernel_ast.ml b/src/plugins/server/kernel_ast.ml
index 109d8849e03a5f5fa4ab6b48ff796c39eb73f173..34f517c581b91ba276b112605784e14dca9fe94c 100644
--- a/src/plugins/server/kernel_ast.ml
+++ b/src/plugins/server/kernel_ast.ml
@@ -899,26 +899,28 @@ let () = Server_parameters.Debug.add_hook_on_update
 (* -------------------------------------------------------------------------- *)
 
 let get_marker_at ~file ~line ~col =
-  let pos_path = Filepath.Normalized.of_string file in
-  let pos =
-    Filepath.{ pos_path; pos_lnum = line; pos_cnum = col; pos_bol = 0; }
-  in
-  Printer_tag.loc_to_localizable ~precise_col:true pos
+  if file="" then None else
+    let pos_path = Filepath.Normalized.of_string file in
+    let pos =
+      Filepath.{ pos_path; pos_lnum = line; pos_cnum = col; pos_bol = 0; }
+    in
+    Printer_tag.loc_to_localizable ~precise_col:true pos
 
 let () =
   let descr =
     Md.plain
       "Returns the marker and function at a source file position, if any. \
-       Input: file path, line and column."
+       Input: file path, line and column. \
+       File can be empty, in case no marker is returned."
   in
   let signature = Request.signature
       ~output:(module Joption(Marker)) () in
   let get_file = Request.param signature
       ~name:"file" ~descr:(Md.plain "File path") (module Jstring) in
   let get_line = Request.param signature
-      ~name:"line" ~descr:(Md.plain "Line number") (module Jint) in
+      ~name:"line" ~descr:(Md.plain "Line (1-based)") (module Jint) in
   let get_col = Request.param signature
-      ~name:"column" ~descr:(Md.plain "Column number") (module Jint) in
+      ~name:"column" ~descr:(Md.plain "Column (0-based)") (module Jint) in
   Request.register_sig signature
     ~package ~descr ~kind:`GET ~name:"getMarkerAt"
     ~signals:[ast_changed_signal]