Skip to content

Commit 87ae60d

Browse files
authored
Merge pull request #119 from Flow-Launcher/add_filecontent_search
Add file content search for Explorer plugin
2 parents e4979ab + f44364f commit 87ae60d

File tree

15 files changed

+367
-36
lines changed

15 files changed

+367
-36
lines changed

Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ public void UpdatePluginSettings(List<PluginMetadata> metadatas)
1515
if (Plugins.ContainsKey(metadata.ID))
1616
{
1717
var settings = Plugins[metadata.ID];
18+
19+
// TODO: Remove. This is one off for 1.2.0 release.
20+
// Introduced a new action keyword in Explorer, so need to update plugin setting in the UserData folder.
21+
// This kind of plugin meta update should be handled by a dedicated method trigger by version bump.
22+
if (metadata.ID == "572be03c74c642baae319fc283e561a8" && metadata.ActionKeywords.Count != settings.ActionKeywords.Count)
23+
settings.ActionKeywords = metadata.ActionKeywords;
24+
25+
if (string.IsNullOrEmpty(settings.Version))
26+
settings.Version = metadata.Version;
27+
1828
if (settings.ActionKeywords?.Count > 0)
1929
{
2030
metadata.ActionKeywords = settings.ActionKeywords;
@@ -28,6 +38,7 @@ public void UpdatePluginSettings(List<PluginMetadata> metadatas)
2838
{
2939
ID = metadata.ID,
3040
Name = metadata.Name,
41+
Version = metadata.Version,
3142
ActionKeywords = metadata.ActionKeywords,
3243
Disabled = metadata.Disabled
3344
};
@@ -39,6 +50,7 @@ public class Plugin
3950
{
4051
public string ID { get; set; }
4152
public string Name { get; set; }
53+
public string Version { get; set; }
4254
public List<string> ActionKeywords { get; set; } // a reference of the action keywords from plugin manager
4355

4456
/// <summary>

Flow.Launcher.Test/Plugins/ExplorerTest.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,55 @@ public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMe
184184
$"Actual number of results is {results.Count} {Environment.NewLine}");
185185
}
186186

187+
[TestCase(@"some words", @"FREETEXT('some words')")]
188+
public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString(
189+
string querySearchString, string expectedString)
190+
{
191+
// Given
192+
var queryConstructor = new QueryConstructor(new Settings());
193+
194+
//When
195+
var resultString = queryConstructor.QueryWhereRestrictionsForFileContentSearch(querySearchString);
196+
197+
// Then
198+
Assert.IsTrue(resultString == expectedString,
199+
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
200+
$"Actual string was: {resultString}{Environment.NewLine}");
201+
}
202+
203+
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType " +
204+
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:'")]
205+
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
206+
string userSearchString, string expectedString)
207+
{
208+
// Given
209+
var queryConstructor = new QueryConstructor(new Settings());
210+
211+
//When
212+
var resultString = queryConstructor.QueryForFileContentSearch(userSearchString);
213+
214+
// Then
215+
Assert.IsTrue(resultString == expectedString,
216+
$"Expected query string: {expectedString}{Environment.NewLine} " +
217+
$"Actual string was: {resultString}{Environment.NewLine}");
218+
}
219+
220+
public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileContentSearchRequiredShouldReturnTrue()
221+
{
222+
// Given
223+
var query = new Query { ActionKeyword = "doc:", Search = "search term" };
224+
225+
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
226+
227+
// When
228+
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
229+
230+
// Then
231+
Assert.IsTrue(result,
232+
$"Expected True for file content search. {Environment.NewLine} " +
233+
$"Actual result was: {result}{Environment.NewLine}");
234+
}
235+
187236
[TestCase(@"c:\\", false)]
188237
[TestCase(@"i:\", true)]
189238
[TestCase(@"\c:\", false)]

Flow.Launcher/Languages/en.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@
111111
<system:String x:Key="done">Done</system:String>
112112
<system:String x:Key="cannotFindSpecifiedPlugin">Can't find specified plugin</system:String>
113113
<system:String x:Key="newActionKeywordsCannotBeEmpty">New Action Keyword can't be empty</system:String>
114-
<system:String x:Key="newActionKeywordsHasBeenAssigned">New Action Keywords have been assigned to another plugin, please assign other new action keyword</system:String>
114+
<system:String x:Key="newActionKeywordsHasBeenAssigned">This new Action Keyword is already assigned to another plugin, please choose a different one</system:String>
115115
<system:String x:Key="success">Success</system:String>
116116
<system:String x:Key="actionkeyword_tips">Use * if you don't want to specify an action keyword</system:String>
117117

Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
</ItemGroup>
105105

106106
<ItemGroup>
107+
<ProjectReference Include="..\..\Flow.Launcher.Core\Flow.Launcher.Core.csproj" />
107108
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
108109
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
109110
</ItemGroup>

Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
1+
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
22
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
33
xmlns:system="clr-namespace:System;assembly=mscorlib">
44

55
<!--Dialogues-->
6+
<system:String x:Key="plugin_explorer_make_selection_warning">Please make a selection first</system:String>
67
<system:String x:Key="plugin_explorer_select_folder_link_warning">Please select a folder link</system:String>
78
<system:String x:Key="plugin_explorer_delete_folder_link">Are you sure you want to delete {0}?</system:String>
89
<system:String x:Key="plugin_explorer_deletefilefolderconfirm">Are you sure you want to permanently delete this {0}?</system:String>
@@ -13,10 +14,12 @@
1314
<system:String x:Key="plugin_explorer_delete">Delete</system:String>
1415
<system:String x:Key="plugin_explorer_edit">Edit</system:String>
1516
<system:String x:Key="plugin_explorer_add">Add</system:String>
17+
<system:String x:Key="plugin_explorer_manageactionkeywords_header">Customise Action Keywords</system:String>
1618
<system:String x:Key="plugin_explorer_quickfolderaccess_header">Quick Folder Access Paths</system:String>
1719
<system:String x:Key="plugin_explorer_indexsearchexcludedpaths_header">Index Search Excluded Paths</system:String>
1820
<system:String x:Key="plugin_explorer_manageindexoptions">Indexing Options</system:String>
19-
21+
<system:String x:Key="plugin_explorer_actionkeywordview_search">Search Activation:</system:String>
22+
<system:String x:Key="plugin_explorer_actionkeywordview_filecontentsearch">File Content Search:</system:String>
2023

2124
<!--Plugin Infos-->
2225
<system:String x:Key="plugin_explorer_plugin_name">Explorer</system:String>

Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@ internal List<Result> Search(Query query)
3636

3737
var quickFolderLinks = quickFolderAccess.FolderList(query, settings.QuickFolderAccessLinks, context);
3838

39-
if (quickFolderLinks.Count > 0)
39+
if (quickFolderLinks.Count > 0 && query.ActionKeyword == settings.SearchActionKeyword)
4040
return quickFolderLinks;
4141

4242
if (string.IsNullOrEmpty(querySearch))
4343
return results;
4444

45+
if (IsFileContentSearch(query.ActionKeyword))
46+
return WindowsIndexFileContentSearch(query, querySearch);
47+
4548
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
4649

4750
if (isEnvironmentVariable)
@@ -74,6 +77,24 @@ internal List<Result> Search(Query query)
7477
return results;
7578
}
7679

80+
private List<Result> WindowsIndexFileContentSearch(Query query, string querySearchString)
81+
{
82+
var queryConstructor = new QueryConstructor(settings);
83+
84+
if (string.IsNullOrEmpty(querySearchString))
85+
return new List<Result>();
86+
87+
return indexSearch.WindowsIndexSearch(querySearchString,
88+
queryConstructor.CreateQueryHelper().ConnectionString,
89+
queryConstructor.QueryForFileContentSearch,
90+
query);
91+
}
92+
93+
public bool IsFileContentSearch(string actionKeyword)
94+
{
95+
return actionKeyword == settings.FileContentSearchActionKeyword;
96+
}
97+
7798
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch)
7899
{
79100
var directoryInfoSearch = new DirectoryInfoSearch(context);

Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,23 @@ public string QueryWhereRestrictionsForAllFilesAndFoldersSearch()
117117
{
118118
return $"scope='file:'";
119119
}
120+
121+
///<summary>
122+
/// Search will be performed on all indexed file contents for the specified search keywords.
123+
///</summary>
124+
public string QueryForFileContentSearch(string userSearchString)
125+
{
126+
string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE ";
127+
128+
return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch();
129+
}
130+
131+
///<summary>
132+
/// Set the required WHERE clause restriction to search within file content.
133+
///</summary>
134+
public string QueryWhereRestrictionsForFileContentSearch(string searchQuery)
135+
{
136+
return $"FREETEXT('{searchQuery}')";
137+
}
120138
}
121139
}

Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,11 @@ public class Settings
1717

1818
[JsonProperty]
1919
public List<FolderLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new List<FolderLink>();
20+
21+
[JsonProperty]
22+
public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign;
23+
24+
[JsonProperty]
25+
public string FileContentSearchActionKeyword { get; set; } = "doc:";
2026
}
2127
}

Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Flow.Launcher.Infrastructure.Storage;
1+
using Flow.Launcher.Core.Plugin;
2+
using Flow.Launcher.Infrastructure.Storage;
23
using Flow.Launcher.Plugin.Explorer.Search;
34
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
45
using System.Diagnostics;
@@ -40,5 +41,18 @@ internal void OpenWindowsIndexingOptions()
4041

4142
Process.Start(psi);
4243
}
44+
45+
internal void UpdateActionKeyword(string newActionKeyword, string oldActionKeyword)
46+
{
47+
PluginManager.ReplaceActionKeyword(Context.CurrentPluginMetadata.ID, oldActionKeyword, newActionKeyword);
48+
49+
if (Settings.FileContentSearchActionKeyword == oldActionKeyword)
50+
Settings.FileContentSearchActionKeyword = newActionKeyword;
51+
52+
if (Settings.SearchActionKeyword == oldActionKeyword)
53+
Settings.SearchActionKeyword = newActionKeyword;
54+
}
55+
56+
internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
4357
}
4458
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Window x:Class="Flow.Launcher.Plugin.Explorer.Views.ActionKeywordSetting"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:local="clr-namespace:Flow.Launcher.Plugin.Explorer.Views"
7+
mc:Ignorable="d"
8+
ResizeMode="NoResize"
9+
WindowStartupLocation="CenterScreen"
10+
Title="Action Keyword Setting" Height="200" Width="500">
11+
<Grid>
12+
<Grid.RowDefinitions>
13+
<RowDefinition />
14+
<RowDefinition />
15+
</Grid.RowDefinitions>
16+
<Grid.ColumnDefinitions>
17+
<ColumnDefinition Width="180" />
18+
<ColumnDefinition />
19+
</Grid.ColumnDefinitions>
20+
<TextBlock Margin="20 10 10 10" FontSize="14" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
21+
HorizontalAlignment="Left" Text="Current Action Keyword:" />
22+
<TextBox Name="txtCurrentActionKeyword"
23+
Margin="10" Grid.Row="0" Width="105" Grid.Column="1"
24+
VerticalAlignment="Center"
25+
HorizontalAlignment="Left" />
26+
27+
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="1" Grid.Column="1">
28+
<Button Click="OnConfirmButtonClick"
29+
Margin="10 0 10 0" Width="80" Height="35"
30+
Content="OK" />
31+
<Button Click="OnCancelButtonClick"
32+
Margin="10 0 10 0" Width="80" Height="35"
33+
Content="Cancel" />
34+
</StackPanel>
35+
</Grid>
36+
</Window>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using Flow.Launcher.Plugin.Explorer.ViewModels;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Windows;
7+
using System.Windows.Controls;
8+
using System.Windows.Data;
9+
using System.Windows.Documents;
10+
using System.Windows.Input;
11+
using System.Windows.Media;
12+
using System.Windows.Media.Imaging;
13+
using System.Windows.Shapes;
14+
15+
namespace Flow.Launcher.Plugin.Explorer.Views
16+
{
17+
/// <summary>
18+
/// Interaction logic for ActionKeywordSetting.xaml
19+
/// </summary>
20+
public partial class ActionKeywordSetting : Window
21+
{
22+
private SettingsViewModel settingsViewModel;
23+
24+
private ActionKeywordView currentActionKeyword;
25+
26+
private List<ActionKeywordView> actionKeywordListView;
27+
28+
public ActionKeywordSetting(SettingsViewModel settingsViewModel, List<ActionKeywordView> actionKeywordListView, ActionKeywordView selectedActionKeyword)
29+
{
30+
InitializeComponent();
31+
32+
this.settingsViewModel = settingsViewModel;
33+
34+
currentActionKeyword = selectedActionKeyword;
35+
36+
txtCurrentActionKeyword.Text = selectedActionKeyword.Keyword;
37+
38+
this.actionKeywordListView = actionKeywordListView;
39+
}
40+
41+
private void OnConfirmButtonClick(object sender, RoutedEventArgs e)
42+
{
43+
var newActionKeyword = txtCurrentActionKeyword.Text;
44+
45+
if (string.IsNullOrEmpty(newActionKeyword))
46+
return;
47+
48+
if (newActionKeyword == currentActionKeyword.Keyword)
49+
{
50+
Close();
51+
52+
return;
53+
}
54+
55+
if(!settingsViewModel.IsActionKeywordAlreadyAssigned(newActionKeyword))
56+
{
57+
settingsViewModel.UpdateActionKeyword(newActionKeyword, currentActionKeyword.Keyword);
58+
59+
actionKeywordListView.Where(x => x.Description == currentActionKeyword.Description).FirstOrDefault().Keyword = newActionKeyword;
60+
61+
Close();
62+
63+
return;
64+
}
65+
66+
MessageBox.Show(settingsViewModel.Context.API.GetTranslation("newActionKeywordsHasBeenAssigned"));
67+
}
68+
69+
private void OnCancelButtonClick(object sender, RoutedEventArgs e)
70+
{
71+
Close();
72+
73+
return;
74+
}
75+
}
76+
}

Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<UserControl x:Class="Flow.Launcher.Plugin.Explorer.Views.ExplorerSettings"
1+
<UserControl x:Class="Flow.Launcher.Plugin.Explorer.Views.ExplorerSettings"
22
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -17,6 +17,16 @@
1717
Text="{Binding Nickname, Mode=OneTime}"
1818
Margin="0,5,0,5" />
1919
</DataTemplate>
20+
<DataTemplate x:Key="ListViewActionKeywords">
21+
<Grid>
22+
<TextBlock
23+
Text="{Binding Description, Mode=OneTime}"
24+
Margin="0,5,0,0" />
25+
<TextBlock
26+
Text="{Binding Keyword, Mode=OneTime}"
27+
Margin="150,5,0,0" />
28+
</Grid>
29+
</DataTemplate>
2030
</UserControl.Resources>
2131
<Grid Margin="10">
2232
<Grid.RowDefinitions>
@@ -25,16 +35,22 @@
2535
</Grid.RowDefinitions>
2636
<ScrollViewer Margin="20 35 0 0" HorizontalScrollBarVisibility="Hidden" Grid.Row="0" VerticalScrollBarVisibility="Auto">
2737
<StackPanel>
38+
<Expander Name="expActionKeywords" Header="{DynamicResource plugin_explorer_manageactionkeywords_header}"
39+
Expanded="expActionKeywords_Click" Collapsed="expActionKeywords_Collapsed">
40+
<ListView x:Name="lbxActionKeywords"
41+
ItemTemplate="{StaticResource ListViewActionKeywords}"/>
42+
</Expander>
2843
<Expander Name="expFolderLinks" Header="{DynamicResource plugin_explorer_quickfolderaccess_header}"
29-
Expanded="expFolderLinks_Click" Collapsed="expFolderLinks_Click">
44+
Expanded="expFolderLinks_Click" Collapsed="expFolderLinks_Collapsed"
45+
Margin="0 10 0 0">
3046
<ListView
3147
x:Name="lbxFolderLinks" AllowDrop="True"
3248
Drop="lbxFolders_Drop"
3349
DragEnter="lbxFolders_DragEnter"
3450
ItemTemplate="{StaticResource ListViewTemplateFolderLinks}"/>
3551
</Expander>
3652
<Expander x:Name="expExcludedPaths" Header="{DynamicResource plugin_explorer_indexsearchexcludedpaths_header}"
37-
Expanded="expExcludedPaths_Click" Collapsed="expExcludedPaths_Click"
53+
Expanded="expExcludedPaths_Click"
3854
Margin="0 10 0 0">
3955
<ListView
4056
x:Name="lbxExcludedPaths" AllowDrop="True"

0 commit comments

Comments
 (0)