Queries
What Are Queries?
Queries are a way to search for information in your plugin. They are the main way users interact with your plugin, and they are the main way your plugin can provide information to the user.
Responding to Queries
To respond to a query, you need to define a method in your plugin that is marked with the [Search]
attribute.
That method must return data to display.
For now, we’ll just return a string to ensure that everything’s working.
With this code your plugin will always return one result with the title “Hello,
world!” and the icon that is specified in your plugin.json
file.
public class MyPlugin : ExtendedPlugin{ [Search] public string MyQuery() { return "Hello, world!"; }}
Using Search Text
Let’s say you want to return a different result based on the search text.
You can do that by adding an ExtendedQuery
parameter to your method.
ExtendedQuery
has two properties:
string Search
— the text that the user entered after the action keywordstring[] SearchTerms
— the text that the user entered after the action keyword, split into an array of words
[Search]public string MyQuery(ExtendedQuery query){ return $"Hello, {query.Search}!";}
Returning Multiple Results
If you want to return multiple results, you can return List
, IEnumerable
, or array instead:
[Search]public List<string> MyQuery(ExtendedQuery query){ return new List<string> { $"Hey, {query.Search}!", $"Hello, {query.Search}!", };}
Returning More Complex Results
If you want to return more complex results, you can return ExtendedResult
instead:
[Search]public ExtendedResult MyQuery(ExtendedQuery query){ return new ExtendedResult { Title = $"Hello, {query.Search}!", Subtitle = "Welcome to Flow Launcher!", };}
This works with List
, IEnumerable
, and arrays as well:
[Search]public List<ExtendedResult> MyQuery(ExtendedQuery query){ return new List<ExtendedResult> { new ExtendedResult { Title = $"Hey, {query.Search}!", Subtitle = "Welcome to Flow Launcher!", }, new ExtendedResult { Title = $"Hello, {query.Search}!", Subtitle = "Welcome to Flow Launcher!", }, };}
Different Commands
If you want to return different results based on the search text,
you can define multiple methods with the [Search]
attribute.
[Search]public string EmptyQuery(){ return "Type 'name' or 'weather'"}
[Search(equalTo: "name")]public string EmptyNameQuery(){ return "Type a name after the word 'name'";}
[Search(startsWith: "name ")]public ExtendedResult NameQuery(ExtendedQuery query){ return new ExtendedResult { Title = $"Hello, {query.Search}!", Subtitle = "Welcome to Flow Launcher!", };}
[Search(equalTo: "weather")]public string EmptyWeatherQuery(){ return "Type a city name after the word 'weather'";}
[Search(startsWith: "weather ")]public string WeatherQuery(ExtendedQuery query){ return $"Probably sunny in {query.Search}, idk";}
Case Sensitivity
By default, the [Search]
attribute is case-insensitive.
If you want to make it case-sensitive, you can set the stringComparison
property to something else, like this:
[Search(startsWith: "name ", stringComparison: StringComparison.CurrentCulture)]public ExtendedResult NameQuery(ExtendedQuery query){ return new ExtendedResult { Title = $"Hello, {query.Search}!", Subtitle = "Welcome to Flow Launcher!", };}
Async Queries
If you need to perform an async operation (such as fetching data from the internet), you can define an async method:
[Search(startsWith: "weather ")]public async Task<string> WeatherQuery(ExtendedQuery query){ await Task.Delay(1000); // Simulate downloading data return $"Probably sunny in {query.Search}, idk";}
Regex Queries
Sometimes simple text matching is not enough. In this case, you can use regular expressions to match the search text.
Let’s write a plugin that will accept two integers and perform a simple operation on them.
You can use the matched groups to extract the numbers from the search text.
To do this,
give your method parameters appropriate types (int
and string
in this case)
and name them _N
where N
is the index of the capturing group.
The first capturing group is available as the parameter called _1
, the second group as _2
, etc.
[Search(regex: @"^(\d+)\s*([+\-*/])\s*(\d+)$")]public string? MyRegexQuery(string _2, int _1, int _3){ return _2 switch { "+" => $"= {_1 + _3}", "-" => $"= {_1 - _3}", "*" => $"= {_1 * _3}", "/" => $"= {_1 / _3}", _ => null, };}
While this does work, it’s not the most readable code when you have to use capturing group indexes as variable names. Let’s improve this by using named capturing groups.
[Search(regex: @"^(?<a>\d+)\s*(?<op>[+\-*/])\s*(?<b>\d+)$")]public string? MyRegexQuery(string op, int a, int b){ return op switch { "+" => $"= {a + b}", "-" => $"= {a - b}", "*" => $"= {a * b}", "/" => $"= {a / b}", _ => null, };}
Functionally, it works exactly the same way, but now the code is much more readable.
Regex Options
Just like the [Search]
attribute with text matching,
regular expressions in this attribute are case-insensitive by default.
You can change this behavior by specifying regexOptions
parameter.
[Search( regex: @"^My name is (?<name>\w+) and I'm (?<age>\d+) years old$", regexOptions: RegexOptions.None)]public string[] MyNumberQuery(string name, int age){ return new string[] { $"Name: {name}", $"Age: {age}", };}
Performing Actions
Often, when the user selects a result, you want to perform an action.
You can do that by returning an ExtendedResult
with the Action
or ActionAsync
property set.
This property must be a function that returns bool
or an async function that returns bool
.
If the return value is true
, Flow Launcher will close the search window.
If it’s false
, the search window will stay open after performing the action.
[Search]public List<ExtendedResult> MyQuery(ExtendedQuery query){ return new List<ExtendedResult> { new ExtendedResult { Title = $"Hey, {query.Search}!", Subtitle = "Welcome to Flow Launcher!", Action = ctx => { Api.ShowMsg("Hey, you clicked me!"); return true; } }, new ExtendedResult { Title = $"Hello, {query.Search}!", Subtitle = "Welcome to Flow Launcher!", ActionAsync = async ctx => { await Task.Delay(1000); // Simulate async action Api.ShowMsg("Hello, you clicked me!"); return true; } }, };}
Nothing changed visually, but now, when you select a result, a message will appear.
Common Action Shortcuts
There are some common actions that you can perform with a single line of code.
ExtendedResult
has special properties for these actions:
string TextToCopyOnCtrlC
— when the user pressesCtrl+C
, this text will be copied to the clipboardstring TextToCopyOnEnter
— when the user selects this result, this text will be copied to the clipboardstring UrlToOpen
— when the user selects this result, this URL will be openedstring UrlToOpenIncognito
— when the user selects this result, this URL will be opened in incognito mode
Only one of these properties can be set at a time.
return new ExtendedResult[]{ new ExtendedResult { Title = "Press Ctrl+C to copy the URL", Subtitle = "https://example.com/", TextToCopyOnCtrlC = "https://example.com/", }, new ExtendedResult { Title = "Press Enter to copy the URL", Subtitle = "https://example.com/", TextToCopyOnEnter = "https://example.com/", }, new ExtendedResult { Title = "Press Enter to open the URL", Subtitle = "https://example.com/", UrlToOpen = "https://example.com/", }, new ExtendedResult { Title = "Press Enter to open the URL in incognito mode", Subtitle = "https://example.com/", UrlToOpenIncognito = "https://example.com/", },};
Context Menus
Context menus are a way to provide additional actions for a result.
In Flow Launcher, you can call a context menu on a result by pressing Shift + Enter
.
If you want to use context menus, you need to do four things:
- Mark your class as
partial
. - Define a method that returns your context menu. Its return type is similar to the return type of the
[Search]
method, i.e., you can return a string, a list of strings, an array, or anything else described above on this page. - Mark this method with the
[ContextMenu]
attribute. - Use this method prefixed with
Create
. If this method’s name doesn’t include the words “context” or “menu”, you will also have to addContextMenu
to the end of the method name.
Let’s go over the code now.
Context Menu Example
Let’s create a plugin that can show a context menu with two options: with the search text uppercased and lowercased.
public partial class MyPlugin : ExtendedPlugin{ [Search(startsWith: "text ")] public ExtendedResult MyQuery(ExtendedQuery query) { return new ExtendedResult { Title = $"Text: {query.Search}", ContextMenu = CreateMyQueryContextMenu(query.Search), }; }
[ContextMenu] public ExtendedResult[] MyQueryContextMenu(string str) { return new ExtendedResult[] { new ExtendedResult { Title = str.ToUpper(), Subtitle = "Uppercase", }, new ExtendedResult { Title = str.ToLower(), Subtitle = "Lowercase", }, }; }}
Multiple Context Menus
You can have multiple different context menus:
[Search(startsWith: "text ")]public ExtendedResult MyQuery(ExtendedQuery query){ return new ExtendedResult { Title = $"Text: {query.Search}", ContextMenu = CreateMyQueryContextMenu(query.Search), };}
[ContextMenu]public ExtendedResult[] MyQueryContextMenu(string str){ return new ExtendedResult[] { new ExtendedResult { Title = str.ToUpper(), Subtitle = "Uppercased", }, new ExtendedResult { Title = str.ToLower(), Subtitle = "Lowercased", }, };}
[Search(regex: @"^(?<number>\d+)$")]public ExtendedResult MyNumberQuery(int number){ return new ExtendedResult { Title = $"Number: {number}", ContextMenu = CreateMyNumberQueryContextMenu(number), };}
[ContextMenu]public ExtendedResult[] MyNumberQueryContextMenu(int number){ return new ExtendedResult[] { new ExtendedResult { Title = $"Square: {number * number}", Subtitle = "Number squared", }, new ExtendedResult { Title = $"Cube: {number * number * number}", Subtitle = "Number cubed", }, };}
Different Context Menus In the Same Query
You can even specify different context menus for different results in the same query:
[Search(regex: @"^My name is (?<name>\w+) and I'm (?<age>\d+) years old$")]public ExtendedResult[] MyQuery(string name, int age){ return new ExtendedResult[] { new ExtendedResult { Title = $"Name: {name}", ContextMenu = CreateNameContextMenu(name), }, new ExtendedResult { Title = $"Age: {age}", ContextMenu = CreateAgeContextMenu(age), } };}
[ContextMenu]public string AgeContextMenu(int number){ return $"In 5 years you will be {number + 5} years old";}
[ContextMenu]public string NameContextMenu(string name){ return $"Hello, {name}!";}