diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index c5b1b373a..2ff3df80a 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -40,9 +40,29 @@ into OneWare Studio regardless of OneWare's own license: The full GPL v2 text accompanies the libgit2 distribution in the LibGit2Sharp.NativeBinaries package (libgit2/libgit2.license.txt). +================================================================================ +SYNTAX HIGHLIGHTING GRAMMARS +================================================================================ +The Verilog, SystemVerilog, and Verilog filelist grammars were imported from +https://github.com/mshr-h/vscode-verilog-hdl-support at revision +e6a5a2b76055dd7acbdc7420060e0314cb86e8dd. + +The SystemVerilog grammar is derived from VSCode-SystemVerilog: +Copyright (c) 2018 Eirik Prestegårdshus. It is licensed under the MIT License +reproduced below. + +The Verilog grammar is derived from the TextMate Verilog Bundle: + +Permission to copy, use, modify, sell and distribute this software is granted. +This software is provided "as is" without express or implied warranty, and +with no claim as to its suitability for any purpose. + ================================================================================ COMPONENTS UNDER THE MIT LICENSE ================================================================================ + - VSCode-SystemVerilog syntax grammar (revision e6a5a2b76055dd7acbdc7420060e0314cb86e8dd) + Copyright (c) 2018 Eirik Prestegårdshus + Source: https://github.com/mshr-h/vscode-verilog-hdl-support - Asmichi.ChildProcess 0.18.0 - Avalonia 11.3.11 - Avalonia 11.3.12 diff --git a/src/OneWare.ApplicationCommands/Tabs/CommandManagerSymbolsTab.cs b/src/OneWare.ApplicationCommands/Tabs/CommandManagerSymbolsTab.cs new file mode 100644 index 000000000..94a40fe2e --- /dev/null +++ b/src/OneWare.ApplicationCommands/Tabs/CommandManagerSymbolsTab.cs @@ -0,0 +1,8 @@ +using Avalonia.LogicalTree; + +namespace OneWare.ApplicationCommands.Tabs; + +public class CommandManagerSymbolsTab(ILogical logical) : CommandManagerTabBase("Symbols", logical) +{ + public override string SearchBarText => ""; +} diff --git a/src/OneWare.ApplicationCommands/ViewModels/CommandManagerViewModel.cs b/src/OneWare.ApplicationCommands/ViewModels/CommandManagerViewModel.cs index 45b20dafb..4dd4e5293 100644 --- a/src/OneWare.ApplicationCommands/ViewModels/CommandManagerViewModel.cs +++ b/src/OneWare.ApplicationCommands/ViewModels/CommandManagerViewModel.cs @@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.Input; using DynamicData; using DynamicData.Binding; +using OneWare.ApplicationCommands.Models; using OneWare.ApplicationCommands.Tabs; using OneWare.ApplicationCommands.Views; using OneWare.Essentials.Commands; @@ -28,21 +29,28 @@ public partial class CommandManagerViewModel : FlexibleWindowViewModelBase private readonly IApplicationCommandService _applicationCommandService; private readonly IProjectExplorerService _projectExplorerService; + private readonly ILanguageManager _languageManager; + private readonly IMainDockService _mainDockService; private readonly IWindowService _windowService; [ObservableProperty] private CommandManagerTabBase _selectedTab; private readonly CommandManagerAllTab _allTab; private readonly CommandManagerFilesTab _filesTab; + private readonly CommandManagerSymbolsTab _symbolsTab; private readonly CommandManagerActionsTab _actionsTab; private readonly IReadOnlyCollection _staticCommands; private CancellationTokenSource _fileSearchCancellation = new(); + private CancellationTokenSource _symbolSearchCancellation = new(); public CommandManagerViewModel(ILogical logical, IApplicationCommandService commandService, - IProjectExplorerService projectExplorerService, IWindowService windowService) + IProjectExplorerService projectExplorerService, IWindowService windowService, + ILanguageManager languageManager, IMainDockService mainDockService) { ActiveFocus = logical; _projectExplorerService = projectExplorerService; + _languageManager = languageManager; + _mainDockService = mainDockService; _applicationCommandService = commandService; _windowService = windowService; _staticCommands = commandService.ApplicationCommands.ToList(); @@ -57,6 +65,11 @@ public CommandManagerViewModel(ILogical logical, IApplicationCommandService comm Items = new ObservableCollection() }; Tabs.Add(_filesTab); + _symbolsTab = new CommandManagerSymbolsTab(logical) + { + Items = new ObservableCollection() + }; + Tabs.Add(_symbolsTab); _actionsTab = new CommandManagerActionsTab(logical) { Items = commandService.ApplicationCommands @@ -72,6 +85,9 @@ public CommandManagerViewModel(ILogical logical, IApplicationCommandService comm _filesTab.WhenValueChanged(x => x.SearchText) .Throttle(TimeSpan.FromMilliseconds(150)) .Subscribe(text => _ = UpdateFileSearchAsync(_filesTab, text)); + _symbolsTab.WhenValueChanged(x => x.SearchText) + .Throttle(TimeSpan.FromMilliseconds(150)) + .Subscribe(text => _ = UpdateSymbolSearchAsync(text)); } public static KeyGesture ChangeShortcutGesture => new(Key.Enter, PlatformHelper.ControlKey); @@ -164,6 +180,61 @@ private IReadOnlyList FindFileCommands(string searchText, C return sorted; } + private async Task UpdateSymbolSearchAsync(string? searchText) + { + await _symbolSearchCancellation.CancelAsync(); + _symbolSearchCancellation.Dispose(); + _symbolSearchCancellation = new CancellationTokenSource(); + var token = _symbolSearchCancellation.Token; + + if (string.IsNullOrWhiteSpace(searchText) || _mainDockService.CurrentDocument is not IEditor editor) + { + await Dispatcher.UIThread.InvokeAsync(() => ApplySymbolCommands([])); + return; + } + + var languageService = _languageManager.GetLanguageService(editor.FullPath); + if (languageService == null) + { + await Dispatcher.UIThread.InvokeAsync(() => ApplySymbolCommands([])); + return; + } + + IsSearching = true; + var symbols = await languageService.RequestWorkspaceSymbolsAsync(searchText); + IsSearching = false; + if (token.IsCancellationRequested) return; + + var commands = symbols? + .Where(symbol => symbol.Location.IsLocation && symbol.Location.Location != null) + .Select(symbol => + { + var location = symbol.Location.Location!; + var path = location.Uri.GetFileSystemPath(); + return (IApplicationCommand)new OpenSymbolApplicationCommand(symbol.Name, path, + location.Range.Start.Line, location.Range.Start.Character) + { + Detail = string.IsNullOrWhiteSpace(symbol.ContainerName) + ? Path.GetFileName(path) + : $"{symbol.ContainerName} - {Path.GetFileName(path)}" + }; + }) + .Take(MaxFileResults) + .ToList() ?? []; + + await Dispatcher.UIThread.InvokeAsync(() => ApplySymbolCommands(commands)); + } + + private void ApplySymbolCommands(IReadOnlyList commands) + { + _symbolsTab.Items.Clear(); + _symbolsTab.Items.AddRange(commands); + _symbolsTab.VisibleItems = commands + .Select(command => new CommandManagerItemModel(command, command.CanExecute(ActiveFocus))) + .ToList(); + _symbolsTab.SelectedItem = _symbolsTab.VisibleItems.FirstOrDefault(); + } + private static int ScoreFileMatch(string relativePath, string searchText) { var fileName = Path.GetFileName(relativePath); diff --git a/src/OneWare.Core/Services/FileIconService.cs b/src/OneWare.Core/Services/FileIconService.cs index e9ddbf734..e81b0aa63 100644 --- a/src/OneWare.Core/Services/FileIconService.cs +++ b/src/OneWare.Core/Services/FileIconService.cs @@ -21,9 +21,9 @@ public FileIconService(ILogger logger) RegisterFileIcon("GhdpFileIcon", ".ghdp"); RegisterFileIcon("VhdpFileIcon", ".vhdp"); RegisterFileIcon("VhdlFileIcon", ".vhd", ".vhdl"); - RegisterFileIcon("VerilogFileIcon", ".v"); + RegisterFileIcon("VerilogFileIcon", ".v", ".vh"); RegisterFileIcon("QsysFileIcon", ".qsys"); - RegisterFileIcon("SystemVerilogFileIcon", ".sv"); + RegisterFileIcon("SystemVerilogFileIcon", ".sv", ".svh"); RegisterFileIcon("SimpleIcons.Arduino", ".ino"); RegisterFileIcon("Ionicons.LogoJavascript", ".js"); RegisterFileIcon("FontAwesome.PythonBrands", ".py"); diff --git a/src/OneWare.Core/Styles/Icons.axaml b/src/OneWare.Core/Styles/Icons.axaml index 3a588a804..4499ff20d 100644 --- a/src/OneWare.Core/Styles/Icons.axaml +++ b/src/OneWare.Core/Styles/Icons.axaml @@ -617,25 +617,50 @@ - - + + + + + + + + + - - + + + + + + + + + - - + + + + + + + + + + diff --git a/src/OneWare.Core/ViewModels/Windows/MainWindowViewModel.cs b/src/OneWare.Core/ViewModels/Windows/MainWindowViewModel.cs index 772d09eff..ccce6a005 100644 --- a/src/OneWare.Core/ViewModels/Windows/MainWindowViewModel.cs +++ b/src/OneWare.Core/ViewModels/Windows/MainWindowViewModel.cs @@ -88,6 +88,11 @@ public MainWindowViewModel(IPaths paths, IApplicationStateService applicationSta Header = "Find Files", Command = new RelayCommand(() => OpenManager(GetMainView(), "Files")), InputGesture = new KeyGesture(Key.A, PlatformHelper.ControlKey | KeyModifiers.Shift) + }, new MenuItemModel("FindSymbols") + { + Header = "Go to Symbol", + Command = new RelayCommand(() => OpenManager(GetMainView(), "Symbols")), + InputGesture = new KeyGesture(Key.O, PlatformHelper.ControlKey | KeyModifiers.Shift) }); MainMenu.WatchTreeChanges(AddMenuItem, (r, p) => RemoveMenuItem(r)); diff --git a/src/OneWare.Essentials/Commands/OpenSymbolApplicationCommand.cs b/src/OneWare.Essentials/Commands/OpenSymbolApplicationCommand.cs new file mode 100644 index 000000000..92b84c691 --- /dev/null +++ b/src/OneWare.Essentials/Commands/OpenSymbolApplicationCommand.cs @@ -0,0 +1,29 @@ +using Avalonia.LogicalTree; +using OneWare.Essentials.Services; +using OneWare.Essentials.ViewModels; + +namespace OneWare.Essentials.Commands; + +public class OpenSymbolApplicationCommand(string name, string file, int line, int character) + : ApplicationCommandBase(name) +{ + public override bool Execute(ILogical source) + { + _ = OpenAsync(); + return true; + } + + public override bool CanExecute(ILogical source) + { + return File.Exists(file); + } + + private async Task OpenAsync() + { + if (await ContainerLocator.Container.Resolve().OpenFileAsync(file) is not IEditor editor) + return; + + var documentLine = editor.CurrentDocument.GetLineByNumber(Math.Min(line + 1, editor.CurrentDocument.LineCount)); + editor.Select(Math.Min(documentLine.Offset + character, documentLine.EndOffset), 0); + } +} diff --git a/src/OneWare.Essentials/LanguageService/ILanguageService.cs b/src/OneWare.Essentials/LanguageService/ILanguageService.cs index 118e42188..7e84043ee 100644 --- a/src/OneWare.Essentials/LanguageService/ILanguageService.cs +++ b/src/OneWare.Essentials/LanguageService/ILanguageService.cs @@ -1,4 +1,5 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Newtonsoft.Json.Linq; using OneWare.Essentials.EditorExtensions; using OneWare.Essentials.ViewModels; using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; @@ -44,7 +45,7 @@ public interface ILanguageService SignatureHelp? activeSignatureHelp); public Task RequestCodeActionAsync(string fullPath, Range range, - Diagnostic diagnostic); + IEnumerable? diagnostics = null); public Task?> RequestFoldingsAsync(string fullPath); public Task RequestHoverAsync(string fullPath, Position pos); @@ -67,7 +68,7 @@ public interface ILanguageService public Task RequestInlayHintsAsync(string fullPath, Range range); public Task RequestFormattingAsync(string fullPath); public Task RequestRangeFormattingAsync(string fullPath, Range range); - public Task ExecuteCommandAsync(Command cmd); + public Task ExecuteCommandAsync(Command cmd); public Task ApplyWorkspaceEditAsync(ApplyWorkspaceEditParams param); public Task ApplyWorkspaceEditAsync(WorkspaceEdit? param); public void ApplyContainer(string path, IEnumerable con); diff --git a/src/OneWare.Essentials/LanguageService/LanguageServiceBase.cs b/src/OneWare.Essentials/LanguageService/LanguageServiceBase.cs index ec6458eab..e678afbde 100644 --- a/src/OneWare.Essentials/LanguageService/LanguageServiceBase.cs +++ b/src/OneWare.Essentials/LanguageService/LanguageServiceBase.cs @@ -1,5 +1,6 @@ using Avalonia.Threading; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OneWare.Essentials.EditorExtensions; using OneWare.Essentials.Enums; @@ -96,7 +97,7 @@ public virtual void RefreshTextDocument(string fullPath, string newText) } public virtual Task RequestCodeActionAsync(string fullPath, Range range, - Diagnostic diagnostic) + IEnumerable? diagnostics = null) { return Task.FromResult(null); } @@ -176,9 +177,9 @@ public virtual void RefreshTextDocument(string fullPath, string newText) return Task.FromResult(null); } - public virtual Task ExecuteCommandAsync(Command cmd) + public virtual Task ExecuteCommandAsync(Command cmd) { - return Task.CompletedTask; + return Task.FromResult(null); } public virtual async Task ApplyWorkspaceEditAsync(ApplyWorkspaceEditParams param) @@ -321,8 +322,13 @@ protected virtual IEnumerable ConvertErrors(PublishDiagnosticsPar _ => ErrorType.Hint }; + var endLine = p.Range.End.Line; + var endCharacter = p.Range.End.Character; + if (p.Range.Start.Line == endLine && p.Range.Start.Character == endCharacter) + endCharacter++; + yield return new ErrorListItem(p.Message, errorType, fullPath, Name, p.Range.Start.Line + 1, - p.Range.Start.Character + 1, p.Range.End.Line + 1, p.Range.End.Character + 1, + p.Range.Start.Character + 1, endLine + 1, endCharacter + 1, p.Code?.String ?? p.Code?.Long.ToString() ?? "", p, root); } } diff --git a/src/OneWare.Essentials/LanguageService/LanguageServiceLsp.cs b/src/OneWare.Essentials/LanguageService/LanguageServiceLsp.cs index 170cd22c0..d33bd4468 100644 --- a/src/OneWare.Essentials/LanguageService/LanguageServiceLsp.cs +++ b/src/OneWare.Essentials/LanguageService/LanguageServiceLsp.cs @@ -227,6 +227,15 @@ private async Task InitAsync(Stream input, Stream output, Action( + (SymbolKind[])Enum.GetValues(typeof(SymbolKind))) + } + }); options.WithCapability(new SignatureHelpCapability { SignatureInformation = new SignatureInformationCapabilityOptions @@ -440,7 +449,7 @@ public override void DidOpenTextDocument(string fullPath, string text) { Text = text, Uri = fullPath, - LanguageId = Path.GetExtension(fullPath), + LanguageId = GetLanguageId(fullPath), Version = _version } }); @@ -448,6 +457,11 @@ public override void DidOpenTextDocument(string fullPath, string text) RequestPullDiagnostics(fullPath); } + protected virtual string GetLanguageId(string fullPath) + { + return Path.GetExtension(fullPath); + } + public override void DidSaveTextDocument(string fullPath, string text) { Client?.DidSaveTextDocument(new DidSaveTextDocumentParams @@ -646,11 +660,26 @@ private void HandlePullDiagnosticsReport(string fullPath, JObject report) }); } - public override Task ExecuteCommandAsync(Command cmd) + public override async Task ExecuteCommandAsync(Command cmd) { if (Client?.ServerSettings.Capabilities.ExecuteCommandProvider == null) - return Task.CompletedTask; - return Client.ExecuteCommand(cmd); + return null; + + try + { + return await Client + .SendRequest(WorkspaceNames.ExecuteCommand, new ExecuteCommandParams + { + Command = cmd.Name, + Arguments = cmd.Arguments + }) + .Returning(CancellationToken.None); + } + catch (Exception e) + { + ContainerLocator.Container.Resolve()?.Error(e.Message, e); + return null; + } } public override async Task?> RequestSemanticTokensFullAsync(string fullPath) @@ -845,7 +874,7 @@ public override Task ExecuteCommandAsync(Command cmd) } public override async Task RequestCodeActionAsync(string fullPath, Range range, - Diagnostic diagnostic) + IEnumerable? diagnostics = null) { if (Client == null || Client.ServerSettings.Capabilities.CodeActionProvider == null) return null; try @@ -859,7 +888,7 @@ public override Task ExecuteCommandAsync(Command cmd) Range = range, Context = new CodeActionContext { - Diagnostics = new Container(diagnostic) + Diagnostics = new Container(diagnostics ?? []) } }); diff --git a/src/OneWare.Essentials/LanguageService/LspFoldingStrategy.cs b/src/OneWare.Essentials/LanguageService/LspFoldingStrategy.cs index 87487bb9a..1ab63fa39 100644 --- a/src/OneWare.Essentials/LanguageService/LspFoldingStrategy.cs +++ b/src/OneWare.Essentials/LanguageService/LspFoldingStrategy.cs @@ -1,6 +1,8 @@ using AvaloniaEdit.Document; using AvaloniaEdit.Folding; +using Microsoft.Extensions.Logging; using OneWare.Essentials.EditorExtensions; +using OneWare.Essentials.Services; namespace OneWare.Essentials.LanguageService; @@ -36,8 +38,9 @@ public async Task UpdateFoldingsAsync(FoldingManager manager, TextDocument docum //ContainerLocator.Container.Resolve()?.Log("Updated foldings after: " + (DateTime.Now.TimeOfDay - beforeFolding).Milliseconds + "ms", ConsoleColor.DarkGray); } - catch (Exception) + catch (Exception e) { + ContainerLocator.Container.Resolve()?.Error(e.Message, e); } } @@ -49,18 +52,28 @@ public async Task> CreateNewFoldingsAsync(TextDocument d if (f is not null) foreach (var folding in f) { - if (folding.StartLine + 1 >= document.LineCount || - folding.EndLine + 1 >= document.LineCount) continue; + if (folding.StartLine + 1 > document.LineCount || + folding.EndLine + 1 > document.LineCount) continue; var sLine = document.GetLineByNumber(folding.StartLine + 1); var eLine = document.GetLineByNumber(folding.EndLine + 1); - //var sChar = folding.StartCharacter + 1 > sLine.L + var sOff = sLine.Offset + - (folding.StartCharacter.HasValue ? folding.StartCharacter.Value : sLine.Length); + Math.Clamp(folding.StartCharacter ?? sLine.Length, 0, sLine.Length); var eOff = eLine.Offset + - (folding.EndCharacter.HasValue ? folding.EndCharacter.Value : eLine.Length); + Math.Clamp(folding.EndCharacter ?? eLine.Length, 0, eLine.Length); + + if (eOff <= sOff) continue; + l.Add(new NewFolding(sOff, eOff)); } + // AvaloniaEdit requires foldings sorted by start offset, the language server does not guarantee any order + l.Sort((a, b) => + { + var startComparison = a.StartOffset.CompareTo(b.StartOffset); + return startComparison != 0 ? startComparison : b.EndOffset.CompareTo(a.EndOffset); + }); + return l; } } diff --git a/src/OneWare.Essentials/LanguageService/LspFormattingStrategy.cs b/src/OneWare.Essentials/LanguageService/LspFormattingStrategy.cs new file mode 100644 index 000000000..83656ead0 --- /dev/null +++ b/src/OneWare.Essentials/LanguageService/LspFormattingStrategy.cs @@ -0,0 +1,18 @@ +using AvaloniaEdit.Document; +using OneWare.Essentials.EditorExtensions; + +namespace OneWare.Essentials.LanguageService; + +public class LspFormattingStrategy(ILanguageService languageService, string filePath) : IFormattingStrategy +{ + public void Format(TextDocument document) + { + _ = FormatAsync(); + } + + private async Task FormatAsync() + { + var edits = await languageService.RequestFormattingAsync(filePath); + if (edits is not null) languageService.ApplyContainer(filePath, edits); + } +} diff --git a/src/OneWare.Essentials/LanguageService/TypeAssistanceLanguageService.cs b/src/OneWare.Essentials/LanguageService/TypeAssistanceLanguageService.cs index 4feac8ddd..1512af944 100644 --- a/src/OneWare.Essentials/LanguageService/TypeAssistanceLanguageService.cs +++ b/src/OneWare.Essentials/LanguageService/TypeAssistanceLanguageService.cs @@ -118,45 +118,53 @@ protected TypeAssistanceLanguageService(IEditor evm, ILanguageService langServic var location = CodeBox.Document.GetLocation(offset); var pos = CodeBox.Document.GetPositionFromOffset(offset); - //Quick Fixes var error = GetErrorAtLocation(location); - if (error != null && error.Diagnostic != null) + var codeActionRange = error?.Diagnostic?.Range ?? new Range { - var codeactions = await Service.RequestCodeActionAsync(CurrentFilePath, - new Range - { - Start = new Position(Math.Max(error.StartLine - 1, 0), Math.Max(error.StartColumn - 1 ?? 0, 0)), - End = new Position(Math.Max(error.EndLine - 1 ?? 0, 0), Math.Max(error.EndColumn - 1 ?? 0, 0)) - }, error.Diagnostic); - - if (codeactions is not null && IsOpen) - { - var quickfixes = new ObservableCollection(); - foreach (var ca in codeactions) - if (ca.IsCodeAction && ca.CodeAction != null) - { - if (ca.CodeAction.Command != null) - quickfixes.Add(new MenuItemModel(ca.CodeAction.Title) - { - Header = ca.CodeAction.Title, - Command = new RelayCommand(ExecuteCommand), - CommandParameter = ca.CodeAction.Command - }); - else if (ca.CodeAction.Edit != null) - quickfixes.Add(new MenuItemModel(ca.CodeAction.Title) - { - Header = ca.CodeAction.Title, - Command = new AsyncRelayCommand(Service.ApplyWorkspaceEditAsync), - CommandParameter = ca.CodeAction.Edit - }); - } + Start = pos, + End = pos + }; + var diagnostics = error?.Diagnostic is { } diagnostic ? new[] { diagnostic } : []; + var codeactions = await Service.RequestCodeActionAsync(CurrentFilePath, codeActionRange, diagnostics); - if (quickfixes.Count > 0) - menuItems.Add(new MenuItemModel("Quick fix") + if (codeactions is not null && IsOpen) + { + var actions = new ObservableCollection(); + foreach (var ca in codeactions) + if (ca.IsCodeAction && ca.CodeAction != null) + { + if (ca.CodeAction.Command != null) + actions.Add(new MenuItemModel(ca.CodeAction.Title) + { + Header = ca.CodeAction.Title, + Command = new RelayCommand(ExecuteCommand), + CommandParameter = ca.CodeAction.Command + }); + else if (ca.CodeAction.Edit != null) + actions.Add(new MenuItemModel(ca.CodeAction.Title) + { + Header = ca.CodeAction.Title, + Command = new AsyncRelayCommand(Service.ApplyWorkspaceEditAsync), + CommandParameter = ca.CodeAction.Edit + }); + } + else if (ca.IsCommand && ca.Command != null) + { + actions.Add(new MenuItemModel(ca.Command.Title) { - Header = "Quick fix...", - Items = quickfixes + Header = ca.Command.Title, + Command = new RelayCommand(ExecuteCommand), + CommandParameter = ca.Command }); + } + + if (actions.Count > 0) + { + menuItems.Add(new MenuItemModel("Code actions") + { + Header = "Code actions...", + Items = actions + }); } } @@ -198,31 +206,32 @@ protected async Task StartRenameSymbolAsync(RangeOrPlaceholderRange? range) { if (range == null) return; - if (range.IsRange && range.Range != null) + var renameRange = range.IsRange ? range.Range : range.PlaceholderRange?.Range; + if (renameRange != null) { await Task.Delay(10); - var sOff = CodeBox.Document.GetOffsetFromPosition(range.Range.Start) - 1; - var eOff = CodeBox.Document.GetOffsetFromPosition(range.Range.End) - 1; + var sOff = CodeBox.Document.GetOffsetFromPosition(renameRange.Start) - 1; + var eOff = CodeBox.Document.GetOffsetFromPosition(renameRange.End) - 1; if (sOff >= eOff) return; - var initialValue = CodeBox.Text[sOff..eOff]; + var initialValue = range.PlaceholderRange?.Placeholder ?? CodeBox.Text[sOff..eOff]; var textInputWindow = new TextInputWindow(CodeBox.TextArea, - new TextViewPosition(range.Range.Start.Line + 1, range.Range.Start.Character + 1), initialValue) + new TextViewPosition(renameRange.Start.Line + 1, renameRange.Start.Character + 1), initialValue) { - CompleteAction = x => _ = RenameSymbolAsync(range, x ?? "") + CompleteAction = x => _ = RenameSymbolAsync(renameRange, x ?? "") }; textInputWindow.Show(); } else { - ContainerLocator.Container.Resolve()?.Error("Placeholder Range renaming not supported yet!"); + ContainerLocator.Container.Resolve()?.Error("Default rename behavior is not supported."); } } - private async Task RenameSymbolAsync(RangeOrPlaceholderRange range, string newName) + private async Task RenameSymbolAsync(Range range, string newName) { if (Regex.IsMatch(newName, @"\W")) { @@ -231,17 +240,10 @@ private async Task RenameSymbolAsync(RangeOrPlaceholderRange range, string newNa return; } - if (range.IsRange && range.Range != null) - { - var workspaceEdit = await Service.RequestRenameAsync(CurrentFilePath, range.Range.Start, newName); - if (workspaceEdit != null && IsOpen) - await Service.ApplyWorkspaceEditAsync(new ApplyWorkspaceEditParams - { Edit = workspaceEdit, Label = "Rename" }); - } - else - { - ContainerLocator.Container.Resolve()?.Error("Placeholder Range renaming not supported yet!"); - } + var workspaceEdit = await Service.RequestRenameAsync(CurrentFilePath, range.Start, newName); + if (workspaceEdit != null && IsOpen) + await Service.ApplyWorkspaceEditAsync(new ApplyWorkspaceEditParams + { Edit = workspaceEdit, Label = "Rename" }); } public override async Task GetActionOnControlWordAsync(int offset) diff --git a/src/OneWare.Essentials/PackageManager/PackageTarget.cs b/src/OneWare.Essentials/PackageManager/PackageTarget.cs index 2c9cbfe7f..018b7e7af 100644 --- a/src/OneWare.Essentials/PackageManager/PackageTarget.cs +++ b/src/OneWare.Essentials/PackageManager/PackageTarget.cs @@ -6,5 +6,7 @@ public class PackageTarget public string? Url { get; init; } + public bool IsArchive { get; init; } = true; + public PackageAutoSetting[]? AutoSetting { get; init; } } \ No newline at end of file diff --git a/src/OneWare.OssCadSuiteIntegration/OssCadSuiteIntegrationModule.cs b/src/OneWare.OssCadSuiteIntegration/OssCadSuiteIntegrationModule.cs index 3c577b4b7..d9716a2d8 100644 --- a/src/OneWare.OssCadSuiteIntegration/OssCadSuiteIntegrationModule.cs +++ b/src/OneWare.OssCadSuiteIntegration/OssCadSuiteIntegrationModule.cs @@ -70,6 +70,7 @@ public override void Initialize(IServiceProvider serviceProvider) toolService.Register(new ToolContext("gtkwave", "Visualisation", "gtkwave"), new NativeStrategy()); toolService.Register(new ToolContext("iverilog", "Simulation", "iverilog"), new NativeStrategy()); toolService.Register(new ToolContext("vvp", "Simulation", "vvp"), new NativeStrategy()); + toolService.Register(new ToolContext("verilator", "Simulation", "verilator"), new NativeStrategy()); serviceProvider.Resolve().RegisterPackage(OssCadSuiteHelper.OssCadPackage); @@ -198,6 +199,7 @@ await windowService.ShowDialogAsync( serviceProvider.Resolve().RegisterToolchain(); serviceProvider.Resolve().RegisterLoader(); serviceProvider.Resolve().RegisterSimulator(); + serviceProvider.Resolve().RegisterSimulator(); settingsService.RegisterSetting( "Tools", @@ -240,8 +242,9 @@ await windowService.ShowDialogAsync( Path.Combine(x, "share", "openFPGALoader")); environmentService.SetEnvironmentVariable("PYTHON_EXECUTABLE", Path.Combine(x, "lib", $"python3{PlatformHelper.ExecutableExtension}")); - //environmentService.SetEnvironmentVariable("VERILATOR_ROOT", - // Path.Combine(x, "share", $"verilator")); + var verilatorRoot = Path.Combine(x, "share", "verilator"); + if (Directory.Exists(verilatorRoot)) + environmentService.SetEnvironmentVariable("VERILATOR_ROOT", verilatorRoot); // GHDL is not provided in the Windows version if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { diff --git a/src/OneWare.OssCadSuiteIntegration/Simulators/HdlSimulationSourceHelper.cs b/src/OneWare.OssCadSuiteIntegration/Simulators/HdlSimulationSourceHelper.cs new file mode 100644 index 000000000..e6fa4fb6d --- /dev/null +++ b/src/OneWare.OssCadSuiteIntegration/Simulators/HdlSimulationSourceHelper.cs @@ -0,0 +1,31 @@ +using System.Text.RegularExpressions; +using OneWare.Essentials.Extensions; +using OneWare.UniversalFpgaProjectSystem.Models; + +namespace OneWare.OssCadSuiteIntegration.Simulators; + +internal static partial class HdlSimulationSourceHelper +{ + public static List GetOrderedSources(UniversalFpgaProjectRoot root, string activeTestBenchPath) + { + var activeTestBenchRelative = Path.GetRelativePath(root.FullPath, activeTestBenchPath).ToUnixPath(); + + return root.GetFiles() + .Where(x => Path.GetExtension(x) is ".v" or ".sv") + .Where(x => !root.IsCompileExcluded(x)) + .Where(x => !root.IsTestBench(x) || x.EqualPaths(activeTestBenchRelative)) + .OrderBy(x => DeclaresPackage(root, x) ? 0 : 1) + .ThenBy(x => x, StringComparer.OrdinalIgnoreCase) + .Select(x => x.ToUnixPath()) + .ToList(); + } + + private static bool DeclaresPackage(UniversalFpgaProjectRoot root, string relativePath) + { + var source = File.ReadAllText(Path.Combine(root.FullPath, relativePath)); + return PackageDeclarationRegex().IsMatch(source); + } + + [GeneratedRegex(@"\bpackage\s+(?!body\b)[A-Za-z_][A-Za-z0-9_$]*", RegexOptions.Compiled)] + private static partial Regex PackageDeclarationRegex(); +} diff --git a/src/OneWare.OssCadSuiteIntegration/Simulators/IcarusVerilogSimulator.cs b/src/OneWare.OssCadSuiteIntegration/Simulators/IcarusVerilogSimulator.cs index 8268da80a..bab5f8812 100644 --- a/src/OneWare.OssCadSuiteIntegration/Simulators/IcarusVerilogSimulator.cs +++ b/src/OneWare.OssCadSuiteIntegration/Simulators/IcarusVerilogSimulator.cs @@ -56,12 +56,7 @@ public async Task SimulateAsync(string fullPath) var vvpPath = Path.Combine(relativeFolderPath, Path.GetFileNameWithoutExtension(fullPath) + ".vvp").ToUnixPath(); - var activeTestBenchRelative = Path.GetRelativePath(root.FullPath, fullPath).ToUnixPath(); - - var verilogFiles = root.GetFiles("*.v") - .Where(x => !root.IsCompileExcluded(x)) - .Where(x => !root.IsTestBench(x) || x.EqualPaths(activeTestBenchRelative)) - .Select(x => x.ToUnixPath()); + var verilogFiles = HdlSimulationSourceHelper.GetOrderedSources(root, fullPath); _mainDockService.Show(); @@ -73,6 +68,7 @@ public async Task SimulateAsync(string fullPath) .WithStatus("Running IVerilog..", AppState.Loading) .WithTimer(true) .AddPathOption("-o", vvpPath) + .Add("-g2012") .AddRawArguments(settings.GetBenchProperty(nameof(IcarusVerilogSimulatorToolbarViewModel.IcarusVerilogArguments))) .AddPaths(verilogFiles) .Build(); @@ -89,9 +85,10 @@ public async Task SimulateAsync(string fullPath) var vvpCommand = _toolExecutionDispatcherService.CreateToolCommandBuilder("vvp").WithWorkingDirectory(root.FullPath) .WithStatus("Running VPP Simulation", AppState.Loading) .WithTimer(true) - .Add(vvpPath) .AddIf(waveOutput == "LXT2", "-lxt2") - .AddIf(waveOutput == "FST", "-fst").Build(); + .AddIf(waveOutput == "FST", "-fst") + .AddPath(vvpPath) + .Build(); var (resultVvp, outputVvp) = await _toolExecutionDispatcherService.ExecuteAsync(vvpCommand); diff --git a/src/OneWare.OssCadSuiteIntegration/Simulators/VerilatorSimulator.cs b/src/OneWare.OssCadSuiteIntegration/Simulators/VerilatorSimulator.cs new file mode 100644 index 000000000..d88006f73 --- /dev/null +++ b/src/OneWare.OssCadSuiteIntegration/Simulators/VerilatorSimulator.cs @@ -0,0 +1,144 @@ +using Microsoft.Extensions.Logging; +using OneWare.Essentials.Enums; +using OneWare.Essentials.Extensions; +using OneWare.Essentials.Helpers; +using OneWare.Essentials.Models; +using OneWare.Essentials.Services; +using OneWare.OssCadSuiteIntegration.Tools; +using OneWare.OssCadSuiteIntegration.ViewModels; +using OneWare.OssCadSuiteIntegration.Views; +using OneWare.UniversalFpgaProjectSystem.Context; +using OneWare.UniversalFpgaProjectSystem.Models; +using OneWare.UniversalFpgaProjectSystem.Services; + +namespace OneWare.OssCadSuiteIntegration.Simulators; + +public class VerilatorSimulator : IFpgaSimulator +{ + private static readonly HashSet WaveformExtensions = + new(StringComparer.OrdinalIgnoreCase) { ".vcd", ".fst", ".lxt", ".lxt2" }; + + private readonly GtkWaveService _gtkWaveService; + private readonly ILogger _logger; + private readonly IMainDockService _mainDockService; + private readonly IProjectExplorerService _projectExplorerService; + private readonly IToolExecutionDispatcherService _toolExecutionDispatcherService; + + public VerilatorSimulator(ILogger logger, IMainDockService mainDockService, + IProjectExplorerService projectExplorerService, GtkWaveService gtkWaveService, + IToolExecutionDispatcherService toolExecutionDispatcherService) + { + _logger = logger; + _mainDockService = mainDockService; + _projectExplorerService = projectExplorerService; + _gtkWaveService = gtkWaveService; + _toolExecutionDispatcherService = toolExecutionDispatcherService; + + TestBenchToolbarTopUiExtension = new OneWareUiExtension(x => + { + if (x is not TestBenchContext context) return null; + return new VerilatorSimulatorToolbarView + { + DataContext = new VerilatorSimulatorToolbarViewModel(context) + }; + }); + } + + public string Name => "Verilator"; + + public OneWareUiExtension? TestBenchToolbarTopUiExtension { get; } + + public async Task SimulateAsync(string fullPath) + { + if (_projectExplorerService.GetRootFromFile(fullPath) is not UniversalFpgaProjectRoot root) return false; + + var settings = await TestBenchContextManager.LoadContextAsync(fullPath); + var topModule = settings.GetBenchProperty(nameof(VerilatorSimulatorToolbarViewModel.TopModule)); + if (string.IsNullOrWhiteSpace(topModule)) + topModule = Path.GetFileNameWithoutExtension(fullPath); + + var sourceFiles = HdlSimulationSourceHelper.GetOrderedSources(root, fullPath); + + if (sourceFiles.Count == 0) + { + _logger.Warning("Verilator simulation requires at least one Verilog or SystemVerilog source file."); + return false; + } + + var buildDirectory = Path.Combine(root.FullPath, "build", "sim", "verilator", + Path.GetFileNameWithoutExtension(fullPath)); + Directory.CreateDirectory(buildDirectory); + var executableName = $"simulation{PlatformHelper.ExecutableExtension}"; + var executablePath = Path.Combine(buildDirectory, executableName); + + _mainDockService.Show(); + + var compileCommand = _toolExecutionDispatcherService.CreateToolCommandBuilder("verilator") + .WithWorkingDirectory(root.FullPath) + .WithStatus("Compiling with Verilator...", AppState.Loading) + .WithTimer(true) + .Add("--binary", "--timing", "--trace") + .AddRawArguments(settings.GetBenchProperty( + nameof(VerilatorSimulatorToolbarViewModel.VerilatorArguments))) + .AddOption("--top-module", topModule) + .AddPathOption("--Mdir", buildDirectory) + .AddOption("-o", executableName) + .AddPaths(sourceFiles) + .Build(); + + var (compiled, _) = await _toolExecutionDispatcherService.ExecuteAsync(compileCommand); + if (!compiled) + { + _logger.LogWarning("Verilator compilation failed"); + return false; + } + + if (!File.Exists(executablePath)) + { + _logger.Error($"Verilator did not create the expected executable: {executablePath}"); + return false; + } + + var simulationStarted = DateTime.UtcNow; + var runCommand = _toolExecutionDispatcherService.CreateToolCommandBuilder("verilator") + .WithExecutable(executablePath) + .WithWorkingDirectory(root.FullPath) + .WithStatus("Running Verilator simulation...", AppState.Loading) + .WithTimer(true) + .AddRawArguments(settings.GetBenchProperty( + nameof(VerilatorSimulatorToolbarViewModel.VerilatorRuntimeArguments))) + .Build(); + + var (simulated, _) = await _toolExecutionDispatcherService.ExecuteAsync(runCommand); + if (!simulated) + { + _logger.LogWarning("Verilator simulation failed"); + return false; + } + + await OpenLatestWaveformAsync(root.FullPath, simulationStarted); + return true; + } + + private async Task OpenLatestWaveformAsync(string projectPath, DateTime simulationStarted) + { + var options = new EnumerationOptions + { + AttributesToSkip = FileAttributes.Hidden | FileAttributes.System, + IgnoreInaccessible = true, + RecurseSubdirectories = true + }; + var waveform = Directory.EnumerateFiles(projectPath, "*", options) + .Where(x => WaveformExtensions.Contains(Path.GetExtension(x))) + .Where(x => File.GetLastWriteTimeUtc(x) >= simulationStarted.AddSeconds(-1)) + .OrderByDescending(File.GetLastWriteTimeUtc) + .FirstOrDefault(); + + if (waveform == null) return; + + if (Path.GetExtension(waveform).Equals(".vcd", StringComparison.OrdinalIgnoreCase)) + _ = await _mainDockService.OpenFileAsync(waveform); + else + _ = _gtkWaveService.OpenInGtkWaveAsync(waveform); + } +} diff --git a/src/OneWare.OssCadSuiteIntegration/ViewModels/VerilatorSimulatorToolbarViewModel.cs b/src/OneWare.OssCadSuiteIntegration/ViewModels/VerilatorSimulatorToolbarViewModel.cs new file mode 100644 index 000000000..24c35c446 --- /dev/null +++ b/src/OneWare.OssCadSuiteIntegration/ViewModels/VerilatorSimulatorToolbarViewModel.cs @@ -0,0 +1,37 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using OneWare.UniversalFpgaProjectSystem.Context; + +namespace OneWare.OssCadSuiteIntegration.ViewModels; + +public class VerilatorSimulatorToolbarViewModel(TestBenchContext context) : ObservableObject +{ + public string TopModule + { + get => context.GetBenchProperty(nameof(TopModule)) ?? Path.GetFileNameWithoutExtension(context.FilePath); + set + { + context.SetBenchProperty(nameof(TopModule), value); + OnPropertyChanged(); + } + } + + public string VerilatorArguments + { + get => context.GetBenchProperty(nameof(VerilatorArguments)) ?? ""; + set + { + context.SetBenchProperty(nameof(VerilatorArguments), value); + OnPropertyChanged(); + } + } + + public string VerilatorRuntimeArguments + { + get => context.GetBenchProperty(nameof(VerilatorRuntimeArguments)) ?? ""; + set + { + context.SetBenchProperty(nameof(VerilatorRuntimeArguments), value); + OnPropertyChanged(); + } + } +} diff --git a/src/OneWare.OssCadSuiteIntegration/Views/VerilatorSimulatorToolbarView.axaml b/src/OneWare.OssCadSuiteIntegration/Views/VerilatorSimulatorToolbarView.axaml new file mode 100644 index 000000000..14238d5d2 --- /dev/null +++ b/src/OneWare.OssCadSuiteIntegration/Views/VerilatorSimulatorToolbarView.axaml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/src/OneWare.OssCadSuiteIntegration/Views/VerilatorSimulatorToolbarView.axaml.cs b/src/OneWare.OssCadSuiteIntegration/Views/VerilatorSimulatorToolbarView.axaml.cs new file mode 100644 index 000000000..49d20f200 --- /dev/null +++ b/src/OneWare.OssCadSuiteIntegration/Views/VerilatorSimulatorToolbarView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace OneWare.OssCadSuiteIntegration.Views; + +public partial class VerilatorSimulatorToolbarView : UserControl +{ + public VerilatorSimulatorToolbarView() + { + InitializeComponent(); + } +} diff --git a/src/OneWare.PackageManager/Services/IPackageDownloader.cs b/src/OneWare.PackageManager/Services/IPackageDownloader.cs index 9038f8807..c4b44b2cc 100644 --- a/src/OneWare.PackageManager/Services/IPackageDownloader.cs +++ b/src/OneWare.PackageManager/Services/IPackageDownloader.cs @@ -2,6 +2,6 @@ namespace OneWare.PackageManager.Services; public interface IPackageDownloader { - Task DownloadAndExtractAsync(string url, string extractionPath, IProgress progress, + Task DownloadAndExtractAsync(string url, string extractionPath, bool isArchive, IProgress progress, CancellationToken cancellationToken = default); } diff --git a/src/OneWare.PackageManager/Services/PackageDownloader.cs b/src/OneWare.PackageManager/Services/PackageDownloader.cs index a14a30665..ad5ad35f6 100644 --- a/src/OneWare.PackageManager/Services/PackageDownloader.cs +++ b/src/OneWare.PackageManager/Services/PackageDownloader.cs @@ -11,10 +11,17 @@ public PackageDownloader(IHttpService httpService) _httpService = httpService; } - public Task DownloadAndExtractAsync(string url, string extractionPath, IProgress progress, + public Task DownloadAndExtractAsync(string url, string extractionPath, bool isArchive, + IProgress progress, CancellationToken cancellationToken = default) { - return _httpService.DownloadAndExtractArchiveAsync(url, extractionPath, progress, + if (isArchive) + return _httpService.DownloadAndExtractArchiveAsync(url, extractionPath, progress, + cancellationToken: cancellationToken); + + Directory.CreateDirectory(extractionPath); + var fileName = Path.GetFileName(new Uri(url).LocalPath); + return _httpService.DownloadFileAsync(url, Path.Combine(extractionPath, fileName), progress, cancellationToken: cancellationToken); } } diff --git a/src/OneWare.PackageManager/Services/PackageService.cs b/src/OneWare.PackageManager/Services/PackageService.cs index c91d99a57..241db376f 100644 --- a/src/OneWare.PackageManager/Services/PackageService.cs +++ b/src/OneWare.PackageManager/Services/PackageService.cs @@ -595,7 +595,8 @@ private async Task DownloadAndInstallAsync(PackageState st var url = target.Url ?? $"{state.Package.SourceUrl}/{version.Version}/{state.Package.Id}_{version.Version}_{target.Target}.zip"; - var success = await _downloader.DownloadAndExtractAsync(url, extractionPath, progress, cancellationToken); + var success = await _downloader.DownloadAndExtractAsync(url, extractionPath, target.IsArchive, progress, + cancellationToken); if (!success) { diff --git a/src/OneWare.Verilog/Assets/systemverilog.json b/src/OneWare.Verilog/Assets/systemverilog.json new file mode 100644 index 000000000..0c8bc9ea1 --- /dev/null +++ b/src/OneWare.Verilog/Assets/systemverilog.json @@ -0,0 +1,168 @@ +{ + "logic": { + "prefix": "logic", + "body": ["logic ${1:signal};"], + "description": "Declare a four-state variable" + }, + "logic vector": { + "prefix": "logicv", + "body": ["logic [${1:WIDTH}-1:0] ${2:signal};"], + "description": "Declare a packed logic vector" + }, + "always combinational": { + "prefix": "always_comb", + "body": [ + "always_comb begin", + "\t${1}", + "end" + ], + "description": "SystemVerilog combinational process" + }, + "always flip-flop": { + "prefix": "always_ff", + "body": [ + "always_ff @(posedge ${1:clk}) begin", + "\t${2:q} <= ${3:d};", + "end" + ], + "description": "SystemVerilog sequential process" + }, + "always flip-flop with reset": { + "prefix": "always_ff_reset", + "body": [ + "always_ff @(posedge ${1:clk} or negedge ${2:rst_n}) begin", + "\tif (!${2:rst_n}) begin", + "\t\t${3:q} <= '0;", + "\tend else begin", + "\t\t${3:q} <= ${4:d};", + "\tend", + "end" + ], + "description": "Sequential process with asynchronous active-low reset" + }, + "always latch": { + "prefix": "always_latch", + "body": [ + "always_latch begin", + "\tif (${1:enable}) begin", + "\t\t${2:q} <= ${3:d};", + "\tend", + "end" + ], + "description": "SystemVerilog latch process" + }, + "package": { + "prefix": "package", + "body": [ + "package ${1:package_name};", + "\t${2}", + "endpackage : ${1:package_name}" + ], + "description": "SystemVerilog package" + }, + "interface": { + "prefix": "interface", + "body": [ + "interface ${1:interface_name} (input logic ${2:clk});", + "\t${3}", + "endinterface : ${1:interface_name}" + ], + "description": "SystemVerilog interface" + }, + "modport": { + "prefix": "modport", + "body": [ + "modport ${1:name} (", + "\tinput ${2:input_signal},", + "\toutput ${3:output_signal}", + ");" + ], + "description": "SystemVerilog interface modport" + }, + "typedef enum": { + "prefix": "typedef_enum", + "body": [ + "typedef enum logic [${1:WIDTH}-1:0] {", + "\t${2:IDLE},", + "\t${3:ACTIVE}", + "} ${4:state_t};" + ], + "description": "Strongly typed packed enumeration" + }, + "typedef struct": { + "prefix": "typedef_struct", + "body": [ + "typedef struct packed {", + "\t${1}", + "} ${2:data_t};" + ], + "description": "Packed structure type" + }, + "automatic function": { + "prefix": "function_automatic", + "body": [ + "function automatic ${1:logic} ${2:name} (${3});", + "\t${4}", + "endfunction : ${2:name}" + ], + "description": "Automatic SystemVerilog function" + }, + "automatic task": { + "prefix": "task_automatic", + "body": [ + "task automatic ${1:name} (${2});", + "\t${3}", + "endtask : ${1:name}" + ], + "description": "Automatic SystemVerilog task" + }, + "property and assertion": { + "prefix": "assert_property", + "body": [ + "property ${1:name};", + "\t@(posedge ${2:clk}) disable iff (!${3:rst_n})", + "\t${4:antecedent} |-> ${5:consequent};", + "endproperty : ${1:name}", + "", + "${1:name}_a: assert property (${1:name});" + ], + "description": "Concurrent assertion with a named property" + }, + "generate loop": { + "prefix": "generate_for", + "body": [ + "for (genvar ${1:i} = 0; ${1:i} < ${2:COUNT}; ${1:i}++) begin : ${3:gen_block}", + "\t${4}", + "end" + ], + "description": "SystemVerilog generate loop" + }, + "class": { + "prefix": "class", + "body": [ + "class ${1:class_name};", + "\t${2}", + "endclass : ${1:class_name}" + ], + "description": "SystemVerilog class" + }, + "clocking block": { + "prefix": "clocking", + "body": [ + "clocking ${1:cb} @(posedge ${2:clk});", + "\tdefault input #1step output #0;", + "\t${3}", + "endclocking : ${1:cb}" + ], + "description": "SystemVerilog clocking block" + }, + "covergroup": { + "prefix": "covergroup", + "body": [ + "covergroup ${1:name} @(posedge ${2:clk});", + "\t${3:cp}: coverpoint ${4:signal};", + "endgroup : ${1:name}" + ], + "description": "SystemVerilog functional coverage group" + } +} diff --git a/src/OneWare.Verilog/Assets/systemverilog.tmLanguage.json b/src/OneWare.Verilog/Assets/systemverilog.tmLanguage.json new file mode 100644 index 000000000..5026152b3 --- /dev/null +++ b/src/OneWare.Verilog/Assets/systemverilog.tmLanguage.json @@ -0,0 +1,1418 @@ +{ + "name": "SystemVerilog", + "scopeName": "source.systemverilog", + "fileTypes": [ + "v", + "vh", + "sv", + "svh" + ], + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#strings" + }, + { + "include": "#typedef-enum-struct-union" + }, + { + "include": "#typedef" + }, + { + "include": "#functions" + }, + { + "include": "#keywords" + }, + { + "include": "#tables" + }, + { + "include": "#function-task" + }, + { + "include": "#module-declaration" + }, + { + "include": "#class-declaration" + }, + { + "include": "#enum-struct-union" + }, + { + "include": "#sequence" + }, + { + "include": "#all-types" + }, + { + "include": "#module-parameters" + }, + { + "include": "#module-no-parameters" + }, + { + "include": "#port-net-parameter" + }, + { + "include": "#system-tf" + }, + { + "include": "#assertion" + }, + { + "include": "#bind-directive" + }, + { + "include": "#cast-operator" + }, + { + "include": "#storage-scope" + }, + { + "include": "#attributes" + }, + { + "include": "#imports" + }, + { + "include": "#operators" + }, + { + "include": "#constants" + }, + { + "include": "#identifiers" + }, + { + "include": "#selects" + } + ], + "repository": { + "function-task": { + "begin": "[ \\t\\r\\n]*(?:\\b(virtual)[ \\t\\r\\n]+)?(?:\\b(function|task)\\b)(?:[ \\t\\r\\n]+\\b(static|automatic)\\b)?", + "beginCaptures": { + "1": { + "name": "storage.modifier.systemverilog" + }, + "2": { + "name": "storage.type.function.systemverilog" + }, + "3": { + "name": "storage.modifier.systemverilog" + } + }, + "end": ";", + "endCaptures": { + "0": { + "name": "punctuation.definition.function.end.systemverilog" + } + }, + "patterns": [ + { + "match": "[ \\t\\r\\n]*(?:(?|\\|=>|->>|\\*>|#-#|#=#|&&&", + "name": "keyword.operator.logical.systemverilog" + }, + { + "match": "@|##|#|->|<->", + "name": "keyword.operator.channel.systemverilog" + }, + { + "match": "\\+=|-=|/=|\\*=|%=|&=|\\|=|\\^=|>>>=|>>=|<<<=|<<=|<=|=", + "name": "keyword.operator.assignment.systemverilog" + }, + { + "match": "\\+\\+", + "name": "keyword.operator.increment.systemverilog" + }, + { + "match": "--", + "name": "keyword.operator.decrement.systemverilog" + }, + { + "match": "\\+|-|\\*\\*|\\*|/|%", + "name": "keyword.operator.arithmetic.systemverilog" + }, + { + "match": "!|&&|\\|\\|", + "name": "keyword.operator.logical.systemverilog" + }, + { + "match": "<<<|<<|>>>|>>", + "name": "keyword.operator.bitwise.shift.systemverilog" + }, + { + "match": "~&|~\\||~|\\^~|~\\^|&|\\||\\^|{|'{|}|:|\\?", + "name": "keyword.operator.bitwise.systemverilog" + }, + { + "match": "<=|<|>=|>|==\\?|!=\\?|===|!==|==|!=", + "name": "keyword.operator.comparison.systemverilog" + } + ] + }, + "comments": { + "patterns": [ + { + "begin": "/\\*", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.systemverilog" + } + }, + "end": "\\*/", + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.systemverilog" + } + }, + "patterns": [ + { + "include": "#fixme-todo" + } + ], + "name": "comment.block.systemverilog" + }, + { + "begin": "//", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.systemverilog" + } + }, + "end": "$\\n?", + "patterns": [ + { + "include": "#fixme-todo" + } + ], + "name": "comment.line.double-slash.systemverilog" + } + ] + }, + "fixme-todo": { + "patterns": [ + { + "match": "(?i:fixme)", + "name": "invalid.broken.fixme.systemverilog" + }, + { + "match": "(?i:todo)", + "name": "invalid.unimplemented.todo.systemverilog" + } + ] + }, + "port-net-parameter": { + "patterns": [ + { + "match": ",?[ \\t\\r\\n]*(?:\\b(output|input|inout|ref)\\b[ \\t\\r\\n]*)?(?:\\b(localparam|parameter|var|supply[01]|tri|triand|trior|trireg|tri[01]|uwire|wire|wand|wor)\\b[ \\t\\r\\n]*)?(?:(?<^!~\\(][ \\t\\r\\n]*)(?", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.systemverilog" + } + }, + "name": "string.quoted.other.lt-gt.include.systemverilog" + } + ] + }, + "module-binding": { + "begin": "\\.([a-zA-Z_][a-zA-Z0-9_$]*)[ \\t\\r\\n]*\\(", + "beginCaptures": { + "1": { + "name": "support.function.port.systemverilog" + } + }, + "end": "\\),?", + "patterns": [ + { + "include": "#constants" + }, + { + "include": "#comments" + }, + { + "include": "#operators" + }, + { + "include": "#strings" + }, + { + "include": "#constants" + }, + { + "include": "#storage-scope" + }, + { + "include": "#cast-operator" + }, + { + "include": "#system-tf" + }, + { + "match": "\\bvirtual\\b", + "name": "storage.modifier.systemverilog" + }, + { + "include": "#identifiers" + } + ], + "name": "meta.port.binding.systemverilog" + }, + "parameters": { + "begin": "[ \\t\\r\\n]*(#)[ \\t\\r\\n]*(\\()", + "beginCaptures": { + "1": { + "name": "keyword.operator.channel.systemverilog" + }, + "2": { + "name": "punctuation.section.parameters.begin" + } + }, + "end": "(\\))[ \\t\\r\\n]*(?=;|\\(|[a-zA-Z_]|\\\\|$)", + "endCaptures": { + "1": { + "name": "punctuation.section.parameters.end" + } + }, + "patterns": [ + { + "include": "#port-net-parameter" + }, + { + "include": "#comments" + }, + { + "include": "#constants" + }, + { + "include": "#operators" + }, + { + "include": "#strings" + }, + { + "include": "#system-tf" + }, + { + "include": "#functions" + }, + { + "match": "\\bvirtual\\b", + "name": "storage.modifier.systemverilog" + }, + { + "include": "#module-binding" + } + ], + "name": "meta.parameters.systemverilog" + }, + "identifiers": { + "patterns": [ + { + "match": "(? 0) - { - _block = _blocks.Pop(); - _block.Continuation = false; - _block.ResetOneLineBlock(); - _block.OneLineBlock = 0; - } + CloseBlock(); } else if (OneLineKeywords.Contains(word)) { @@ -294,6 +286,28 @@ private void ProcessWord(string word, IDocumentAccessor doc, IndentationSettings } } + private void OpenBlock(string bracket, IDocumentAccessor doc, IndentationSettings set) + { + _pendingOneLineKeyword = null; + _blockOpenedThisLine = true; + _block.ResetOneLineBlock(); + _block.OneLineBlock = 0; + _blocks.Push(_block); + _block.StartLine = doc.LineNumber; + _block.Indent(set); + _block.Bracket = bracket; + } + + private void CloseBlock() + { + if (_blocks.Count == 0) return; + + _block = _blocks.Pop(); + _block.Continuation = false; + _block.ResetOneLineBlock(); + _block.OneLineBlock = 0; + } + private static string BuildIndent(Block block, IndentationSettings set) { if (block.OneLineBlock <= 0) return block.InnerIndent; @@ -375,4 +389,3 @@ public override string ToString() } } } - diff --git a/src/OneWare.Verilog/LanguageServiceVerilog.cs b/src/OneWare.Verilog/LanguageServiceVerilog.cs index d5632ee76..eb6754f47 100644 --- a/src/OneWare.Verilog/LanguageServiceVerilog.cs +++ b/src/OneWare.Verilog/LanguageServiceVerilog.cs @@ -1,4 +1,8 @@ -using OneWare.Essentials.LanguageService; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; +using OneWare.Essentials.LanguageService; using OneWare.Essentials.Services; using OneWare.Essentials.ViewModels; @@ -10,10 +14,9 @@ public class LanguageServiceVerilog : LanguageServiceLspAutoDownload public LanguageServiceVerilog(string workspace, ISettingsService settingsService, IPackageService packageService) : base(settingsService.GetSettingObservable(VerilogModule.LspPathSetting), - VerilogModule.VeriblePackage, + VerilogModule.LazyVerilogPackage, VerilogModule.LspName, workspace, packageService, - settingsService.GetSettingObservable("Experimental_AutoDownloadBinaries"), - arguments: "--rules=-no-trailing-spaces") + settingsService.GetSettingObservable("Experimental_AutoDownloadBinaries")) { _settingsService = settingsService; } @@ -22,4 +25,44 @@ public override ITypeAssistance GetTypeAssistance(IEditor editor) { return new TypeAssistanceVerilog(editor, this, _settingsService); } + + protected override string GetLanguageId(string fullPath) + { + return VerilogModule.SystemVerilogExtensions.Contains(Path.GetExtension(fullPath), + StringComparer.OrdinalIgnoreCase) + ? "systemverilog" + : "verilog"; + } + + public override async Task ExecuteCommandAsync(Command cmd) + { + var commandName = cmd.Name switch + { + "lazyverilog.autoffPreview" => "lazyverilog.autoffApply", + "lazyverilog.autoffAllPreview" => "lazyverilog.autoffAllApply", + _ => cmd.Name + }; + + var result = await base.ExecuteCommandAsync(new Command + { + Title = cmd.Title, + Name = commandName, + Arguments = cmd.Arguments + }); + + if (result is JObject resultObject && resultObject["changes"] != null) + { + try + { + var edit = result.ToObject(LspSerializer.Instance.JsonSerializer); + await ApplyWorkspaceEditAsync(edit); + } + catch (Exception e) + { + ContainerLocator.Container.Resolve()?.Error(e.Message, e); + } + } + + return result; + } } diff --git a/src/OneWare.Verilog/Models/VerilogRtlTreeNode.cs b/src/OneWare.Verilog/Models/VerilogRtlTreeNode.cs new file mode 100644 index 000000000..dbd34c80c --- /dev/null +++ b/src/OneWare.Verilog/Models/VerilogRtlTreeNode.cs @@ -0,0 +1,34 @@ +using System.Collections.ObjectModel; +using Newtonsoft.Json; + +namespace OneWare.Verilog.Models; + +public class VerilogRtlTreeNode +{ + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; + + [JsonProperty("inst")] + public string Instance { get; set; } = string.Empty; + + [JsonProperty("file")] + public string File { get; set; } = string.Empty; + + [JsonProperty("line")] + public int Line { get; set; } + + [JsonProperty("col")] + public int Column { get; set; } + + [JsonProperty("recursive")] + public bool IsRecursive { get; set; } + + [JsonProperty("truncated")] + public bool IsTruncated { get; set; } + + [JsonProperty("children")] + public ObservableCollection Children { get; set; } = new(); + + [JsonIgnore] + public string DisplayName => string.IsNullOrWhiteSpace(Instance) ? Name : $"{Instance} : {Name}"; +} diff --git a/src/OneWare.Verilog/Models/VerilogSymbolNode.cs b/src/OneWare.Verilog/Models/VerilogSymbolNode.cs new file mode 100644 index 000000000..f284e12b6 --- /dev/null +++ b/src/OneWare.Verilog/Models/VerilogSymbolNode.cs @@ -0,0 +1,14 @@ +using System.Collections.ObjectModel; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + +namespace OneWare.Verilog.Models; + +public class VerilogSymbolNode(string name, SymbolKind kind, string filePath, Range range) +{ + public string Name { get; } = name; + public SymbolKind Kind { get; } = kind; + public string FilePath { get; } = filePath; + public Range Range { get; } = range; + public ObservableCollection Children { get; } = new(); +} diff --git a/src/OneWare.Verilog/OneWare.Verilog.csproj b/src/OneWare.Verilog/OneWare.Verilog.csproj index dc02a0d40..3d656fa4f 100644 --- a/src/OneWare.Verilog/OneWare.Verilog.csproj +++ b/src/OneWare.Verilog/OneWare.Verilog.csproj @@ -15,7 +15,10 @@ + + + diff --git a/src/OneWare.Verilog/Parsing/VerilogNodeProvider.cs b/src/OneWare.Verilog/Parsing/VerilogNodeProvider.cs index 87d405fdb..ea87e479c 100644 --- a/src/OneWare.Verilog/Parsing/VerilogNodeProvider.cs +++ b/src/OneWare.Verilog/Parsing/VerilogNodeProvider.cs @@ -30,7 +30,7 @@ public class VerilogNodeProvider : INodeProvider public string Name => "Verilog_Basic"; - public string[] SupportedLanguages => ["Verilog"]; + public string[] SupportedLanguages => ["Verilog", "SystemVerilog"]; public Task> ExtractNodesAsync(IProjectFile file) { diff --git a/src/OneWare.Verilog/Services/LazyVerilogCommandService.cs b/src/OneWare.Verilog/Services/LazyVerilogCommandService.cs new file mode 100644 index 000000000..0b4c56a9b --- /dev/null +++ b/src/OneWare.Verilog/Services/LazyVerilogCommandService.cs @@ -0,0 +1,221 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OneWare.Essentials.Enums; +using OneWare.Essentials.Models; +using OneWare.Essentials.Services; +using OneWare.Essentials.ViewModels; + +namespace OneWare.Verilog.Services; + +public class LazyVerilogCommandService( + IMainDockService mainDockService, + ILanguageManager languageManager, + IWindowService windowService, + IErrorService errorService, + IProjectExplorerService projectExplorerService) +{ + public bool CanExecute => + mainDockService.CurrentDocument is IEditor editor && + languageManager.GetLanguageService(editor.FullPath) is LanguageServiceVerilog { IsLanguageServiceReady: true }; + + public Task FormatAsync() + { + return ExecuteAsync("lazyverilog.format", "full"); + } + + public Task AutoWireAsync() + { + return ExecuteAtCaretAsync("lazyverilog.autowire"); + } + + public Task AutoFfAsync() + { + return ExecuteAtCaretAsync("lazyverilog.autoffApply"); + } + + public Task AutoFfAllAsync() + { + return ExecuteAsync("lazyverilog.autoffAllApply"); + } + + public Task LintAsync() + { + return RunLintAsync(false); + } + + public Task LintAllAsync() + { + return RunLintAsync(true); + } + + public async Task ShowInterfaceAsync() + { + var first = await PromptAsync("Inspect Interface", "First instance name"); + if (first == null) return; + var second = await PromptAsync("Inspect Interface", "Second instance name (leave empty for a single instance)"); + if (second == null) return; + + var result = string.IsNullOrWhiteSpace(second) + ? await ExecuteAsync("lazyverilog.singleInterface", first) + : await ExecuteAsync("lazyverilog.interface", first, second); + await ShowResultAsync("LazyVerilog Interface", result); + } + + public async Task ConnectAsync() + { + var sourcePath = await PromptAsync("Connect", "Source hierarchical instance path"); + if (sourcePath == null) return; + var sourcePort = await PromptAsync("Connect", "Source port"); + if (sourcePort == null) return; + var destinationPath = await PromptAsync("Connect", "Destination hierarchical instance path"); + if (destinationPath == null) return; + var destinationPort = await PromptAsync("Connect", "Destination port"); + if (destinationPort == null) return; + var wireName = await PromptAsync("Connect", "Wire name"); + if (wireName == null) return; + + await ExecuteAsync("lazyverilog.connectApply", sourcePath, sourcePort, destinationPath, destinationPort, + wireName, string.Empty, string.Empty); + } + + public async Task ConnectInterfacePortsAsync() + { + var values = await PromptManyAsync("Connect Interface Ports", + "First instance", "Second instance", "First port", "Second port", "Wire name", "Wire type"); + if (values == null) return; + await ExecuteAsync("lazyverilog.interfaceConnect", values); + } + + public async Task DisconnectInterfacePortsAsync() + { + var values = await PromptManyAsync("Disconnect Interface Ports", + "First instance", "Second instance", "First port", "Second port", "Signal name"); + if (values == null) return; + await ExecuteAsync("lazyverilog.interfaceDisconnect", values); + } + + private Task ExecuteAtCaretAsync(string command) + { + if (!TryGetContext(out var editor, out _)) return Task.FromResult(null); + var line = editor.CurrentDocument.GetLocation(editor.Editor.CaretOffset).Line - 1; + return ExecuteAsync(command, line); + } + + private async Task ExecuteAsync(string command, params object[] arguments) + { + if (!TryGetContext(out var editor, out var service)) return null; + + var commandArguments = new JArray(DocumentUri.FromFileSystemPath(editor.FullPath).ToString()); + foreach (var argument in arguments) commandArguments.Add(JToken.FromObject(argument)); + + var result = await service.ExecuteCommandAsync(new Command + { + Title = command, + Name = command, + Arguments = commandArguments + }); + + if (result is JObject resultObject && resultObject["error"] is JToken error) + await windowService.ShowMessageAsync("LazyVerilog", error.Value() ?? "Command failed.", + MessageBoxIcon.Error, mainDockService.GetWindowOwner(editor)); + + return result; + } + + private async Task RunLintAsync(bool all) + { + if (!TryGetContext(out var editor, out var service)) return; + var command = all ? "lazyverilog.lintAll" : "lazyverilog.lint"; + var arguments = all + ? new JArray() + : new JArray(DocumentUri.FromFileSystemPath(editor.FullPath).ToString()); + var result = await service.ExecuteCommandAsync(new Command + { + Title = command, + Name = command, + Arguments = arguments + }); + if (result is not JArray violations) return; + + if (all) errorService.Clear(VerilogModule.LspName); + + var errors = violations.OfType() + .Select(ToError) + .Where(error => error != null) + .Cast() + .GroupBy(error => error.FilePath); + + foreach (var fileErrors in errors) + errorService.RefreshErrors(fileErrors.ToList(), VerilogModule.LspName, fileErrors.Key); + + if (!all && violations.Count == 0) + errorService.RefreshErrors([], VerilogModule.LspName, editor.FullPath); + } + + private ErrorListItem? ToError(JObject violation) + { + var uri = violation.Value("uri"); + var path = !string.IsNullOrWhiteSpace(uri) + ? DocumentUri.From(uri).GetFileSystemPath() + : violation.Value("file"); + if (string.IsNullOrWhiteSpace(path)) return null; + + var severity = violation.Value("severity") switch + { + "Error" => ErrorType.Error, + "Warning" => ErrorType.Warning, + _ => ErrorType.Hint + }; + var line = Math.Max(violation.Value("line") ?? 1, 1); + var column = Math.Max(violation.Value("col") ?? 1, 1); + return new ErrorListItem(violation.Value("message") ?? "LazyVerilog diagnostic", severity, + path, VerilogModule.LspName, line, column, line, column + 1, string.Empty, null, + projectExplorerService.GetRootFromFile(path)); + } + + private bool TryGetContext(out IEditor editor, out LanguageServiceVerilog service) + { + editor = null!; + service = null!; + if (mainDockService.CurrentDocument is not IEditor currentEditor || + languageManager.GetLanguageService(currentEditor.FullPath) is not LanguageServiceVerilog currentService || + !currentService.IsLanguageServiceReady) + return false; + + editor = currentEditor; + service = currentService; + return true; + } + + private Task PromptAsync(string title, string message) + { + return windowService.ShowInputAsync(title, message, MessageBoxIcon.Info, null, + mainDockService.CurrentDocument == null + ? null + : mainDockService.GetWindowOwner(mainDockService.CurrentDocument)); + } + + private async Task PromptManyAsync(string title, params string[] prompts) + { + var values = new object[prompts.Length]; + for (var i = 0; i < prompts.Length; i++) + { + var value = await PromptAsync(title, prompts[i]); + if (value == null) return null; + values[i] = value; + } + + return values; + } + + private Task ShowResultAsync(string title, JToken? result) + { + var message = result?.ToString(Formatting.Indented) ?? "No result."; + return windowService.ShowMessageAsync(title, message, MessageBoxIcon.Info, + mainDockService.CurrentDocument == null + ? null + : mainDockService.GetWindowOwner(mainDockService.CurrentDocument)); + } +} diff --git a/src/OneWare.Verilog/TypeAssistanceVerilog.cs b/src/OneWare.Verilog/TypeAssistanceVerilog.cs index e966c2628..c3ff2e72b 100644 --- a/src/OneWare.Verilog/TypeAssistanceVerilog.cs +++ b/src/OneWare.Verilog/TypeAssistanceVerilog.cs @@ -5,14 +5,15 @@ using OneWare.Essentials.LanguageService; using OneWare.Essentials.Services; using OneWare.Essentials.ViewModels; -using OneWare.Verilog.Folding; using OneWare.Verilog.Indentation; namespace OneWare.Verilog; internal class TypeAssistanceVerilog : TypeAssistanceLanguageService { - private static List? _snippets; + private static List? _verilogSnippets; + private static List? _systemVerilogSnippets; + private readonly bool _isSystemVerilog; private readonly ISettingsService _settingsService; public TypeAssistanceVerilog(IEditor editor, LanguageServiceVerilog ls, ISettingsService settingsService) : @@ -22,11 +23,18 @@ public TypeAssistanceVerilog(IEditor editor, LanguageServiceVerilog ls, ISetting CodeBox.TextArea.IndentationStrategy = IndentationStrategy = new VerilogIndentationStrategy(CodeBox.Options); - FoldingStrategy = new RegexFoldingStrategy(FoldingRegexVerilog.FoldingStart, FoldingRegexVerilog.FoldingEnd); + FormattingStrategy = new LspFormattingStrategy(ls, editor.FullPath); + FoldingStrategy = new LspFoldingStrategy(ls, editor.FullPath); LineCommentSequence = "//"; - - _snippets ??= TextMateSnippetHelper.ParseVsCodeSnippets("avares://OneWare.Verilog/Assets/verilog.json"); + _isSystemVerilog = VerilogModule.SystemVerilogExtensions.Contains(Path.GetExtension(editor.FullPath), + StringComparer.OrdinalIgnoreCase); + + // We dont use snippets since switching to lazyverilog, since it provides them + // _verilogSnippets ??= TextMateSnippetHelper.ParseVsCodeSnippets("avares://OneWare.Verilog/Assets/verilog.json"); + // if (_isSystemVerilog) + // _systemVerilogSnippets ??= + // TextMateSnippetHelper.ParseVsCodeSnippets("avares://OneWare.Verilog/Assets/systemverilog.json"); } protected override Task> GetCustomCompletionItemsAsync() @@ -35,11 +43,17 @@ protected override Task> GetCustomCompletionItemsAsync() if (IsInComment(CodeBox.CaretOffset)) return Task.FromResult(items); - if (_settingsService.GetSettingValue(VerilogModule.EnableSnippetsSetting) && _snippets != null) - items.AddRange(_snippets.Select(snippet => new CompletionData(snippet.Content, snippet.Label, null, + if (_settingsService.GetSettingValue(VerilogModule.EnableSnippetsSetting) && _verilogSnippets != null) + items.AddRange(_verilogSnippets.Select(snippet => new CompletionData(snippet.Content, snippet.Label, null, snippet.Description, TypeAssistanceIconStore.Instance.Icons[CompletionItemKind.Snippet], 0, CodeBox.CaretOffset, CurrentFilePath))); + if (_settingsService.GetSettingValue(VerilogModule.EnableSnippetsSetting) && _isSystemVerilog && + _systemVerilogSnippets != null) + items.AddRange(_systemVerilogSnippets.Select(snippet => new CompletionData(snippet.Content, snippet.Label, + null, snippet.Description, TypeAssistanceIconStore.Instance.Icons[CompletionItemKind.Snippet], 0, + CodeBox.CaretOffset, CurrentFilePath))); + return Task.FromResult(items); } diff --git a/src/OneWare.Verilog/VerilogModule.cs b/src/OneWare.Verilog/VerilogModule.cs index dee1be451..e6386d8f2 100644 --- a/src/OneWare.Verilog/VerilogModule.cs +++ b/src/OneWare.Verilog/VerilogModule.cs @@ -1,36 +1,48 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using CommunityToolkit.Mvvm.Input; +using OneWare.Essentials.Commands; using OneWare.Essentials.Helpers; +using OneWare.Essentials.Enums; using OneWare.Essentials.Models; using OneWare.Essentials.PackageManager; using OneWare.Essentials.Services; using OneWare.UniversalFpgaProjectSystem.Services; using OneWare.Verilog.Parsing; +using OneWare.Verilog.Services; using OneWare.Verilog.Templates; +using OneWare.Verilog.ViewModels; namespace OneWare.Verilog; public class VerilogModule : OneWareModuleBase { - public const string LspName = "Verible"; - public const string LspPathSetting = "VerilogModule_VeriblePath"; + public const string LspName = "LazyVerilog"; + public const string LspPathSetting = "VerilogModule_LazyVerilogPath"; public const string EnableSnippetsSetting = "VerilogModule_EnableSnippets"; - public static readonly string[] SupportedExtensions = [".v", ".sv"]; + public static readonly string[] VerilogExtensions = [".v", ".vh"]; + public static readonly string[] SystemVerilogExtensions = [".sv", ".svh"]; + public static readonly string[] FileListExtensions = [".f"]; + public static readonly string[] SupportedExtensions = [..VerilogExtensions, ..SystemVerilogExtensions]; - public static readonly Package VeriblePackage = new() + private const string LazyVerilogVersion = "2.0.0"; + private const string ReleaseBaseUrl = + "https://github.com/lazyverilog/LazyVerilog/releases/download/v2.0.0"; + + public static readonly Package LazyVerilogPackage = new() { Category = "Binaries", - Id = "verible", + Id = "lazyverilog", Type = "NativeTool", - Name = "Verible", - Description = "Used for Verilog/SystemVerilog Support", - License = "Apache 2.0", - IconUrl = "https://raw.githubusercontent.com/chipsalliance/verible/master/img/verible-logo-headline.png", + Name = "LazyVerilog", + Description = "SystemVerilog language server for RTL development", + License = "MIT", + IconUrl = "https://raw.githubusercontent.com/lazyverilog/LazyVerilog/main/assets/lazyverilog_logo.png", Links = [ new PackageLink { Name = "GitHub", - Url = "https://github.com/chipsalliance/verible" + Url = "https://github.com/lazyverilog/LazyVerilog" } ], Tabs = @@ -38,334 +50,65 @@ public class VerilogModule : OneWareModuleBase new PackageTab { Title = "License", - ContentUrl = "https://raw.githubusercontent.com/chipsalliance/verible/master/LICENSE" + ContentUrl = "https://raw.githubusercontent.com/lazyverilog/LazyVerilog/main/LICENSE" } ], Versions = [ new PackageVersion { - Version = "0.0.3582", - Targets = - [ - new PackageTarget - { - Target = "win-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3582-g25611a89/verible-v0.0-3582-g25611a89-win64.zip", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3582-g25611a89-win64", - "verible-verilog-ls.exe"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "linux-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3582-g25611a89/verible-v0.0-3582-g25611a89-linux-static-x86_64.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3582-g25611a89", "bin", "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "osx-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3582-g25611a89/verible-v0.0-3582-g25611a89-macOS.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3582-g25611a89-macOS", "bin", - "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - } - ] - }, - new PackageVersion - { - Version = "0.0.3716", - Targets = - [ - new PackageTarget - { - Target = "win-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3716-g914652db/verible-v0.0-3716-g914652db-win64.zip", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3716-g914652db-win64", - "verible-verilog-ls.exe"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "linux-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3716-g914652db/verible-v0.0-3716-g914652db-linux-static-x86_64.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3716-g914652db", "bin", "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "osx-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3716-g914652db/verible-v0.0-3716-g914652db-macOS.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3716-g914652db-macOS", "bin", - "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - } - ] - }, - new PackageVersion - { - Version = "0.0.3836", - Targets = - [ - new PackageTarget - { - Target = "win-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3836-g86ee9bab/verible-v0.0-3836-g86ee9bab-win64.zip", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3836-g86ee9bab-win64", - "verible-verilog-ls.exe"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "linux-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3836-g86ee9bab/verible-v0.0-3836-g86ee9bab-linux-static-x86_64.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3836-g86ee9bab", "bin", "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "osx-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-3836-g86ee9bab/verible-v0.0-3836-g86ee9bab-macOS.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-3836-g86ee9bab-macOS", "bin", - "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - } - ] - }, - new PackageVersion - { - Version = "0.0.4003", - Targets = - [ - new PackageTarget - { - Target = "win-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4003-gd42da6b9/verible-v0.0-4003-gd42da6b9-win64.zip", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4003-gd42da6b9-win64", - "verible-verilog-ls.exe"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "linux-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4003-gd42da6b9/verible-v0.0-4003-gd42da6b9-linux-static-x86_64.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4003-gd42da6b9", "bin", "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "osx-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4003-gd42da6b9/verible-v0.0-4003-gd42da6b9-macOS.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4003-gd42da6b9-macOS", "bin", - "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - } - ] - }, - new PackageVersion - { - Version = "0.0.4023", - Targets = - [ - new PackageTarget - { - Target = "win-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4023-gc1271a00/verible-v0.0-4023-gc1271a00-win64.zip", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4023-gc1271a00-win64", - "verible-verilog-ls.exe"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "linux-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4023-gc1271a00/verible-v0.0-4023-gc1271a00-linux-static-x86_64.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4023-gc1271a00", "bin", "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "osx-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4023-gc1271a00/verible-v0.0-4023-gc1271a00-macOS.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4023-gc1271a00-macOS", "bin", - "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - } - ] - }, - new PackageVersion - { - Version = "0.0.4061", + Version = LazyVerilogVersion, Targets = [ - new PackageTarget - { - Target = "win-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4061-ga870659f/verible-v0.0-4061-ga870659f-win64.zip", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4061-ga870659f-win64", - "verible-verilog-ls.exe"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "linux-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4061-ga870659f/verible-v0.0-4061-ga870659f-linux-static-x86_64.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4061-ga870659f", "bin", "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - }, - new PackageTarget - { - Target = "osx-x64", - Url = - "https://github.com/chipsalliance/verible/releases/download/v0.0-4061-ga870659f/verible-v0.0-4061-ga870659f-macOS.tar.gz", - AutoSetting = - [ - new PackageAutoSetting - { - RelativePath = Path.Combine("verible-v0.0-4061-ga870659f-macOS", "bin", - "verible-verilog-ls"), - SettingKey = LspPathSetting - } - ] - } + CreateTarget("win-x64", "windows-x64.exe"), + CreateTarget("linux-x64", "linux-x64-static"), + CreateTarget("linux-arm64", "linux-arm64-static"), + CreateTarget("osx-x64", "darwin-x64"), + CreateTarget("osx-arm64", "darwin-arm64") ] } ] }; + private static PackageTarget CreateTarget(string target, string assetPlatform) + { + var fileName = $"lazyverilog-lsp-v{LazyVerilogVersion}-{assetPlatform}"; + return new PackageTarget + { + Target = target, + Url = $"{ReleaseBaseUrl}/{fileName}", + IsArchive = false, + AutoSetting = + [ + new PackageAutoSetting + { + RelativePath = fileName, + SettingKey = LspPathSetting + } + ] + }; + } + public override void RegisterServices(IServiceCollection services) { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } public override void Initialize(IServiceProvider serviceProvider) { var settingsService = serviceProvider.Resolve(); var fpgaService = serviceProvider.Resolve(); + var dockService = serviceProvider.Resolve(); + var commandService = serviceProvider.Resolve(); - fpgaService.RegisterLanguage("Verilog", SupportedExtensions); + fpgaService.RegisterLanguage("Verilog", VerilogExtensions); + fpgaService.RegisterLanguage("SystemVerilog", SystemVerilogExtensions); - serviceProvider.Resolve().RegisterPackage(VeriblePackage); + serviceProvider.Resolve().RegisterPackage(LazyVerilogPackage); - var pathSetting = new FilePathSetting("Verible Path", "", null, + var pathSetting = new FilePathSetting("LazyVerilog Path", "", null, serviceProvider.Resolve().PackagesDirectory, File.Exists, PlatformHelper.ExeFile); settingsService.RegisterSetting("Languages", "Verilog", LspPathSetting, pathSetting); @@ -373,10 +116,61 @@ public override void Initialize(IServiceProvider serviceProvider) new CheckBoxSetting("Enable Snippets", true)); serviceProvider.Resolve().RegisterErrorSource(LspName); - serviceProvider.Resolve().RegisterTextMateLanguage("verilog", - "avares://OneWare.Verilog/Assets/verilog.tmLanguage.json", SupportedExtensions); - serviceProvider.Resolve() - .RegisterService(typeof(LanguageServiceVerilog), true, SupportedExtensions); + var languageManager = serviceProvider.Resolve(); + languageManager.RegisterTextMateLanguage("verilog", + "avares://OneWare.Verilog/Assets/verilog.tmLanguage.json", VerilogExtensions); + languageManager.RegisterTextMateLanguage("systemverilog", + "avares://OneWare.Verilog/Assets/systemverilog.tmLanguage.json", SystemVerilogExtensions); + languageManager.RegisterTextMateLanguage("verilog-filelist", + "avares://OneWare.Verilog/Assets/verilog-filelist.tmLanguage.json", FileListExtensions); + languageManager.RegisterService(typeof(LanguageServiceVerilog), true, SupportedExtensions); + + dockService.RegisterLayoutExtension(DockShowLocation.Right); + dockService.RegisterLayoutExtension(DockShowLocation.Right); + serviceProvider.Resolve().RegisterMenuItem("MainWindow_MainMenu/View/Tool Windows", + new MenuItemModel("VerilogOutline") + { + Header = "Verilog Outline", + Command = new RelayCommand(() => + dockService.Show(serviceProvider.Resolve(), DockShowLocation.Right)), + Icon = new IconModel(VerilogOutlineViewModel.IconKey) + }, new MenuItemModel("VerilogRtlTree") + { + Header = "Verilog RTL Tree", + Command = new RelayCommand(() => + dockService.Show(serviceProvider.Resolve(), DockShowLocation.Right)), + Icon = new IconModel(VerilogRtlTreeViewModel.IconKey) + }); + + var applicationCommands = serviceProvider.Resolve(); + RegisterCommand("LazyVerilog: Format Document", commandService.FormatAsync); + RegisterCommand("LazyVerilog: AutoWire", commandService.AutoWireAsync); + RegisterCommand("LazyVerilog: AutoFF", commandService.AutoFfAsync); + RegisterCommand("LazyVerilog: AutoFF All", commandService.AutoFfAllAsync); + RegisterCommand("LazyVerilog: Lint File", commandService.LintAsync); + RegisterCommand("LazyVerilog: Lint Project", commandService.LintAllAsync); + RegisterCommand("LazyVerilog: Inspect Interface", commandService.ShowInterfaceAsync); + RegisterCommand("LazyVerilog: Connect Hierarchy Ports", commandService.ConnectAsync); + RegisterCommand("LazyVerilog: Connect Interface Ports", commandService.ConnectInterfacePortsAsync); + RegisterCommand("LazyVerilog: Disconnect Interface Ports", commandService.DisconnectInterfacePortsAsync); + RegisterCommand("LazyVerilog: Show RTL Tree", async () => + { + var tree = serviceProvider.Resolve(); + dockService.Show(tree, DockShowLocation.Right); + await tree.LoadForwardAsync(); + }); + RegisterCommand("LazyVerilog: Show Reverse RTL Tree", async () => + { + var tree = serviceProvider.Resolve(); + dockService.Show(tree, DockShowLocation.Right); + await tree.LoadReverseAsync(); + }); + + void RegisterCommand(string name, Func action) + { + applicationCommands.RegisterCommand(new SimpleApplicationCommand(name, () => _ = action(), + () => commandService.CanExecute)); + } fpgaService.RegisterTemplate(); fpgaService.RegisterTemplate(); diff --git a/src/OneWare.Verilog/ViewModels/VerilogOutlineViewModel.cs b/src/OneWare.Verilog/ViewModels/VerilogOutlineViewModel.cs new file mode 100644 index 000000000..8b25b5bc5 --- /dev/null +++ b/src/OneWare.Verilog/ViewModels/VerilogOutlineViewModel.cs @@ -0,0 +1,103 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using DynamicData.Binding; +using OneWare.Essentials.Services; +using OneWare.Essentials.ViewModels; +using OneWare.Verilog.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace OneWare.Verilog.ViewModels; + +public partial class VerilogOutlineViewModel : ExtendedTool +{ + public const string IconKey = "VsImageLib.Search16XMd"; + + private readonly ILanguageManager _languageManager; + private readonly IMainDockService _mainDockService; + private IEditor? _currentEditor; + private int _refreshVersion; + + [ObservableProperty] private VerilogSymbolNode? _selectedNode; + + public VerilogOutlineViewModel(IMainDockService mainDockService, ILanguageManager languageManager) + : base(IconKey) + { + _mainDockService = mainDockService; + _languageManager = languageManager; + Id = "VerilogOutline"; + Title = "Outline"; + + mainDockService.WhenValueChanged(x => x.CurrentDocument) + .Subscribe(OnCurrentDocumentChanged); + } + + public ObservableCollection Nodes { get; } = new(); + + private void OnCurrentDocumentChanged(IExtendedDocument? currentDocument) + { + if (_currentEditor != null) _currentEditor.FileSaved -= OnCurrentFileSaved; + _currentEditor = currentDocument as IEditor; + if (_currentEditor != null) _currentEditor.FileSaved += OnCurrentFileSaved; + _ = RefreshAsync(); + } + + private void OnCurrentFileSaved(object? sender, EventArgs e) + { + _ = RefreshAsync(); + } + + [RelayCommand] + public async Task RefreshAsync() + { + var version = ++_refreshVersion; + if (_mainDockService.CurrentDocument is not IEditor editor || + !VerilogModule.SupportedExtensions.Contains(Path.GetExtension(editor.FullPath), + StringComparer.OrdinalIgnoreCase)) + { + Nodes.Clear(); + return; + } + + var service = _languageManager.GetLanguageService(editor.FullPath); + var symbols = service == null ? null : await service.RequestSymbolsAsync(editor.FullPath); + if (version != _refreshVersion) return; + + Nodes.Clear(); + if (symbols is null) return; + + foreach (var symbol in symbols) + { + if (symbol.IsDocumentSymbol && symbol.DocumentSymbol != null) + Nodes.Add(CreateNode(symbol.DocumentSymbol, editor.FullPath)); + else if (symbol.IsDocumentSymbolInformation && symbol.SymbolInformation != null) + { + var info = symbol.SymbolInformation; + Nodes.Add(new VerilogSymbolNode(info.Name, info.Kind, info.Location.Uri.GetFileSystemPath(), + info.Location.Range)); + } + } + } + + [RelayCommand] + public async Task NavigateAsync() + { + if (SelectedNode == null) return; + if (await _mainDockService.OpenFileAsync(SelectedNode.FilePath) is not IEditor editor) return; + + var start = SelectedNode.Range.Start; + var end = SelectedNode.Range.End; + var startOffset = editor.CurrentDocument.GetOffset(start.Line + 1, start.Character + 1); + var endOffset = editor.CurrentDocument.GetOffset(end.Line + 1, end.Character + 1); + editor.Select(startOffset, Math.Max(endOffset - startOffset, 0)); + } + + private static VerilogSymbolNode CreateNode(DocumentSymbol symbol, string filePath) + { + var node = new VerilogSymbolNode(symbol.Name, symbol.Kind, filePath, symbol.SelectionRange); + if (symbol.Children is not null) + foreach (var child in symbol.Children) + node.Children.Add(CreateNode(child, filePath)); + return node; + } +} diff --git a/src/OneWare.Verilog/ViewModels/VerilogRtlTreeViewModel.cs b/src/OneWare.Verilog/ViewModels/VerilogRtlTreeViewModel.cs new file mode 100644 index 000000000..f4be1956f --- /dev/null +++ b/src/OneWare.Verilog/ViewModels/VerilogRtlTreeViewModel.cs @@ -0,0 +1,87 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OneWare.Essentials.Services; +using OneWare.Essentials.ViewModels; +using OneWare.Verilog.Models; + +namespace OneWare.Verilog.ViewModels; + +public partial class VerilogRtlTreeViewModel : ExtendedTool +{ + public const string IconKey = "VsImageLib.Team16X"; + + private readonly ILanguageManager _languageManager; + private readonly IMainDockService _mainDockService; + + [ObservableProperty] private bool _isReverse; + [ObservableProperty] private VerilogRtlTreeNode? _selectedNode; + + public VerilogRtlTreeViewModel(IMainDockService mainDockService, ILanguageManager languageManager) + : base(IconKey) + { + _mainDockService = mainDockService; + _languageManager = languageManager; + Id = "VerilogRtlTree"; + Title = "RTL Tree"; + } + + public ObservableCollection Nodes { get; } = new(); + + [RelayCommand] + public Task LoadForwardAsync() + { + IsReverse = false; + return RefreshAsync(); + } + + [RelayCommand] + public Task LoadReverseAsync() + { + IsReverse = true; + return RefreshAsync(); + } + + [RelayCommand] + public async Task RefreshAsync() + { + Nodes.Clear(); + if (_mainDockService.CurrentDocument is not IEditor editor) return; + if (_languageManager.GetLanguageService(editor.FullPath) is not LanguageServiceVerilog service) return; + + var result = await service.ExecuteCommandAsync(CreateCommand( + IsReverse ? "lazyverilog.rtlTreeReverse" : "lazyverilog.rtlTree", + editor.FullPath)); + if (result is not JObject) return; + + var root = result.ToObject(); + if (root != null) Nodes.Add(root); + } + + [RelayCommand] + public async Task NavigateAsync() + { + if (SelectedNode == null || string.IsNullOrWhiteSpace(SelectedNode.File) || SelectedNode.Line <= 0) return; + + var path = SelectedNode.File.StartsWith("file:", StringComparison.OrdinalIgnoreCase) + ? DocumentUri.From(SelectedNode.File).GetFileSystemPath() + : SelectedNode.File; + if (await _mainDockService.OpenFileAsync(path) is not IEditor editor) return; + + var line = editor.CurrentDocument.GetLineByNumber(Math.Min(SelectedNode.Line, editor.CurrentDocument.LineCount)); + editor.Select(Math.Min(line.Offset + SelectedNode.Column, line.EndOffset), 0); + } + + private static Command CreateCommand(string name, string filePath) + { + return new Command + { + Title = name, + Name = name, + Arguments = new JArray(DocumentUri.FromFileSystemPath(filePath).ToString()) + }; + } +} diff --git a/src/OneWare.Verilog/Views/VerilogOutlineView.axaml b/src/OneWare.Verilog/Views/VerilogOutlineView.axaml new file mode 100644 index 000000000..a4ed0c6aa --- /dev/null +++ b/src/OneWare.Verilog/Views/VerilogOutlineView.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/src/OneWare.Verilog/Views/VerilogOutlineView.axaml.cs b/src/OneWare.Verilog/Views/VerilogOutlineView.axaml.cs new file mode 100644 index 000000000..10c180133 --- /dev/null +++ b/src/OneWare.Verilog/Views/VerilogOutlineView.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia.Controls; +using Avalonia.Input; +using OneWare.Verilog.ViewModels; + +namespace OneWare.Verilog.Views; + +public partial class VerilogOutlineView : UserControl +{ + public VerilogOutlineView() + { + InitializeComponent(); + } + + private void TreeView_OnDoubleTapped(object? sender, TappedEventArgs e) + { + if (DataContext is VerilogOutlineViewModel viewModel) + _ = viewModel.NavigateAsync(); + } +} diff --git a/src/OneWare.Verilog/Views/VerilogRtlTreeView.axaml b/src/OneWare.Verilog/Views/VerilogRtlTreeView.axaml new file mode 100644 index 000000000..5a2fdee6d --- /dev/null +++ b/src/OneWare.Verilog/Views/VerilogRtlTreeView.axaml @@ -0,0 +1,33 @@ + + + + +