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
82 changes: 0 additions & 82 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -339,85 +339,3 @@ dotnet_diagnostic.CA1849.severity = none
# Test code runs on the test context and doesn't need ConfigureAwait.
# xUnit tests should not use ConfigureAwait(false) (xUnit1030).
dotnet_diagnostic.CA2007.severity = none

# SonarAnalyzer.CSharp: S3776/S1541 (Cognitive/Cyclomatic Complexity) pre-existing violations,
# grandfathered per-file pending a follow-up refactor task.
[src/Engine/Filtering/FilterEvaluator.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/ColumnTypeResolver.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/ActionApplier.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/Csv/DataRowReader.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/Recipes/RecipeYamlParser.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/JsonObject/TopLevelScanner.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/DrillDown/KeyPathTraverser.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/JsonArray/RowIndexer.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/JsonArray/ElementReader.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/JsonLines/TypeInferrer.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/JsonLines/SchemaScanner.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/Engine/IO/JsonLines/RowReader.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/App/Cli/ArgumentParser.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/App/AppKeyHandler.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/App/Views/VimKeyTranslator.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/App/FileDialogHandler.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/App/ViewManager.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/App/Views/MorphTableView.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/App/Views/MorphTreeView.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none

[src/App/Views/LazyTransformer.cs]
dotnet_diagnostic.S3776.severity = none
dotnet_diagnostic.S1541.severity = none
142 changes: 82 additions & 60 deletions src/App/AppKeyHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,62 +164,72 @@ private bool HandleViewToggle()
return false;
}

[SuppressMessage(
"Reliability",
"CA2000:Dispose objects before losing scope",
Justification = "The dialog is managed by Terminal.Gui's IApplication.Run() and will be disposed automatically."
)]
internal bool HandleActionMenu()
{
var currentView = _viewManager.GetCurrentView();

if (currentView is MorphTableView mt)
{
if (mt.Table is null || mt.GetRawColumnName is null
|| mt.OnMorphAction is null || mt.Value is null)
{
return false;
}
return HandleActionMenuForTable(mt);
}

var format = FormatDetector.Detect(_state.CurrentFilePath);
if (format.IsFailure)
{
_app.Invoke(() => _viewManager.ShowError(format.Error));
return false;
}
if (currentView is MorphTreeView tv)
{
return HandleActionMenuForTree(tv);
}

var handler = new ColumnActionHandler(
_app, mt.Table, mt.Value.SelectedCell.X,
mt.GetRawColumnName, mt.OnMorphAction, format.Value, mt.IsRowIndexComplete);
return false;
}

var dialog = new ActionMenuDialog(ColumnActionHandler.GetAvailableActions(), handler.ExecuteAction);
_app.Run(dialog);
return true;
[SuppressMessage(
"Reliability",
"CA2000:Dispose objects before losing scope",
Justification = "The dialog is managed by Terminal.Gui's IApplication.Run() and will be disposed automatically."
)]
private bool HandleActionMenuForTable(MorphTableView mt)
{
if (mt.Table is null || mt.GetRawColumnName is null
|| mt.OnMorphAction is null || mt.Value is null)
{
return false;
}

if (currentView is MorphTreeView tv)
var format = FormatDetector.Detect(_state.CurrentFilePath);
if (format.IsFailure)
{
if (tv.SelectedObject is not ITreeNode selectedNode)
{
return false;
}
_app.Invoke(() => _viewManager.ShowError(format.Error));
return false;
}

var treeFormat = FormatDetector.Detect(_state.CurrentFilePath);
if (treeFormat.IsFailure)
{
_app.Invoke(() => _viewManager.ShowError(treeFormat.Error));
return false;
}
var handler = new ColumnActionHandler(
_app, mt.Table, mt.Value.SelectedCell.X,
mt.GetRawColumnName, mt.OnMorphAction, format.Value, mt.IsRowIndexComplete);

if (treeFormat.Value == DataFormat.JsonObject)
{
return HandleSingleDrillDown(selectedNode, treeFormat.Value);
}
var dialog = new ActionMenuDialog(ColumnActionHandler.GetAvailableActions(), handler.ExecuteAction);
_app.Run(dialog);
return true;
}

return HandleFullAggregationDrillDown(selectedNode, treeFormat.Value);
private bool HandleActionMenuForTree(MorphTreeView tv)
{
if (tv.SelectedObject is not ITreeNode selectedNode)
{
return false;
}

return false;
var treeFormat = FormatDetector.Detect(_state.CurrentFilePath);
if (treeFormat.IsFailure)
{
_app.Invoke(() => _viewManager.ShowError(treeFormat.Error));
return false;
}

if (treeFormat.Value == DataFormat.JsonObject)
{
return HandleSingleDrillDown(selectedNode, treeFormat.Value);
}

return HandleFullAggregationDrillDown(selectedNode, treeFormat.Value);
}

/// <summary>
Expand Down Expand Up @@ -348,39 +358,51 @@ private void OnGlobalKeyDown(object? sender, Key key)
return;
}

// Skip global key handling when a text input view is focused
if (IsTextFieldFocused())
{
return;
}

// Shortcuts like o, s, q, t, x, Backspace should not have Ctrl or Alt modifiers.
if ((key.KeyCode & (KeyCode.CtrlMask | KeyCode.AltMask)) != 0)
{
return;
}

key.Handled = DispatchShortcut(key.KeyCode & ~KeyCode.ShiftMask);
}

private bool DispatchShortcut(KeyCode baseKey) => baseKey switch
{
KeyCode.O => HandleOpen(),
KeyCode.S => HandleSave(),
KeyCode.Q => HandleQuit(),
KeyCode.T => HandleViewToggle(),
KeyCode.X => HandleActionMenu(),
KeyCode.C => HandleClearActions(),
KeyCode.Backspace => HandleDrillDownBack(),
(KeyCode)'?' => HandleHelp(),
_ => false,
};

// Walks up the SuperView chain since focus may be on a child of the TextField
// (e.g. its internal cursor/selection handling), not the TextField itself.
private bool IsTextFieldFocused()
{
var focused = _app.Navigation?.GetFocused() ?? _app.TopRunnableView?.MostFocused;
var current = focused;
while (current is not null)
{
var type = current.GetType();
if (current is TextField || type.Name == "TextField" || type.FullName == "Terminal.Gui.Views.TextField")
{
return;
return true;
}

current = current.SuperView;
}

// Shortcuts like o, s, q, t, x, Backspace should not have Ctrl or Alt modifiers.
if ((key.KeyCode & (KeyCode.CtrlMask | KeyCode.AltMask)) != 0)
{
return;
}

var baseKey = key.KeyCode & ~KeyCode.ShiftMask;
key.Handled = baseKey switch
{
KeyCode.O => HandleOpen(),
KeyCode.S => HandleSave(),
KeyCode.Q => HandleQuit(),
KeyCode.T => HandleViewToggle(),
KeyCode.X => HandleActionMenu(),
KeyCode.C => HandleClearActions(),
KeyCode.Backspace => HandleDrillDownBack(),
(KeyCode)'?' => HandleHelp(),
_ => false,
};
return false;
}

public void Dispose()
Expand Down
87 changes: 46 additions & 41 deletions src/App/Cli/ArgumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,56 +35,61 @@ public static Result<Arguments> Parse(IReadOnlyList<string> args)

while (i < args.Count)
{
if (!args[i].StartsWith("--", StringComparison.Ordinal))
var stepResult = ParseNextToken(args, i, result);
if (stepResult.IsFailure)
{
return Results.Failure<Arguments>($"Invalid flag: '{args[i]}'");
return Results.Failure<Arguments>(stepResult.Error);
}

if (args[i].Equals(CliFlag, StringComparison.Ordinal))
{
i += 1;
continue;
}
i = stepResult.Value;
}

if (args[i].Equals(InputFlag, StringComparison.Ordinal))
{
if (i + 1 >= args.Count || args[i + 1].StartsWith("--", StringComparison.Ordinal))
{
return Results.Failure<Arguments>($"Missing value for {InputFlag}");
}

result.InputFile = args[i + 1];
i += 2;
continue;
}
return BuildArguments(result);
}

if (args[i].Equals(RecipeFlag, StringComparison.Ordinal))
{
if (i + 1 >= args.Count || args[i + 1].StartsWith("--", StringComparison.Ordinal))
{
return Results.Failure<Arguments>($"Missing value for {RecipeFlag}");
}

result.RecipeFile = args[i + 1];
i += 2;
continue;
}
private static Result<int> ParseNextToken(IReadOnlyList<string> args, int i, ArgumentsParseResult result)
{
if (!args[i].StartsWith("--", StringComparison.Ordinal))
{
return Results.Failure<int>($"Invalid flag: '{args[i]}'");
}

if (args[i].Equals(OutputFlag, StringComparison.Ordinal))
{
if (i + 1 >= args.Count || args[i + 1].StartsWith("--", StringComparison.Ordinal))
{
return Results.Failure<Arguments>($"Missing value for {OutputFlag}");
}

result.OutputFile = args[i + 1];
i += 2;
continue;
}
if (args[i].Equals(CliFlag, StringComparison.Ordinal))
{
return Results.Success(i + 1);
}

return Results.Failure<Arguments>($"Unknown flag: {args[i]}");
if (args[i].Equals(InputFlag, StringComparison.Ordinal))
{
return ConsumeValueFlag(args, i, InputFlag, value => result.InputFile = value);
}

if (args[i].Equals(RecipeFlag, StringComparison.Ordinal))
{
return ConsumeValueFlag(args, i, RecipeFlag, value => result.RecipeFile = value);
}

if (args[i].Equals(OutputFlag, StringComparison.Ordinal))
{
return ConsumeValueFlag(args, i, OutputFlag, value => result.OutputFile = value);
}

return Results.Failure<int>($"Unknown flag: {args[i]}");
}

private static Result<int> ConsumeValueFlag(IReadOnlyList<string> args, int i, string flag, Action<string> assign)
{
if (i + 1 >= args.Count || args[i + 1].StartsWith("--", StringComparison.Ordinal))
{
return Results.Failure<int>($"Missing value for {flag}");
}

assign(args[i + 1]);
return Results.Success(i + 2);
}

private static Result<Arguments> BuildArguments(ArgumentsParseResult result)
{
if (string.IsNullOrWhiteSpace(result.InputFile))
{
return Results.Failure<Arguments>($"Missing required flag: {InputFlag}");
Expand Down
Loading