diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index d51656b370..fac9c5c7de 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -130,10 +130,10 @@ 5878DA82291863F900DD95A3 /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DA81291863F900DD95A3 /* AcknowledgementsView.swift */; }; 5878DA842918642000DD95A3 /* ParsePackagesResolved.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DA832918642000DD95A3 /* ParsePackagesResolved.swift */; }; 5878DA872918642F00DD95A3 /* AcknowledgementsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DA862918642F00DD95A3 /* AcknowledgementsViewModel.swift */; }; - 5878DAA5291AE76700DD95A3 /* QuickOpenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA1291AE76700DD95A3 /* QuickOpenView.swift */; }; - 5878DAA6291AE76700DD95A3 /* QuickOpenPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA2291AE76700DD95A3 /* QuickOpenPreviewView.swift */; }; - 5878DAA7291AE76700DD95A3 /* QuickOpenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA3291AE76700DD95A3 /* QuickOpenViewModel.swift */; }; - 5878DAA8291AE76700DD95A3 /* QuickOpenItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA4291AE76700DD95A3 /* QuickOpenItem.swift */; }; + 5878DAA5291AE76700DD95A3 /* OpenQuicklyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA1291AE76700DD95A3 /* OpenQuicklyView.swift */; }; + 5878DAA6291AE76700DD95A3 /* OpenQuicklyPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA2291AE76700DD95A3 /* OpenQuicklyPreviewView.swift */; }; + 5878DAA7291AE76700DD95A3 /* OpenQuicklyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA3291AE76700DD95A3 /* OpenQuicklyViewModel.swift */; }; + 5878DAA8291AE76700DD95A3 /* OpenQuicklyListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAA4291AE76700DD95A3 /* OpenQuicklyListItemView.swift */; }; 5878DAB0291D627C00DD95A3 /* EditorPathBarMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAAD291D627C00DD95A3 /* EditorPathBarMenu.swift */; }; 5878DAB1291D627C00DD95A3 /* EditorPathBarComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAAE291D627C00DD95A3 /* EditorPathBarComponent.swift */; }; 5878DAB2291D627C00DD95A3 /* EditorPathBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878DAAF291D627C00DD95A3 /* EditorPathBarView.swift */; }; @@ -325,6 +325,8 @@ 61A53A812B4449F00093BF8A /* WorkspaceDocument+Index.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */; }; 661EF7B82BEE215300C3E577 /* ImageFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661EF7B72BEE215300C3E577 /* ImageFileView.swift */; }; 661EF7BD2BEE215300C3E577 /* LoadingFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661EF7BC2BEE215300C3E577 /* LoadingFileView.swift */; }; + 664935422C35A5BC00461C35 /* NSTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664935412C35A5BC00461C35 /* NSTableViewWrapper.swift */; }; + 6653EE552C34817900B82DE2 /* QuickSearchResultLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6653EE542C34817900B82DE2 /* QuickSearchResultLabel.swift */; }; 669BC4082BED306400D1197C /* AnyFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669BC4072BED306400D1197C /* AnyFileView.swift */; }; 66AF6CE22BF17CC300D83C9D /* StatusBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF6CE12BF17CC300D83C9D /* StatusBarViewModel.swift */; }; 66AF6CE42BF17F6800D83C9D /* StatusBarFileInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF6CE32BF17F6800D83C9D /* StatusBarFileInfoView.swift */; }; @@ -406,7 +408,6 @@ 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; 6CAAF69229BCC71C00A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; 6CAAF69429BCD78600A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; - 6CABB19E29C5591D00340467 /* NSTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CABB19C29C5591D00340467 /* NSTableViewWrapper.swift */; }; 6CABB1A129C5593800340467 /* SearchPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CABB1A029C5593800340467 /* SearchPanelView.swift */; }; 6CB446402B6DFF3A00539ED0 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CB4463F2B6DFF3A00539ED0 /* CodeEditSourceEditor */; }; 6CB52DC92AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CB52DC82AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift */; }; @@ -748,10 +749,10 @@ 5878DA81291863F900DD95A3 /* AcknowledgementsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcknowledgementsView.swift; sourceTree = ""; }; 5878DA832918642000DD95A3 /* ParsePackagesResolved.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsePackagesResolved.swift; sourceTree = ""; }; 5878DA862918642F00DD95A3 /* AcknowledgementsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcknowledgementsViewModel.swift; sourceTree = ""; }; - 5878DAA1291AE76700DD95A3 /* QuickOpenView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenView.swift; sourceTree = ""; wrapsLines = 1; }; - 5878DAA2291AE76700DD95A3 /* QuickOpenPreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenPreviewView.swift; sourceTree = ""; }; - 5878DAA3291AE76700DD95A3 /* QuickOpenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenViewModel.swift; sourceTree = ""; }; - 5878DAA4291AE76700DD95A3 /* QuickOpenItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenItem.swift; sourceTree = ""; }; + 5878DAA1291AE76700DD95A3 /* OpenQuicklyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyView.swift; sourceTree = ""; wrapsLines = 1; }; + 5878DAA2291AE76700DD95A3 /* OpenQuicklyPreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyPreviewView.swift; sourceTree = ""; }; + 5878DAA3291AE76700DD95A3 /* OpenQuicklyViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyViewModel.swift; sourceTree = ""; }; + 5878DAA4291AE76700DD95A3 /* OpenQuicklyListItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyListItemView.swift; sourceTree = ""; }; 5878DAAD291D627C00DD95A3 /* EditorPathBarMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorPathBarMenu.swift; sourceTree = ""; }; 5878DAAE291D627C00DD95A3 /* EditorPathBarComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorPathBarComponent.swift; sourceTree = ""; }; 5878DAAF291D627C00DD95A3 /* EditorPathBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorPathBarView.swift; sourceTree = ""; }; @@ -943,6 +944,8 @@ 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Index.swift"; sourceTree = ""; }; 661EF7B72BEE215300C3E577 /* ImageFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFileView.swift; sourceTree = ""; }; 661EF7BC2BEE215300C3E577 /* LoadingFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingFileView.swift; sourceTree = ""; }; + 664935412C35A5BC00461C35 /* NSTableViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTableViewWrapper.swift; sourceTree = ""; }; + 6653EE542C34817900B82DE2 /* QuickSearchResultLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSearchResultLabel.swift; sourceTree = ""; }; 669BC4072BED306400D1197C /* AnyFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyFileView.swift; sourceTree = ""; }; 66AF6CE12BF17CC300D83C9D /* StatusBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarViewModel.swift; sourceTree = ""; }; 66AF6CE32BF17F6800D83C9D /* StatusBarFileInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarFileInfoView.swift; sourceTree = ""; }; @@ -1010,7 +1013,6 @@ 6C91D57129B176FF0059A90D /* EditorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorManager.swift; sourceTree = ""; }; 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; 6CA1AE942B46950000378EAB /* EditorInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorInstance.swift; sourceTree = ""; }; - 6CABB19C29C5591D00340467 /* NSTableViewWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NSTableViewWrapper.swift; path = CodeEdit/Features/QuickOpen/Views/NSTableViewWrapper.swift; sourceTree = SOURCE_ROOT; }; 6CABB1A029C5593800340467 /* SearchPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchPanelView.swift; sourceTree = ""; }; 6CB52DC82AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CEWorkspaceFileManager+FileManagement.swift"; sourceTree = ""; }; 6CBA0D502A1BF524002C6FAA /* SegmentedControlImproved.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlImproved.swift; sourceTree = ""; }; @@ -1593,7 +1595,7 @@ 58A5DF9D29339F6400D1BD5D /* Keybindings */, 30B087FB2C0D53080063A882 /* LSP */, 287776EA27E350A100D46668 /* NavigatorArea */, - 5878DAA0291AE76700DD95A3 /* QuickOpen */, + 5878DAA0291AE76700DD95A3 /* OpenQuickly */, 58798210292D92370085B254 /* Search */, B67B270029D7868000FB9301 /* Settings */, 6C147C4729A329E50089B630 /* SplitView */, @@ -1757,22 +1759,22 @@ path = ViewModels; sourceTree = ""; }; - 5878DAA0291AE76700DD95A3 /* QuickOpen */ = { + 5878DAA0291AE76700DD95A3 /* OpenQuickly */ = { isa = PBXGroup; children = ( 5878DAAA291D5CAA00DD95A3 /* ViewModels */, 5878DAA9291D5CA100DD95A3 /* Views */, ); - path = QuickOpen; + path = OpenQuickly; sourceTree = ""; }; 5878DAA9291D5CA100DD95A3 /* Views */ = { isa = PBXGroup; children = ( - 6CABB19C29C5591D00340467 /* NSTableViewWrapper.swift */, - 5878DAA1291AE76700DD95A3 /* QuickOpenView.swift */, - 5878DAA2291AE76700DD95A3 /* QuickOpenPreviewView.swift */, - 5878DAA4291AE76700DD95A3 /* QuickOpenItem.swift */, + 664935412C35A5BC00461C35 /* NSTableViewWrapper.swift */, + 5878DAA1291AE76700DD95A3 /* OpenQuicklyView.swift */, + 5878DAA2291AE76700DD95A3 /* OpenQuicklyPreviewView.swift */, + 5878DAA4291AE76700DD95A3 /* OpenQuicklyListItemView.swift */, ); path = Views; sourceTree = ""; @@ -1780,7 +1782,7 @@ 5878DAAA291D5CAA00DD95A3 /* ViewModels */ = { isa = PBXGroup; children = ( - 5878DAA3291AE76700DD95A3 /* QuickOpenViewModel.swift */, + 5878DAA3291AE76700DD95A3 /* OpenQuicklyViewModel.swift */, 613899BB2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift */, ); path = ViewModels; @@ -1807,6 +1809,7 @@ 58798210292D92370085B254 /* Search */ = { isa = PBXGroup; children = ( + 6653EE532C34816F00B82DE2 /* Views */, 613899AF2B6E6FB800A5CAF6 /* FuzzySearch */, 58798212292D92370085B254 /* Extensions */, 58798214292D92370085B254 /* Model */, @@ -2535,6 +2538,14 @@ path = ActivityViewer; sourceTree = ""; }; + 6653EE532C34816F00B82DE2 /* Views */ = { + isa = PBXGroup; + children = ( + 6653EE542C34817900B82DE2 /* QuickSearchResultLabel.swift */, + ); + path = Views; + sourceTree = ""; + }; 66AF6CE02BF17CB100D83C9D /* ViewModels */ = { isa = PBXGroup; children = ( @@ -3610,7 +3621,7 @@ 587B9E6B29301D8F00AC7927 /* GitLabAvatarURL.swift in Sources */, 58798233292E30B90085B254 /* FeedbackToolbar.swift in Sources */, 587B9E6829301D8F00AC7927 /* GitLabAccountModel.swift in Sources */, - 5878DAA7291AE76700DD95A3 /* QuickOpenViewModel.swift in Sources */, + 5878DAA7291AE76700DD95A3 /* OpenQuicklyViewModel.swift in Sources */, 6CFF967429BEBCC300182D6F /* FindCommands.swift in Sources */, 587B9E6529301D8F00AC7927 /* GitLabGroupAccess.swift in Sources */, 6C91D57229B176FF0059A90D /* EditorManager.swift in Sources */, @@ -3653,6 +3664,7 @@ 58822528292C280D00E83CDE /* StatusBarEncodingSelector.swift in Sources */, 0FD96BCE2BEF42530025A697 /* CodeEditWindowController+Toolbar.swift in Sources */, 6C7F37FE2A3EA6FA00217B83 /* View+focusedValue.swift in Sources */, + 6653EE552C34817900B82DE2 /* QuickSearchResultLabel.swift in Sources */, B6C4F2A12B3CA37500B2B140 /* SourceControlNavigatorHistoryView.swift in Sources */, 6CBE1CFB2B71DAA6003AC32E /* Loopable.swift in Sources */, 30B088092C0D53080063A882 /* LanguageServer+Formatting.swift in Sources */, @@ -3857,7 +3869,7 @@ B6E41C9429DEAE260088F9F4 /* SourceControlAccount.swift in Sources */, 2806E9022979588B000040F4 /* Contributor.swift in Sources */, 58D01C98293167DC00C5B6B4 /* String+RemoveOccurrences.swift in Sources */, - 5878DAA8291AE76700DD95A3 /* QuickOpenItem.swift in Sources */, + 5878DAA8291AE76700DD95A3 /* OpenQuicklyListItemView.swift in Sources */, 58FD7608291EA1CB0051D6E4 /* QuickActionsViewModel.swift in Sources */, B65B11042B09DB1C002852CF /* GitClient+Fetch.swift in Sources */, 5878DA872918642F00DD95A3 /* AcknowledgementsViewModel.swift in Sources */, @@ -3877,7 +3889,6 @@ 587B9E6229301D8F00AC7927 /* GitLabConfiguration.swift in Sources */, 61A53A7E2B4449870093BF8A /* WorkspaceDocument+Find.swift in Sources */, 30B0880A2C0D53080063A882 /* LanguageServer+Hover.swift in Sources */, - 6CABB19E29C5591D00340467 /* NSTableViewWrapper.swift in Sources */, 5879821B292D92370085B254 /* SearchResultMatchModel.swift in Sources */, 6C48B5DA2C0D5FC5001E9955 /* CurrentUser.swift in Sources */, 04BA7C1E2AE2D8A000584E1C /* GitClient+Clone.swift in Sources */, @@ -3925,7 +3936,7 @@ B62AEDC92A2704F3009A9F52 /* UtilityAreaTabView.swift in Sources */, 30B088052C0D53080063A882 /* LanguageServer+DocumentLink.swift in Sources */, 58798250292E78D80085B254 /* CodeFileDocument.swift in Sources */, - 5878DAA5291AE76700DD95A3 /* QuickOpenView.swift in Sources */, + 5878DAA5291AE76700DD95A3 /* OpenQuicklyView.swift in Sources */, 201169D72837B2E300F92B46 /* SourceControlNavigatorView.swift in Sources */, 30B088162C0D53080063A882 /* LSPCache.swift in Sources */, B6F0517929D9E3C900D72287 /* SourceControlGitView.swift in Sources */, @@ -3942,6 +3953,7 @@ 58D01C99293167DC00C5B6B4 /* String+MD5.swift in Sources */, 20EBB505280C329800F3A5DA /* CommitListItemView.swift in Sources */, 5878DAB2291D627C00DD95A3 /* EditorPathBarView.swift in Sources */, + 664935422C35A5BC00461C35 /* NSTableViewWrapper.swift in Sources */, 04BC1CDE2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift in Sources */, 6C6BD70129CD172700235D17 /* ExtensionsListView.swift in Sources */, 77A01E362BB428EE00F0EA38 /* CEWorkspaceSettingsPageView.swift in Sources */, @@ -3958,7 +3970,7 @@ 043BCF03281DA18A000AC47C /* WorkspaceDocument+SearchState.swift in Sources */, 58822527292C280D00E83CDE /* StatusBarIndentSelector.swift in Sources */, 587B9E9629301D8F00AC7927 /* BitBucketRepositories.swift in Sources */, - 5878DAA6291AE76700DD95A3 /* QuickOpenPreviewView.swift in Sources */, + 5878DAA6291AE76700DD95A3 /* OpenQuicklyPreviewView.swift in Sources */, 58798286292ED0FB0085B254 /* SwiftTerm+Color+Init.swift in Sources */, 30B087FC2C0D53080063A882 /* LanguageServer+CallHierarchy.swift in Sources */, 6CFF967C29BEBD5200182D6F /* WindowCommands.swift in Sources */, diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift index 2264d777ba..d6f0fec287 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift @@ -26,11 +26,11 @@ import Combine /// loading all intermediate subdirectories (from the nearest cached parent to the file) has not been done yet and doing /// so would be unnecessary. /// -/// An example of this is in the ``QuickOpenView``. This view finds a file URL via a search bar, and needs to display a -/// quick preview of the file. There's a good chance the file is deep in some subdirectory of the workspace, so fetching -/// it from the ``CEWorkspaceFileManager`` may require loading and caching multiple directories. Instead, it just -/// makes a disconnected object and uses it for the preview. Then, when opening the file in the workspace it forces the -/// file to be loaded and cached. +/// An example of this is in the ``OpenQuicklyView``. This view finds a file URL via a search bar, and needs to display +/// a quick preview of the file. There's a good chance the file is deep in some subdirectory of the workspace, so +/// fetching it from the ``CEWorkspaceFileManager`` may require loading and caching multiple directories. Instead, it +/// just makes a disconnected object and uses it for the preview. Then, when opening the file in the workspace it +/// forces the file to be loaded and cached. final class CEWorkspaceFile: Codable, Comparable, Hashable, Identifiable, EditorTabRepresentable { /// The id of the ``CEWorkspaceFile``. diff --git a/CodeEdit/Features/Commands/ViewModels/QuickActionsViewModel.swift b/CodeEdit/Features/Commands/ViewModels/QuickActionsViewModel.swift index ba53c043f5..a796c2aaa7 100644 --- a/CodeEdit/Features/Commands/ViewModels/QuickActionsViewModel.swift +++ b/CodeEdit/Features/Commands/ViewModels/QuickActionsViewModel.swift @@ -5,7 +5,7 @@ // Created by Alex on 25.05.2022. // -import Foundation +import SwiftUI /// Simple state class for command palette view. Contains currently selected command, /// query text and list of filtered commands @@ -35,4 +35,17 @@ final class QuickActionsViewModel: ObservableObject { self.filteredCommands = CommandManager.shared.commands.filter { $0.title.localizedCaseInsensitiveContains(val) } self.selected = self.filteredCommands.first } + + func highlight(_ commandTitle: String) -> NSAttributedString { + let attribText = NSMutableAttributedString(string: commandTitle) + let range: NSRange = attribText.mutableString.range( + of: self.commandQuery, + options: NSString.CompareOptions.caseInsensitive + ) + attribText.addAttribute(.foregroundColor, value: NSColor(Color(.labelColor)), range: range) + attribText.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize), range: range) + + return attribText + } + } diff --git a/CodeEdit/Features/Commands/Views/QuickActionsView.swift b/CodeEdit/Features/Commands/Views/QuickActionsView.swift index 4bb72f1438..bf8e465fe0 100644 --- a/CodeEdit/Features/Commands/Views/QuickActionsView.swift +++ b/CodeEdit/Features/Commands/Views/QuickActionsView.swift @@ -43,7 +43,7 @@ struct QuickActionsView: View { } var body: some View { - SearchPanelView( + SearchPanelView( title: "Commands", image: Image(systemName: "magnifyingglass"), options: $state.filteredCommands, @@ -51,7 +51,11 @@ struct QuickActionsView: View { alwaysShowOptions: true, optionRowHeight: 30 ) { command in - SearchResultLabel(labelName: command.title, textToMatch: state.commandQuery) + QuickSearchResultLabel( + labelName: command.title, + charactersToHighlight: [], + nsLabelName: state.highlight(command.title) + ) } onRowClick: { command in callHandler(command: command) } onClose: { @@ -62,47 +66,3 @@ struct QuickActionsView: View { } } } - -/// Implementation of command palette entity. While swiftui does not allow to use NSMutableAttributeStrings, -/// the only way to fallback to UIKit and have NSViewRepresentable to be a bridge between UIKit and SwiftUI. -/// Highlights currently entered text query - -struct SearchResultLabel: NSViewRepresentable { - - var labelName: String - var textToMatch: String - - public func makeNSView(context: Context) -> some NSTextField { - let label = NSTextField(wrappingLabelWithString: labelName) - label.translatesAutoresizingMaskIntoConstraints = false - label.drawsBackground = false - label.textColor = .labelColor - label.isEditable = false - label.isSelectable = false - label.font = .labelFont(ofSize: 13) - label.allowsDefaultTighteningForTruncation = false - label.cell?.truncatesLastVisibleLine = true - label.cell?.wraps = true - label.maximumNumberOfLines = 1 - label.attributedStringValue = highlight() - return label - } - - func highlight() -> NSAttributedString { - let attribText = NSMutableAttributedString(string: self.labelName) - let range: NSRange = attribText.mutableString.range( - of: self.textToMatch, - options: NSString.CompareOptions.caseInsensitive - ) - attribText.addAttribute(.foregroundColor, value: NSColor(Color(.labelColor)), range: range) - attribText.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize), range: range) - - return attribText - } - - func updateNSView(_ nsView: NSViewType, context: Context) { - nsView.textColor = textToMatch.isEmpty ? .labelColor : .secondaryLabelColor - nsView.attributedStringValue = highlight() - } - -} diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 0b4066585d..ec9131dace 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -126,7 +126,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs } @IBAction func openQuickly(_ sender: Any) { - if let workspace, let state = workspace.quickOpenViewModel { + if let workspace, let state = workspace.openQuicklyViewModel { if let quickOpenPanel { if quickOpenPanel.isKeyWindow { quickOpenPanel.close() @@ -139,7 +139,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs let panel = SearchPanel() self.quickOpenPanel = panel - let contentView = QuickOpenView(state: state) { + let contentView = OpenQuicklyView(state: state) { panel.close() } openFile: { file in workspace.editorManager.openTab(item: file) diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index c7edd7bcc5..f813e9b550 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -34,7 +34,7 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { var statusBarViewModel = StatusBarViewModel() var utilityAreaModel = UtilityAreaViewModel() var searchState: SearchState? - var quickOpenViewModel: QuickOpenViewModel? + var openQuicklyViewModel: OpenQuicklyViewModel? var commandsPaletteState: QuickActionsViewModel? var listenerModel: WorkspaceNotificationModel = .init() var sourceControlManager: SourceControlManager? @@ -123,7 +123,7 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { self.sourceControlManager = sourceControlManager sourceControlManager.fileManager = workspaceFileManager self.searchState = .init(self) - self.quickOpenViewModel = .init(fileURL: url) + self.openQuicklyViewModel = .init(fileURL: url) self.commandsPaletteState = .init() editorManager.restoreFromState(self) diff --git a/CodeEdit/Features/OpenQuickly/ViewModels/OpenQuicklyViewModel.swift b/CodeEdit/Features/OpenQuickly/ViewModels/OpenQuicklyViewModel.swift new file mode 100644 index 0000000000..9f54bb58e8 --- /dev/null +++ b/CodeEdit/Features/OpenQuickly/ViewModels/OpenQuicklyViewModel.swift @@ -0,0 +1,91 @@ +// +// OpenQuicklyViewModel.swift +// CodeEditModules/QuickOpen +// +// Created by Marco Carnevali on 05/04/22. +// + +import Combine +import Foundation +import CollectionConcurrencyKit + +final class OpenQuicklyViewModel: ObservableObject { + @Published var query: String = "" + @Published var searchResults: [SearchResult] = [] + + let fileURL: URL + var runningTask: Task? + + init(fileURL: URL) { + self.fileURL = fileURL + } + + /// This is used to populate the ``OpenQuicklyListItemView`` view which shows the search results to the user. + /// + /// ``OpenQuicklyPreviewView`` also uses this to load the `fileUrl` for preview. + struct SearchResult: Identifiable, Hashable { + var id: String { fileURL.id } + let fileURL: URL + let matchedCharacters: [NSRange] + + // This custom Hashable implementation prevents the highlighted + // selection from flickering when searching in 'Open Quickly'. + // + // See https://github.com/CodeEditApp/CodeEdit/pull/1790#issuecomment-2206832901 + // for flickering visuals. + // + // Before commit 0e28b382f59184b7ebe5a7c3295afa3655b7d4e7, only the fileURL + // was retrieved from the search results and it worked as expected. + // + static func == (lhs: Self, rhs: Self) -> Bool { lhs.fileURL == rhs.fileURL } + func hash(into hasher: inout Hasher) { hasher.combine(fileURL) } + } + + func fetchResults() { + let startTime = Date() + guard query != "" else { + searchResults = [] + return + } + + runningTask?.cancel() + runningTask = Task.detached(priority: .userInitiated) { + let enumerator = FileManager.default.enumerator( + at: self.fileURL, + includingPropertiesForKeys: [ + .isRegularFileKey + ], + options: [ + .skipsPackageDescendants + ] + ) + if let filePaths = enumerator?.allObjects as? [URL] { + guard !Task.isCancelled else { return } + /// removes all filePaths which aren't regular files + let filteredFiles = filePaths.filter { url in + do { + let values = try url.resourceValues(forKeys: [.isRegularFileKey]) + return (values.isRegularFile ?? false) + } catch { + return false + } + } + + let fuzzySearchResults = await filteredFiles.fuzzySearch( + query: self.query.trimmingCharacters(in: .whitespaces) + ).concurrentMap { + SearchResult( + fileURL: $0.item, + matchedCharacters: $0.result.matchedParts + ) + } + + guard !Task.isCancelled else { return } + await MainActor.run { + self.searchResults = fuzzySearchResults + print("Duration: \(Date().timeIntervalSince(startTime))") + } + } + } + } +} diff --git a/CodeEdit/Features/QuickOpen/ViewModels/URL+FuzzySearchable.swift b/CodeEdit/Features/OpenQuickly/ViewModels/URL+FuzzySearchable.swift similarity index 100% rename from CodeEdit/Features/QuickOpen/ViewModels/URL+FuzzySearchable.swift rename to CodeEdit/Features/OpenQuickly/ViewModels/URL+FuzzySearchable.swift diff --git a/CodeEdit/Features/QuickOpen/Views/NSTableViewWrapper.swift b/CodeEdit/Features/OpenQuickly/Views/NSTableViewWrapper.swift similarity index 100% rename from CodeEdit/Features/QuickOpen/Views/NSTableViewWrapper.swift rename to CodeEdit/Features/OpenQuickly/Views/NSTableViewWrapper.swift diff --git a/CodeEdit/Features/QuickOpen/Views/QuickOpenItem.swift b/CodeEdit/Features/OpenQuickly/Views/OpenQuicklyListItemView.swift similarity index 58% rename from CodeEdit/Features/QuickOpen/Views/QuickOpenItem.swift rename to CodeEdit/Features/OpenQuickly/Views/OpenQuicklyListItemView.swift index ad3aa8a12d..6acd23e381 100644 --- a/CodeEdit/Features/QuickOpen/Views/QuickOpenItem.swift +++ b/CodeEdit/Features/OpenQuickly/Views/OpenQuicklyListItemView.swift @@ -1,5 +1,5 @@ // -// QuickOpenItem.swift +// OpenQuicklyListItemView.swift // CodeEditModules/QuickOpen // // Created by Pavel Kasila on 20.03.22. @@ -7,31 +7,33 @@ import SwiftUI -struct QuickOpenItem: View { +struct OpenQuicklyListItemView: View { private let baseDirectory: URL - private let fileURL: URL + private let searchResult: OpenQuicklyViewModel.SearchResult init( baseDirectory: URL, - fileURL: URL + searchResult: OpenQuicklyViewModel.SearchResult ) { self.baseDirectory = baseDirectory - self.fileURL = fileURL + self.searchResult = searchResult } var relativePathComponents: ArraySlice { - return fileURL.pathComponents.dropFirst(baseDirectory.pathComponents.count).dropLast() + return searchResult.fileURL.pathComponents.dropFirst(baseDirectory.pathComponents.count).dropLast() } var body: some View { HStack(spacing: 8) { - Image(nsImage: NSWorkspace.shared.icon(forFile: fileURL.path)) + Image(nsImage: NSWorkspace.shared.icon(forFile: searchResult.fileURL.path)) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 24, height: 24) VStack(alignment: .leading, spacing: 0) { - Text(fileURL.lastPathComponent).font(.system(size: 13)) - .lineLimit(1) + QuickSearchResultLabel( + labelName: searchResult.fileURL.lastPathComponent, + charactersToHighlight: searchResult.matchedCharacters + ) Text(relativePathComponents.joined(separator: " ▸ ")) .font(.system(size: 10.5)) .foregroundColor(.secondary) diff --git a/CodeEdit/Features/QuickOpen/Views/QuickOpenPreviewView.swift b/CodeEdit/Features/OpenQuickly/Views/OpenQuicklyPreviewView.swift similarity index 91% rename from CodeEdit/Features/QuickOpen/Views/QuickOpenPreviewView.swift rename to CodeEdit/Features/OpenQuickly/Views/OpenQuicklyPreviewView.swift index caaf72806a..78edae4d3e 100644 --- a/CodeEdit/Features/QuickOpen/Views/QuickOpenPreviewView.swift +++ b/CodeEdit/Features/OpenQuickly/Views/OpenQuicklyPreviewView.swift @@ -1,5 +1,5 @@ // -// QuickOpenPreviewView.swift +// OpenQuicklyPreviewView.swift // CodeEditModules/QuickOpen // // Created by Pavel Kasila on 20.03.22. @@ -7,7 +7,7 @@ import SwiftUI -struct QuickOpenPreviewView: View { +struct OpenQuicklyPreviewView: View { private let queue = DispatchQueue(label: "app.codeedit.CodeEdit.quickOpen.preview") private let item: CEWorkspaceFile diff --git a/CodeEdit/Features/QuickOpen/Views/QuickOpenView.swift b/CodeEdit/Features/OpenQuickly/Views/OpenQuicklyView.swift similarity index 53% rename from CodeEdit/Features/QuickOpen/Views/QuickOpenView.swift rename to CodeEdit/Features/OpenQuickly/Views/OpenQuicklyView.swift index 34a04d0083..5bbbbd09c8 100644 --- a/CodeEdit/Features/QuickOpen/Views/QuickOpenView.swift +++ b/CodeEdit/Features/OpenQuickly/Views/OpenQuicklyView.swift @@ -1,5 +1,5 @@ // -// QuickOpenView.swift +// OpenQuicklyView.swift // CodeEditModules/QuickOpen // // Created by Pavel Kasila on 20.03.22. @@ -13,22 +13,22 @@ extension URL: Identifiable { } } -struct QuickOpenView: View { +struct OpenQuicklyView: View { @EnvironmentObject private var workspace: WorkspaceDocument private let onClose: () -> Void private let openFile: (CEWorkspaceFile) -> Void - @ObservedObject private var state: QuickOpenViewModel + @ObservedObject private var openQuicklyViewModel: OpenQuicklyViewModel @State private var selectedItem: CEWorkspaceFile? init( - state: QuickOpenViewModel, + state: OpenQuicklyViewModel, onClose: @escaping () -> Void, openFile: @escaping (CEWorkspaceFile) -> Void ) { - self.state = state + self.openQuicklyViewModel = state self.onClose = onClose self.openFile = openFile } @@ -37,28 +37,31 @@ struct QuickOpenView: View { SearchPanelView( title: "Open Quickly", image: Image(systemName: "magnifyingglass"), - options: $state.openQuicklyFiles, - text: $state.openQuicklyQuery, + options: $openQuicklyViewModel.searchResults, + text: $openQuicklyViewModel.query, optionRowHeight: 40 - ) { file in - QuickOpenItem(baseDirectory: state.fileURL, fileURL: file) - } preview: { fileURL in - QuickOpenPreviewView(item: CEWorkspaceFile(url: fileURL)) - } onRowClick: { fileURL in + ) { searchResult in + OpenQuicklyListItemView( + baseDirectory: openQuicklyViewModel.fileURL, + searchResult: searchResult + ) + } preview: { searchResult in + OpenQuicklyPreviewView(item: CEWorkspaceFile(url: searchResult.fileURL)) + } onRowClick: { searchResult in guard let file = workspace.workspaceFileManager?.getFile( - fileURL.relativePath, + searchResult.fileURL.relativePath, createIfNotFound: true ) else { return } openFile(file) - state.openQuicklyQuery = "" + openQuicklyViewModel.query = "" onClose() } onClose: { onClose() } - .onReceive(state.$openQuicklyQuery.debounce(for: 0.2, scheduler: DispatchQueue.main)) { _ in - state.fetchOpenQuickly() + .onReceive(openQuicklyViewModel.$query.debounce(for: 0.2, scheduler: DispatchQueue.main)) { _ in + openQuicklyViewModel.fetchResults() } } } diff --git a/CodeEdit/Features/QuickOpen/ViewModels/QuickOpenViewModel.swift b/CodeEdit/Features/QuickOpen/ViewModels/QuickOpenViewModel.swift deleted file mode 100644 index 2c2e121608..0000000000 --- a/CodeEdit/Features/QuickOpen/ViewModels/QuickOpenViewModel.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// QuickOpenState.swift -// CodeEditModules/QuickOpen -// -// Created by Marco Carnevali on 05/04/22. -// - -import Combine -import Foundation -import CollectionConcurrencyKit - -final class QuickOpenViewModel: ObservableObject { - @Published var openQuicklyQuery: String = "" - @Published var openQuicklyFiles: [URL] = [] - @Published var isShowingOpenQuicklyFiles: Bool = false - - let fileURL: URL - var runningTask: Task? - - init(fileURL: URL) { - self.fileURL = fileURL - } - - func fetchOpenQuickly() { - let startTime = Date() - guard openQuicklyQuery != "" else { - openQuicklyFiles = [] - self.isShowingOpenQuicklyFiles = !openQuicklyFiles.isEmpty - return - } - - runningTask?.cancel() - runningTask = Task.detached(priority: .userInitiated) { - let enumerator = FileManager.default.enumerator( - at: self.fileURL, - includingPropertiesForKeys: [ - .isRegularFileKey - ], - options: [ - .skipsPackageDescendants - ] - ) - if let filePaths = enumerator?.allObjects as? [URL] { - guard !Task.isCancelled else { return } - /// removes all filePaths which aren't regular files - let filteredFiles = filePaths.filter { url in - do { - let values = try url.resourceValues(forKeys: [.isRegularFileKey]) - return (values.isRegularFile ?? false) - } catch { - return false - } - } - - let files = await filteredFiles.fuzzySearch( - query: self.openQuicklyQuery.trimmingCharacters(in: .whitespaces) - ).concurrentMap { $0.item } - - guard !Task.isCancelled else { return } - await MainActor.run { - self.openQuicklyFiles = files - self.isShowingOpenQuicklyFiles = !self.openQuicklyFiles.isEmpty - print("Duration: \(Date().timeIntervalSince(startTime))") - } - } - } - } -} diff --git a/CodeEdit/Features/Search/Views/QuickSearchResultLabel.swift b/CodeEdit/Features/Search/Views/QuickSearchResultLabel.swift new file mode 100644 index 0000000000..3ed438ac5f --- /dev/null +++ b/CodeEdit/Features/Search/Views/QuickSearchResultLabel.swift @@ -0,0 +1,51 @@ +// +// QuickSearchResultLabel.swift +// CodeEdit +// +// Created by Paul Ebose on 2024/7/2. +// + +import SwiftUI + +/// Implementation of command palette entity. While swiftui does not allow to use NSMutableAttributeStrings, +/// the only way to fallback to UIKit and have NSViewRepresentable to be a bridge between UIKit and SwiftUI. +/// Highlights currently entered text query +struct QuickSearchResultLabel: NSViewRepresentable { + let labelName: String + let charactersToHighlight: [NSRange] + var nsLabelName: NSAttributedString? + + public func makeNSView(context: Context) -> some NSTextField { + let label = NSTextField(wrappingLabelWithString: labelName) + label.translatesAutoresizingMaskIntoConstraints = false + label.drawsBackground = false + label.textColor = .labelColor + label.isEditable = false + label.isSelectable = false + label.font = .labelFont(ofSize: 13) + label.allowsDefaultTighteningForTruncation = false + label.cell?.truncatesLastVisibleLine = true + label.cell?.wraps = true + label.maximumNumberOfLines = 1 + label.attributedStringValue = nsLabelName ?? highlight() + return label + } + + func highlight() -> NSAttributedString { + let attribText = NSMutableAttributedString(string: self.labelName) + for range in charactersToHighlight { + attribText.addAttribute(.foregroundColor, value: NSColor.controlTextColor, range: range) + attribText.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize), range: range) + } + return attribText + } + + func updateNSView(_ nsView: NSViewType, context: Context) { + nsView.textColor = if nsLabelName == nil && charactersToHighlight.isEmpty { + .controlTextColor + } else { + .secondaryLabelColor + } + nsView.attributedStringValue = nsLabelName ?? highlight() + } +} diff --git a/Documentation.docc/Documentation.md b/Documentation.docc/Documentation.md index 5825064d5c..bdc91525fc 100644 --- a/Documentation.docc/Documentation.md +++ b/Documentation.docc/Documentation.md @@ -67,12 +67,12 @@ - ``LSPClient`` -### QuickOpen +### OpenQuickly -- ``QuickOpenView`` -- ``QuickOpenItem`` -- ``QuickOpenViewModel`` -- ``QuickOpenPreviewView`` +- ``OpenQuicklyView`` +- ``OpenQuicklyListItemView`` +- ``OpenQuicklyViewModel`` +- ``OpenQuicklyPreviewView`` ### Search