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
4 changes: 2 additions & 2 deletions src/App/AppKeyHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,13 @@ internal bool HandleActionMenu()
}

if (mt.Table is null || mt.GetRawColumnName is null
|| mt.OnMorphAction is null || mt.SelectedColumn < 0)
|| mt.OnMorphAction is null || mt.Value is null)
{
return false;
}

var handler = new ColumnActionHandler(
_app, mt.Table, mt.SelectedColumn,
_app, mt.Table, mt.Value.Cursor.X,
mt.GetRawColumnName, mt.OnMorphAction, mt.IsRowIndexComplete);

using var dialog = new ActionMenuDialog(ColumnActionHandler.GetAvailableActions(), handler.ExecuteAction);
Expand Down
2 changes: 1 addition & 1 deletion src/App/DataMorph.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Terminal.Gui" Version="2.0.0-develop.5264" />
<PackageReference Include="Terminal.Gui" Version="2.0.1" />
<PackageReference Include="Sep" Version="0.13.0" />
</ItemGroup>

Expand Down
101 changes: 60 additions & 41 deletions src/App/FileDialogHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,64 +68,83 @@ internal async Task HandleFileSelectedAsync(string path)
try
{
var schema = await schemaScanner.InitialScanAsync();
if (schema.Columns.Count == 0)
_app.Invoke(() =>
{
_viewManager.ShowError("File contains no data");
return;
}

_state.Schema = schema;
_state.RowIndexer = indexer;
_state.CsvSchemaScanner = schemaScanner;
_state.CurrentMode = ViewMode.CsvTable;

_viewManager.SwitchToCsvTable(indexer, schema);

_ = schemaScanner
.StartBackgroundScanAsync(schema, _state.Cts.Token)
.ContinueWith(
t =>
{
if (!t.IsCompletedSuccessfully)
if (schema.Columns.Count == 0)
{
_viewManager.ShowError("File contains no data");
return;
}

_state.Schema = schema;
_state.RowIndexer = indexer;
_state.CsvSchemaScanner = schemaScanner;
_state.CurrentMode = ViewMode.CsvTable;

_viewManager.SwitchToCsvTable(indexer, schema);

_ = schemaScanner
.StartBackgroundScanAsync(schema, _state.Cts.Token)
.ContinueWith(
t =>
{
return;
}

_state.Schema = t.Result;
_state.OnSchemaRefined?.Invoke(t.Result);
},
TaskScheduler.Default
);

_onIndexerStart(indexer);
if (!t.IsCompletedSuccessfully)
{
return;
}

_app.Invoke(() =>
{
_state.Schema = t.Result;
_state.OnSchemaRefined?.Invoke(t.Result);
});
},
TaskScheduler.Default
);

_onIndexerStart(indexer);
});
return;
}
#pragma warning disable CA1031 // UI top-level handler
catch (Exception ex)
#pragma warning restore CA1031
{
_viewManager.ShowError($"Error scanning CSV: {ex.Message}");
_app.Invoke(() => _viewManager.ShowError($"Error scanning CSV: {ex.Message}"));
return;
}
}

if (format == DataFormat.JsonLines)
{
_state.RowIndexer = indexer;
_state.JsonLinesSchemaScanner = null;
_state.Schema = null;
_state.OnSchemaRefined = null;
try
{
_state.RowIndexer = indexer;
_state.JsonLinesSchemaScanner = null;
_state.Schema = null;
_state.OnSchemaRefined = null;

var tcs = new TaskCompletionSource(
TaskCreationOptions.RunContinuationsAsynchronously);
indexer.FirstCheckpointReached += () => tcs.TrySetResult();
var tcs = new TaskCompletionSource(
TaskCreationOptions.RunContinuationsAsynchronously);
indexer.FirstCheckpointReached += () => tcs.TrySetResult();

_onIndexerStart(indexer);
await tcs.Task;
_onIndexerStart(indexer);
await tcs.Task;

_state.CurrentMode = ViewMode.JsonLinesTree;
_viewManager.SwitchToJsonLinesTree(indexer);
return;
_app.Invoke(() =>
{
_state.CurrentMode = ViewMode.JsonLinesTree;
_viewManager.SwitchToJsonLinesTree(indexer);
});
return;
}
#pragma warning disable CA1031 // UI top-level handler
catch (Exception ex)
#pragma warning restore CA1031
{
_app.Invoke(() => _viewManager.ShowError($"Error loading JSON Lines: {ex.Message}"));
return;
}
}

_onIndexerStart(indexer);
Expand Down
8 changes: 6 additions & 2 deletions src/App/MainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ internal sealed class MainWindow : Window
)]
private Label? _progressLabel;

[SuppressMessage(
"Reliability",
"CA2213:Disposable fields should be disposed",
Justification = "Child views added to the Window will be disposed automatically when the Window is disposed."
)]
private StatusBar? _statusBar;

public MainWindow(IApplication app, AppState state)
Expand All @@ -53,7 +58,7 @@ public MainWindow(IApplication app, AppState state)
Width = Dim.Fill();
Height = Dim.Fill();

_viewManager = new ViewManager(this, state, _modeController);
_viewManager = new ViewManager(this, state, _modeController, app.Invoke);

_fileDialogHandler = new FileDialogHandler(app, state, _viewManager, StartIndexing);
_recipeCommandHandler = new RecipeCommandHandler(app, state, _viewManager);
Expand Down Expand Up @@ -118,7 +123,6 @@ protected override void Dispose(bool disposing)
_indexTaskManager.Dispose();
_state.Dispose();
_viewManager.Dispose();
_statusBar?.Dispose();
}
base.Dispose(disposing);
}
Expand Down
77 changes: 59 additions & 18 deletions src/App/ViewManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@ internal sealed class ViewManager : IDisposable
private readonly Window _container;
private readonly AppState _state;
private readonly ModeController _modeController;
private readonly Action<Action> _uiThreadInvoke;
private View? _currentView;
private bool _disposed;

internal ViewManager(Window container, AppState state, ModeController modeController)
internal ViewManager(Window container, AppState state, ModeController modeController, Action<Action> uiThreadInvoke)
{
ArgumentNullException.ThrowIfNull(container);
ArgumentNullException.ThrowIfNull(state);
ArgumentNullException.ThrowIfNull(modeController);
ArgumentNullException.ThrowIfNull(uiThreadInvoke);
_container = container;
_state = state;
_modeController = modeController;
_uiThreadInvoke = uiThreadInvoke;
}

/// <summary>
Expand Down Expand Up @@ -89,26 +92,29 @@ internal async Task ToggleJsonLinesModeAsync()
{
var result = await _modeController.ToggleJsonLinesModeAsync();

if (result.IsFailure)
_uiThreadInvoke(() =>
{
ShowError(result.Error);
RefreshStatusBarHints();
return;
}
if (result.IsFailure)
{
ShowError(result.Error);
RefreshStatusBarHints();
return;
}

if (_state.CurrentMode == ViewMode.JsonLinesTree && _state.RowIndexer is not null)
{
SwitchToJsonLinesTree(_state.RowIndexer);
return;
}
if (_state.CurrentMode == ViewMode.JsonLinesTree && _state.RowIndexer is not null)
{
SwitchToJsonLinesTree(_state.RowIndexer);
return;
}

if (
_state.CurrentMode == ViewMode.JsonLinesTable
&& _state.RowIndexer is not null
&& _state.Schema is not null)
{
SwitchToJsonLinesTableView(_state.RowIndexer, _state.Schema);
}
if (
_state.CurrentMode == ViewMode.JsonLinesTable
&& _state.RowIndexer is not null
&& _state.Schema is not null)
{
SwitchToJsonLinesTableView(_state.RowIndexer, _state.Schema);
}
});
}

/// <summary>
Expand Down Expand Up @@ -170,7 +176,9 @@ internal void SwitchToCsvTable(IRowIndexer indexer, TableSchema schema)
OnMorphAction = HandleMorphAction,
GetRawColumnName = getRawColumnName,
};
SetInitialSelectionWhenReady(view, indexer);
SwapView(view);
view.SetFocus();
RefreshStatusBarHints();

if (source is Views.LazyTransformer { FilterRowIndexer: { } filterIndexer })
Expand Down Expand Up @@ -258,7 +266,9 @@ [.. schema.Columns.Select(c => Encoding.UTF8.GetBytes(c.Name))],
OnMorphAction = HandleMorphAction,
GetRawColumnName = getRawColumnName,
};
SetInitialSelectionWhenReady(view, indexer);
SwapView(view);
view.SetFocus();
RefreshStatusBarHints();

if (tableSource is Views.LazyTransformer { FilterRowIndexer: { } filterIndexer })
Expand Down Expand Up @@ -318,6 +328,37 @@ internal void ShowError(string message)
SwapView(view);
}

private void SetInitialSelectionWhenReady(MorphTableView view, IRowIndexer indexer)
{
if (indexer.TotalRows > 0)
{
view.SetSelection(0, 0, false);
view.Update();
return;
}

void onReady()
{
// Unsubscribe immediately to ensure initial selection logic runs only once
// and to release the captured view reference for garbage collection.
indexer.FirstCheckpointReached -= onReady;

_uiThreadInvoke(() =>
{
// Only set if this view is still active and the user hasn't moved the cursor yet
if (_currentView == view && view.Table is not null && view.Table.Rows > 0
&& (view.Value is null || view.Value.Cursor.Y <= 0))
{
view.SetSelection(0, 0, false);
view.Update();
view.SetNeedsDraw();
}
});
}

indexer.FirstCheckpointReached += onReady;
}

private void SwapView(View newView)
{
if (_currentView is not null)
Expand Down
2 changes: 1 addition & 1 deletion src/App/Views/Dialogs/HelpDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@ protected override bool OnKeyDown(Key key)
return true;
}

return base.OnKeyDown(key);
return false;
}
}
16 changes: 11 additions & 5 deletions src/App/Views/JsonLinesTreeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,24 @@ public JsonLinesTreeView(IRowIndexer indexer, Action onTableModeToggle)
_onTableModeToggle = onTableModeToggle;
LoadInitialRootNodes();

ObjectActivated += OnObjectActivated;
Accepted += OnAccepted;
}

private void OnObjectActivated(object? sender, ObjectActivatedEventArgs<ITreeNode> e)
private void OnAccepted(object? sender, CommandEventArgs e)
{
if (IsExpanded(e.ActivatedObject))
var node = SelectedObject;
if (node is null)
{
Collapse(e.ActivatedObject);
return;
}

Expand(e.ActivatedObject);
if (IsExpanded(node))
{
Collapse(node);
return;
}

Expand(node);
}

private void LoadInitialRootNodes()
Expand Down
2 changes: 1 addition & 1 deletion src/App/Views/JsonTreeNodes/JsonValueTreeNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ internal sealed class JsonValueTreeNode : TreeNode
/// </summary>
/// <param name="text">The display text for this value node.</param>
public JsonValueTreeNode(string text)
: base(text)
{
Text = text;
Children = [];
}

Expand Down
11 changes: 11 additions & 0 deletions src/App/Views/LazyTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,15 @@ public LazyTransformer(
/// </summary>
internal string[] RawColumnNames => _rawColumnNames;

private bool _disposed;

/// <inheritdoc/>
public object this[int row, int col]
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);

if (row < 0 || row >= Rows)
{
throw new ArgumentOutOfRangeException(nameof(row));
Expand Down Expand Up @@ -322,9 +326,16 @@ private sealed record WorkingColumn(

public void Dispose()
{
if (_disposed)
{
return;
}

if (_source is IDisposable d)
{
d.Dispose();
}

_disposed = true;
}
}
Loading