Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ words:
- SysML2
- SysML2Tools
- SysML2Workbench
- Antlr
- Xshd
- XSHD
- xshd
- avaloniaedit
- avares
ignorePaths:
- '**/.git/**'
- '**/node_modules/**'
Expand Down
11 changes: 11 additions & 0 deletions .reviewmark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,11 @@ reviews:
- docs/design/sysml2-workbench/app-shell-subsystem/main-window-shell.md
- docs/verification/sysml2-workbench/app-shell-subsystem/main-window-shell.md
- src/**/AppShellSubsystem/MainWindowShell.cs
- src/**/AppShellSubsystem/SourceTextDocumentViewModel.cs
- src/**/AppShellSubsystem/SourceTextDocumentView.axaml
- src/**/AppShellSubsystem/SourceTextDocumentView.axaml.cs
- test/**/AppShellSubsystem/MainWindowShellTests.cs
- test/**/AppShellSubsystem/SourceTextDocumentViewModelTests.cs
- id: SysML2Workbench-AppShellSubsystem-WorkspacePanel
title: Review that SysML2Workbench AppShellSubsystem WorkspacePanel Implementation
is Correct
Expand Down Expand Up @@ -390,3 +394,10 @@ reviews:
- docs/reqstream/ots/communitytoolkit-mvvm.yaml
- docs/design/ots/communitytoolkit-mvvm.md
- docs/verification/ots/communitytoolkit-mvvm.md
- id: OTS-AvaloniaEdit
title: Review that AvaloniaEdit Provides Required Functionality
paths:
- docs/reqstream/ots/avaloniaedit.yaml
- docs/design/ots/avaloniaedit.md
- docs/verification/ots/avaloniaedit.md
- test/OtsSoftwareTests/AvaloniaEditTests.cs
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ interactive pan/zoom viewer, and lets you build ad-hoc custom views through a
GUI - picking a view kind, multi-selecting target elements, and optionally
filtering - without hand-writing SysML `view` syntax. Custom views can be
exported as copy-pasteable SysML `view ... expose ...` text to promote them
into a permanent model file. A live-updating diagnostics panel surfaces parser
into a permanent model file, and any workspace `.sysml` file's raw source
text can be opened read-only, with syntax highlighting, by double-clicking
it in the workspace tree. A live-updating diagnostics panel surfaces parser
and reference-resolution problems across the whole workspace, and a local
rolling log file is written for bug-report attachments.

Expand Down
1 change: 1 addition & 0 deletions docs/design/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ OTS items:
semantic model, layout engine).
- **DemaConsulting.Rendering**: integration and usage design (SVG rendering).
- **Avalonia**: integration and usage design (UI framework).
- **AvaloniaEdit**: integration and usage design (read-only syntax-highlighted text editor control).
- **xUnit**: integration and usage design (test framework).

Out of scope: no Shared Packages exist in this repository. Design documents
Expand Down
12 changes: 7 additions & 5 deletions docs/design/ots.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ OTS items are consumed through narrow integration seams. SysML2Tools and
DemaConsulting.Rendering are wrapped behind local units that translate
workspace or view state into library calls. Avalonia is used as the application
shell and control layer, with Dock providing the resizable, floatable,
closable panel-docking layout for that shell and CommunityToolkit.Mvvm
supplying observable-property generation for the Dock panel view models, but
subsystem responsibilities remain in local units rather than in code-behind
spread across the UI. xUnit is confined to verification projects and does not
participate in runtime behavior. Across all items, the local code treats OTS
closable panel-docking layout for that shell, CommunityToolkit.Mvvm
supplying observable-property generation for the Dock panel view models, and
AvaloniaEdit providing the read-only, syntax-highlighted text editor control
used by the source-text viewer document tabs, but subsystem responsibilities
remain in local units rather than in code-behind spread across the UI. xUnit
is confined to verification projects and does not participate in runtime
behavior. Across all items, the local code treats OTS
failures as diagnosable events: exceptions are surfaced to the shell, and
notable integration faults are logged through the local rolling logger.

Expand Down
39 changes: 39 additions & 0 deletions docs/design/ots/avaloniaedit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## AvaloniaEdit

### Purpose

AvaloniaEdit was chosen to provide read-only, syntax-highlighted text
viewing of a `.sysml` file's raw source without the workbench hand-rolling
its own text control, line-numbered gutter, or highlighting engine. It backs
the source-text viewer's document tabs, giving a user a faithful, readable
view of a file's exact on-disk text alongside its rendered diagrams.

### Features Used

- **`AvaloniaEdit.TextEditor`** — the hosted text control, configured
`IsReadOnly = true` so the source-text viewer can never mutate the
underlying file.
- **`AvaloniaEdit.Highlighting.Xshd.HighlightingLoader`** — loads a
self-authored XSHD v2 highlighting definition from an embedded
`AvaloniaResource` at runtime.
- **`AvaloniaEdit.Highlighting.HighlightingManager`** — registers the
loaded definition once, lazily, and resolves any XSHD-internal
references during load.

### Integration Pattern

`SourceTextDocumentView`'s code-behind loads and registers the embedded
`avares://DemaConsulting.SysML2Workbench/Assets/SysML.xshd` highlighting
definition once, via a static, lazily-initialized `IHighlightingDefinition`
shared across every open source-text tab, and assigns it to the hosted
`TextEditor.SyntaxHighlighting` property. The XSHD file itself is generic
(comment, string, and number rules only); the keyword-highlighting rule is
built at load time by reflecting
`DemaConsulting.SysML2Tools.Parser.Antlr.SysMLv2Lexer`'s literal-token table
rather than hand-copying a keyword list that could drift from the real
grammar, with a hard-coded fallback keyword list used if the reflection
call ever fails. `TextEditor.Text` is a plain CLR property rather than an
Avalonia styled property, so the view model's `Text` is assigned to it
imperatively in code-behind rather than through an XAML binding; this is
safe because the source-text viewer never changes `Text` after
construction. There is no write-back path in this phase.
10 changes: 7 additions & 3 deletions docs/design/sysml2-workbench/app-shell-subsystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@ environment.
discarded on close rather than living in the persistent Dock layout. The
diagram area hosts zero or more independently closable diagram
Documents - one per open `WorkbenchTab`, each bound to its own
`SvgCanvasHost` - and the Dock `DocumentDock` container itself remains
visibly present even when no diagram tab is open, so an empty diagram
area is a normal, supported state rather than a dead end. MainWindowShell
`SvgCanvasHost` - and the same `DocumentDock` container also hosts
independently closable read-only source-text Documents, one per
`WorkbenchTab` opened via double-click from the workspace tree, sharing
the diagram tabs' tabbed presentation area - and the Dock `DocumentDock`
container itself remains visibly present even when no diagram tab is
open, so an empty diagram area is a normal, supported state rather than
a dead end. MainWindowShell
itself has no dependency on Avalonia or Dock and is unaware of how its
panels are arranged on screen.
7. A workspace with zero sources open is a first-class, non-error state, not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ user is in predefined-view mode.
the user is composing a preview.

**OpenTabs**: `IReadOnlyList<WorkbenchTab>` — tabs representing rendered
diagrams, builder surfaces, or related shell content. Each `WorkbenchTab`
owns its own `SvgCanvasHost`, so multiple open diagram tabs have fully
diagrams, builder surfaces, read-only source-text views, or related shell
content. Each `WorkbenchTab` owns its own `SvgCanvasHost` (unused/never
loaded for a source-text tab), so multiple open diagram tabs have fully
independent zoom/pan/content state.

**ActiveTabId**: `string?` — identifier of the diagram tab currently
Expand All @@ -41,7 +42,17 @@ Each `WorkbenchTab` also carries a `SourceDefinition: ViewDefinitionModel?` —
the definition that produced the tab's currently rendered diagram, or `null`
when none could be derived (an unscoped predefined view with zero expose
members, or a brand-new custom-preview tab that has not rendered anything
yet). This backs the tab's "Copy as SysML" context-menu action.
yet, or a source-text tab, which has no rendered diagram at all). This backs
the tab's "Copy as SysML" context-menu action.

`WorkbenchTab` also carries a `FilePath: string?` — the absolute path of the
file a source-text tab displays; `null` for predefined-view and
custom-view-preview tabs. `WorkbenchTabKind` has a corresponding
`SourceText` member alongside `PredefinedView` and `CustomViewPreview`.
`WorkbenchTab.Id` for a source-text tab is the file's own absolute path,
which both gives it a stable dedupe key (opening the same file twice
re-focuses the existing tab rather than duplicating it) and lets
`GetTabFilePath` resolve it without exposing `OpenTabs` internals further.

#### Key Methods

Expand Down Expand Up @@ -151,6 +162,35 @@ UI focus.
when Dock reports a focus change onto a diagram document - never by
`MainWindowShell` itself, which stays Dock-agnostic.

**OpenSourceTextTab**: Opens a read-only source-text tab for a given file
path.

- *Parameters*: `string filePath` — absolute path of the `.sysml` file to
display.
- *Returns*: `WorkbenchTab` — the newly opened tab, or the already-open tab
for the same path if one exists.
- *Postconditions*: If a source-text tab for `filePath` is already open, it
is reused (its `FilePath` is left unchanged) and refocused rather than
duplicated; otherwise a new `WorkbenchTabKind.SourceText` tab is appended,
titled with the file's own name (`Path.GetFileName`) and identified by the
file's absolute path. Either way the tab becomes `ActiveTabId` and
`TabsChanged` is raised. Backs `WorkspacePanelToolView`'s double-click
handler; the view built for a source-text tab
(`SourceTextDocumentView`/`SourceTextDocumentViewModel`, see "Supporting
Types" below) reads the file's contents directly rather than through
`MainWindowShell`, which never loads file text itself.

**GetTabFilePath**: Resolves the file path of an open source-text tab.

- *Parameters*: `string tabId` — identifier of an open tab.
- *Returns*: `string?` — the tab's `FilePath`, or `null` when `tabId` does
not refer to a currently open tab, or refers to a tab of a different kind
(whose `FilePath` is always `null`). Mirrors the existing `GetTabCanvas`
lookup pattern.
- *Postconditions*: None (read-only). Lets `SourceTextDocumentViewModel`
resolve which file it displays without `MainWindowShell` needing to
expose `OpenTabs` internals further.

**CanExportTabAsSysml**: Reports whether an open diagram tab has a derivable
source definition and can export its diagram as a SysML snippet.

Expand All @@ -171,6 +211,20 @@ the diagram currently rendered in an open tab.
successful export is also logged at `Info` level, mirroring
`ExportCustomViewSnippet`'s existing style.

#### Supporting Types

**SourceTextDocumentViewModel**/**SourceTextDocumentView**: the Dock
`Document`/view pair backing a source-text tab, following the same
convention already used for `DiagramDocumentViewModel`/`DiagramDocumentView`
(neither pair is a separately documented unit). `SourceTextDocumentViewModel`
resolves its file path via `GetTabFilePath`, sets its `Title` to
`Path.GetFileName(FilePath)`, and eagerly loads `Text` via `File.ReadAllText`
once at construction, substituting a friendly in-editor error message for a
deleted or locked file instead of throwing (no caching, no file-watch, no
write-back). `SourceTextDocumentView` hosts a read-only AvaloniaEdit
`TextEditor` with SysML v2 syntax highlighting (see
`docs/design/ots/avaloniaedit.md`).

#### Error Handling

MainWindowShell handles user-cancelled operations, empty workspace selections,
Expand Down Expand Up @@ -218,6 +272,9 @@ the time subscribers observe the notification.
notifications onto the thread required by UI-facing subscribers; defaults
to an immediate, synchronous dispatcher when none is supplied.
- **Avalonia** — provides the window, tab, and application-lifetime framework.
- **AvaloniaEdit** — provides the read-only, syntax-highlighted `TextEditor`
control hosted by `SourceTextDocumentView` (see
`docs/design/ots/avaloniaedit.md`).

#### Callers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ already covers everything beneath it).
**WorkspaceFileNode**: `WorkspaceTreeNode` with `required string FilePath`,
`required string SourceId`, and a computed `Name` (`Path.GetFileName(FilePath)`,
shown in the tree since ancestor nodes already convey the file's directory) —
a leaf node for one file. `FilePath` is a stable identity intended for a
future, out-of-scope, double-click read-only file viewer; it is preserved
even though nothing else currently reads it beyond display.
a leaf node for one file. `FilePath` is a stable identity used by
`WorkspacePanelToolView`'s double-click handler to open a `.sysml` file's
read-only source-text tab via `MainWindowShell.OpenSourceTextTab`; it is
preserved even though nothing else currently reads it beyond display and
that handler.

**SelectedNode**: `WorkspaceTreeNode?` — the tree node currently selected by
the user, used to resolve which source `RemoveSelected` acts on.
Expand Down Expand Up @@ -94,6 +96,15 @@ into `MainWindowShell.AddFileSourceAsync` / `AddFolderSourceAsync`.
and surfaced via `StatusMessage` rather than propagated, since a failed
remove should not crash the shell.

`WorkspacePanelToolView`'s code-behind also wires a `DoubleTapped` handler on
its `TreeView` directly to `MainWindowShell.OpenSourceTextTab`: when the
selected item is a `WorkspaceFileNode` whose `FilePath` ends in `.sysml`
(case-insensitive), double-clicking it opens the file's read-only
source-text tab. No view-model method backs this - it follows the same
"view calls `Shell` directly" pattern already used by the Add File/Add
Folder picker flows, and is thin enough that it is not unit tested (see
`docs/verification/sysml2-workbench/app-shell-subsystem/workspace-panel.md`).

#### Error Handling

WorkspacePanel treats a failed `RemoveSelectedAsync` as a locally recoverable
Expand Down
20 changes: 20 additions & 0 deletions docs/reqstream/ots/avaloniaedit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
sections:
- title: 'OTS Software Requirements'
sections:
- title: 'AvaloniaEdit Requirements'
requirements:
- id: 'AvaloniaEdit-RenderReadOnlySyntaxHighlightedText'
title: 'The SysML2Workbench shall render read-only, syntax-highlighted .sysml source text using AvaloniaEdit''s TextEditor control.'
justification: |
AvaloniaEdit provides the text-editing control, read-only enforcement, and highlighting engine required for the source-text viewer without the workbench hand-rolling its own text control.
tests:
- 'Text_OpenedFile_MatchesFileContents'
- 'TextEditor_IsReadOnly_PreventsEdits'

- id: 'AvaloniaEdit-LoadEmbeddedSysmlHighlighting'
title: 'The SysML2Workbench shall load a self-authored SysML v2 highlighting definition from an embedded resource via AvaloniaEdit''s HighlightingLoader, with keywords derived from the real SysML v2 lexer rather than a hand-copied list.'
justification: |
Highlighting fidelity depends on the keyword set staying in sync with the actual grammar; loading the definition through AvaloniaEdit's own XSHD loader and populating keywords by reflecting the lexer avoids a hand-copied list silently drifting out of date.
tests:
- 'EmbeddedXshd_Loads_AndKeywordRuleMatchesSysmlKeyword'
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,12 @@ sections:
tests:
- 'RenderCustomViewPreview_DoesNotMutateOpenTabsOrActiveTab'
- 'RenderCustomViewPreview_NoWorkspaceOpened_ThrowsInvalidOperationException'

- id: 'SysML2Workbench-AppShellSubsystem-MainWindowShell-OpenSourceTextTab'
title: 'The MainWindowShell shall open a read-only source-text tab for a given file path, reusing an already-open tab for the same path instead of duplicating it, and shall resolve an open source-text tab''s file path on request.'
justification: |
Double-clicking a .sysml file in the workspace tree must open its raw text in its own tab without duplicating tabs on repeated double-clicks, and the resulting view model needs a way to resolve which file it displays without MainWindowShell exposing OpenTabs internals further.
tests:
- 'OpenSourceTextTab_NewFile_CreatesOneNewActiveTab'
- 'OpenSourceTextTab_SamePathTwice_RefocusesExistingTabWithoutDuplicating'
- 'GetTabFilePath_ReturnsPathForSourceTextTab_AndNullOtherwise'
7 changes: 7 additions & 0 deletions docs/sysml2/model/ots.sysml
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,12 @@ package SysML2WorkbenchArchitecture {
comment verificationRef /* Verification: docs/verification/ots/communitytoolkit-mvvm.md */
comment reqRef /* Requirements: docs/reqstream/ots/communitytoolkit-mvvm.yaml */
}
part def AvaloniaEdit {
doc /* Avalonia.AvaloniaEdit NuGet dependency for the read-only, syntax-highlighted text editor control backing the source-text viewer document tabs. */

comment designRef /* Design: docs/design/ots/avaloniaedit.md */
comment verificationRef /* Verification: docs/verification/ots/avaloniaedit.md */
comment reqRef /* Requirements: docs/reqstream/ots/avaloniaedit.yaml */
}
}
}
9 changes: 9 additions & 0 deletions docs/user_guide/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ no concrete view definition can be derived for the tab, for example an
unscoped predefined view or a custom-view preview tab that has not yet been
rendered.

## Viewing a File's Raw Source

Double-click any `.sysml` file in the **Workspace** panel's tree to open its
raw source text, read-only, with SysML v2 syntax highlighting, in its own
tab in the same tabbed area as diagram tabs. Double-clicking the same file
again switches focus to its already-open tab instead of opening a
duplicate. This is a view-only capability - editing and saving `.sysml`
files is a future phase (see "Out of Scope" below).

## Building a Custom View

Choose **View > Custom View Builder...** to open the View Builder dialog. The
Expand Down
28 changes: 28 additions & 0 deletions docs/verification/ots/avaloniaedit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## AvaloniaEdit

### Verification Approach

OTS integration tests in `test/OtsSoftwareTests/AvaloniaEditTests.cs` verify AvaloniaEdit's read-only enforcement
and highlighting-load behavior directly: `[AvaloniaFact]` tests host a real, headless `AvaloniaEdit.TextEditor` to
confirm its `IsReadOnly` mode installs a section provider that genuinely rejects insertion, and that the embedded
`Assets/SysML.xshd` highlighting definition loads via `HighlightingLoader.Load` without throwing and defines a
keyword-highlighting rule (built the same way `SourceTextDocumentView` builds it at runtime, by reflecting the real
SysML v2 lexer's literal-token table) that matches a genuine SysML v2 keyword. Unit tests in
`test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/SourceTextDocumentViewModelTests.cs` additionally
verify that a file's text is faithfully surfaced, unmodified, to the editor's view model.

### Test Scenarios

**TextEditor_IsReadOnly_PreventsEdits**: A real, headless-hosted `TextEditor` with `IsReadOnly = true` installs a
`TextArea.ReadOnlySectionProvider` whose `CanInsert` rejects insertion anywhere in the document, while a normal
(non-read-only) editor's provider allows it - proving the read-only "View Source" tab genuinely cannot be edited
through the editor's own input pipeline.

**EmbeddedXshd_Loads_AndKeywordRuleMatchesSysmlKeyword**: The embedded `Assets/SysML.xshd` resource loads via
`HighlightingLoader.Load` without throwing and defines the expected named colors (Keyword, Comment, String,
Number); a keyword-highlighting rule built the same way `SourceTextDocumentView` builds it at runtime - by
independently reflecting `SysMLv2Lexer._LiteralNames` - matches a real SysML v2 keyword (`part`).

**Text_OpenedFile_MatchesFileContents**: `SourceTextDocumentViewModel.Text`, backing the hosted `TextEditor`'s
displayed content, exactly matches the contents of the opened temp file. Verified by
`SourceTextDocumentViewModelTests.Text_OpenedFile_MatchesFileContents`.
Loading
Loading