2021-06-16 22:55:44 -03:00
/*
* Copyright ( c ) 2021 , Spencer Dixon < spencercdixon @ gmail . com >
2023-01-07 14:46:34 -03:00
* Copyright ( c ) 2022 - 2023 , the SerenityOS developers .
2021-06-16 22:55:44 -03:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "Providers.h"
2023-01-07 14:46:34 -03:00
# include <AK/Array.h>
2022-12-04 15:02:33 -03:00
# include <AK/DeprecatedString.h>
2022-02-09 16:57:36 -03:00
# include <AK/Error.h>
2022-11-26 12:22:44 -03:00
# include <AK/LexicalPath.h>
2021-06-16 22:55:44 -03:00
# include <AK/QuickSort.h>
2022-02-09 16:57:36 -03:00
# include <AK/Try.h>
2023-01-17 16:59:08 -03:00
# include <LibCore/Debounce.h>
2021-07-22 00:35:04 -03:00
# include <LibCore/LockFile.h>
2022-02-09 16:57:36 -03:00
# include <LibCore/System.h>
2022-11-26 12:22:44 -03:00
# include <LibDesktop/Launcher.h>
# include <LibGUI/Action.h>
2021-06-16 22:55:44 -03:00
# include <LibGUI/Application.h>
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Event.h>
# include <LibGUI/Icon.h>
# include <LibGUI/ImageWidget.h>
# include <LibGUI/Label.h>
2022-11-26 12:22:44 -03:00
# include <LibGUI/Menu.h>
2021-06-16 22:55:44 -03:00
# include <LibGUI/Painter.h>
# include <LibGUI/TextBox.h>
# include <LibGfx/Palette.h>
2022-02-09 16:57:36 -03:00
# include <LibMain/Main.h>
2021-07-09 06:14:57 -03:00
# include <LibThreading/Mutex.h>
2021-07-22 00:35:04 -03:00
# include <string.h>
2021-07-05 17:54:45 -03:00
# include <unistd.h>
2021-06-16 22:55:44 -03:00
namespace Assistant {
struct AppState {
2021-06-29 08:08:21 -03:00
Optional < size_t > selected_index ;
2023-03-06 10:17:01 -03:00
Vector < NonnullRefPtr < Result const > > results ;
2021-06-16 22:55:44 -03:00
size_t visible_result_count { 0 } ;
2021-07-09 06:14:57 -03:00
Threading : : Mutex lock ;
2022-12-04 15:02:33 -03:00
DeprecatedString last_query ;
2021-06-16 22:55:44 -03:00
} ;
2022-11-25 12:34:10 -03:00
class ResultRow final : public GUI : : Button {
2021-06-16 22:55:44 -03:00
C_OBJECT ( ResultRow )
2021-10-31 19:38:04 -03:00
ResultRow ( )
{
set_greedy_for_hits ( true ) ;
2022-11-25 12:34:10 -03:00
set_fixed_height ( 36 ) ;
set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
set_button_style ( Gfx : : ButtonStyle : : Coolbar ) ;
set_focus_policy ( GUI : : FocusPolicy : : NoFocus ) ;
2022-11-26 12:22:44 -03:00
on_context_menu_request = [ this ] ( auto & event ) {
if ( ! m_context_menu ) {
m_context_menu = GUI : : Menu : : construct ( ) ;
2023-02-12 07:40:58 -03:00
if ( LexicalPath path { text ( ) . to_deprecated_string ( ) } ; path . is_absolute ( ) ) {
2023-01-20 16:06:05 -03:00
m_context_menu - > add_action ( GUI : : Action : : create ( " &Show in File Manager " , MUST ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/app-file-manager.png " sv ) ) , [ = ] ( auto & ) {
2022-11-26 12:22:44 -03:00
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( path . dirname ( ) , path . basename ( ) ) ) ;
} ) ) ;
m_context_menu - > add_separator ( ) ;
}
2023-01-20 16:06:05 -03:00
m_context_menu - > add_action ( GUI : : Action : : create ( " &Copy as Text " , MUST ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/edit-copy.png " sv ) ) , [ & ] ( auto & ) {
2023-02-12 07:40:58 -03:00
GUI : : Clipboard : : the ( ) . set_plain_text ( text ( ) ) ;
2022-11-26 12:22:44 -03:00
} ) ) ;
}
m_context_menu - > popup ( event . screen_position ( ) ) ;
} ;
2021-10-31 19:38:04 -03:00
}
2022-11-26 12:22:44 -03:00
RefPtr < GUI : : Menu > m_context_menu ;
2021-06-16 22:55:44 -03:00
} ;
2023-01-07 14:46:34 -03:00
template < size_t ProviderCount >
2021-06-16 22:55:44 -03:00
class Database {
public :
2023-01-07 14:46:34 -03:00
explicit Database ( AppState & state , Array < NonnullRefPtr < Provider > , ProviderCount > & providers )
2021-06-16 22:55:44 -03:00
: m_state ( state )
2023-01-07 14:46:34 -03:00
, m_providers ( providers )
2021-06-16 22:55:44 -03:00
{
}
2023-09-28 03:05:51 -03:00
Function < void ( Vector < NonnullRefPtr < Result const > > & & ) > on_new_results ;
2021-06-16 22:55:44 -03:00
2022-12-04 15:02:33 -03:00
void search ( DeprecatedString const & query )
2021-06-16 22:55:44 -03:00
{
2023-01-07 14:46:34 -03:00
auto should_display_precached_results = false ;
for ( size_t i = 0 ; i < ProviderCount ; + + i ) {
auto & result_array = m_result_cache . ensure ( query ) ;
if ( result_array . at ( i ) = = nullptr ) {
m_providers [ i ] - > query ( query , [ this , query , i ] ( auto results ) {
{
Threading : : MutexLocker db_locker ( m_mutex ) ;
auto & result_array = m_result_cache . ensure ( query ) ;
if ( result_array . at ( i ) ! = nullptr )
return ;
2023-03-06 10:17:01 -03:00
result_array [ i ] = make < Vector < NonnullRefPtr < Result > > > ( results ) ;
2023-01-07 14:46:34 -03:00
}
on_result_cache_updated ( ) ;
} ) ;
} else {
should_display_precached_results = true ;
}
2021-07-03 15:46:33 -03:00
}
2023-01-07 14:46:34 -03:00
if ( should_display_precached_results )
on_result_cache_updated ( ) ;
2021-06-16 22:55:44 -03:00
}
private :
2023-01-07 14:46:34 -03:00
void on_result_cache_updated ( )
2021-06-16 22:55:44 -03:00
{
2021-07-09 06:14:57 -03:00
Threading : : MutexLocker state_locker ( m_state . lock ) ;
2021-06-16 22:55:44 -03:00
auto new_results = m_result_cache . find ( m_state . last_query ) ;
if ( new_results = = m_result_cache . end ( ) )
return ;
2023-02-19 20:47:48 -03:00
Vector < NonnullRefPtr < Result const > > all_results ;
2023-01-07 14:46:34 -03:00
for ( auto const & results_for_provider : new_results - > value ) {
if ( results_for_provider = = nullptr )
continue ;
for ( auto const & result : * results_for_provider ) {
all_results . append ( result ) ;
}
}
2021-07-01 14:08:14 -03:00
2023-01-07 14:46:34 -03:00
dual_pivot_quick_sort ( all_results , 0 , static_cast < int > ( all_results . size ( ) - 1 ) , [ ] ( auto & a , auto & b ) {
2021-06-16 22:55:44 -03:00
return a - > score ( ) > b - > score ( ) ;
} ) ;
2023-09-28 03:05:51 -03:00
on_new_results ( move ( all_results ) ) ;
2021-06-16 22:55:44 -03:00
}
AppState & m_state ;
2023-01-07 14:46:34 -03:00
Array < NonnullRefPtr < Provider > , ProviderCount > m_providers ;
2021-06-16 22:55:44 -03:00
2021-07-09 06:14:57 -03:00
Threading : : Mutex m_mutex ;
2023-03-06 10:17:01 -03:00
HashMap < DeprecatedString , Array < OwnPtr < Vector < NonnullRefPtr < Result > > > , ProviderCount > > m_result_cache ;
2021-06-16 22:55:44 -03:00
} ;
}
2022-02-09 16:57:36 -03:00
ErrorOr < int > serenity_main ( Main : : Arguments arguments )
2021-06-16 22:55:44 -03:00
{
2022-04-03 20:13:41 -03:00
TRY ( Core : : System : : pledge ( " stdio recvfd sendfd rpath cpath unix proc exec thread " ) ) ;
2021-06-16 22:55:44 -03:00
2021-07-22 00:35:04 -03:00
Core : : LockFile lockfile ( " /tmp/lock/assistant.lock " ) ;
if ( ! lockfile . is_held ( ) ) {
if ( lockfile . error_code ( ) ) {
warnln ( " Core::LockFile: {} " , strerror ( lockfile . error_code ( ) ) ) ;
return 1 ;
}
// Another assistant is open, so exit silently.
return 0 ;
}
2023-05-05 01:24:53 -03:00
auto app = TRY ( GUI : : Application : : create ( arguments ) ) ;
2021-06-16 22:55:44 -03:00
auto window = GUI : : Window : : construct ( ) ;
2021-06-28 22:03:37 -03:00
window - > set_minimizable ( false ) ;
2021-06-16 22:55:44 -03:00
Assistant : : AppState app_state ;
2023-01-07 14:46:34 -03:00
Array < NonnullRefPtr < Assistant : : Provider > , 5 > providers = {
make_ref_counted < Assistant : : AppProvider > ( ) ,
make_ref_counted < Assistant : : CalculatorProvider > ( ) ,
make_ref_counted < Assistant : : TerminalProvider > ( ) ,
make_ref_counted < Assistant : : URLProvider > ( ) ,
make_ref_counted < Assistant : : FileProvider > ( )
} ;
Assistant : : Database db { app_state , providers } ;
2021-06-16 22:55:44 -03:00
2023-09-18 21:13:48 -03:00
auto container = window - > set_main_widget < GUI : : Frame > ( ) ;
2023-01-06 13:48:37 -03:00
container - > set_fill_with_background_color ( true ) ;
2023-04-29 17:47:52 -03:00
container - > set_frame_style ( Gfx : : FrameStyle : : Window ) ;
2023-02-16 18:07:06 -03:00
container - > set_layout < GUI : : VerticalBoxLayout > ( 8 ) ;
2021-06-16 22:55:44 -03:00
2023-01-06 13:48:37 -03:00
auto & text_box = container - > add < GUI : : TextBox > ( ) ;
auto & results_container = container - > add < GUI : : Widget > ( ) ;
2023-02-16 18:07:06 -03:00
results_container . set_layout < GUI : : VerticalBoxLayout > ( ) ;
2021-06-16 22:55:44 -03:00
auto mark_selected_item = [ & ] ( ) {
for ( size_t i = 0 ; i < app_state . visible_result_count ; + + i ) {
2021-06-30 06:44:52 -03:00
auto & row = static_cast < Assistant : : ResultRow & > ( results_container . child_widgets ( ) [ i ] ) ;
2022-11-25 12:34:10 -03:00
row . set_font_weight ( i = = app_state . selected_index ? 700 : 400 ) ;
2021-06-16 22:55:44 -03:00
}
} ;
2023-06-14 10:54:46 -03:00
text_box . on_change = Core : : debounce ( 5 , [ & ] ( ) {
2021-06-16 22:55:44 -03:00
{
2021-07-09 06:14:57 -03:00
Threading : : MutexLocker locker ( app_state . lock ) ;
2021-06-16 22:55:44 -03:00
if ( app_state . last_query = = text_box . text ( ) )
return ;
app_state . last_query = text_box . text ( ) ;
}
db . search ( text_box . text ( ) ) ;
2023-06-14 10:54:46 -03:00
} ) ;
2021-06-16 22:55:44 -03:00
text_box . on_return_pressed = [ & ] ( ) {
2021-06-29 08:08:21 -03:00
if ( ! app_state . selected_index . has_value ( ) )
return ;
2021-09-07 16:35:36 -03:00
lockfile . release ( ) ;
2023-03-06 10:17:01 -03:00
app_state . results [ app_state . selected_index . value ( ) ] - > activate ( ) ;
2021-08-03 09:05:07 -03:00
GUI : : Application : : the ( ) - > quit ( ) ;
2021-06-16 22:55:44 -03:00
} ;
text_box . on_up_pressed = [ & ] ( ) {
2021-06-29 08:08:21 -03:00
if ( ! app_state . visible_result_count )
return ;
auto new_selected_index = app_state . selected_index . value_or ( 0 ) ;
if ( new_selected_index = = 0 )
new_selected_index = app_state . visible_result_count - 1 ;
else if ( new_selected_index > 0 )
new_selected_index - = 1 ;
2021-06-16 22:55:44 -03:00
2021-06-29 08:08:21 -03:00
app_state . selected_index = new_selected_index ;
2021-06-16 22:55:44 -03:00
mark_selected_item ( ) ;
} ;
text_box . on_down_pressed = [ & ] ( ) {
2021-06-29 08:08:21 -03:00
if ( ! app_state . visible_result_count )
return ;
auto new_selected_index = app_state . selected_index . value_or ( 0 ) ;
if ( ( app_state . visible_result_count - 1 ) = = new_selected_index )
new_selected_index = 0 ;
else if ( app_state . visible_result_count > new_selected_index )
new_selected_index + = 1 ;
2021-06-16 22:55:44 -03:00
2021-06-29 08:08:21 -03:00
app_state . selected_index = new_selected_index ;
2021-06-16 22:55:44 -03:00
mark_selected_item ( ) ;
} ;
text_box . on_escape_pressed = [ ] ( ) {
2021-08-03 09:05:07 -03:00
GUI : : Application : : the ( ) - > quit ( ) ;
2021-06-16 22:55:44 -03:00
} ;
2021-08-04 17:28:11 -03:00
window - > on_active_window_change = [ ] ( bool is_active_window ) {
if ( ! is_active_window )
GUI : : Application : : the ( ) - > quit ( ) ;
} ;
2021-06-16 22:55:44 -03:00
2023-01-11 13:31:10 -03:00
auto update_ui_timer = TRY ( Core : : Timer : : create_single_shot ( 10 , [ & ] {
2021-06-16 22:55:44 -03:00
results_container . remove_all_children ( ) ;
2023-02-16 18:07:06 -03:00
results_container . layout ( ) - > set_margins ( app_state . visible_result_count ? GUI : : Margins { 4 , 0 , 0 , 0 } : GUI : : Margins { 0 } ) ;
2021-06-16 22:55:44 -03:00
for ( size_t i = 0 ; i < app_state . visible_result_count ; + + i ) {
2021-07-01 14:08:14 -03:00
auto & result = app_state . results [ i ] ;
2021-06-16 22:55:44 -03:00
auto & match = results_container . add < Assistant : : ResultRow > ( ) ;
2023-03-06 10:17:01 -03:00
match . set_icon ( result - > bitmap ( ) ) ;
match . set_text ( String : : from_deprecated_string ( result - > title ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2023-08-01 15:31:54 -03:00
match . set_tooltip_deprecated ( result - > tooltip ( ) ) ;
2022-11-25 12:34:10 -03:00
match . on_click = [ & result ] ( auto ) {
2023-03-06 10:17:01 -03:00
result - > activate ( ) ;
2021-08-03 09:05:07 -03:00
GUI : : Application : : the ( ) - > quit ( ) ;
2021-06-16 22:55:44 -03:00
} ;
}
mark_selected_item ( ) ;
2022-11-25 12:40:33 -03:00
Core : : deferred_invoke ( [ & ] { window - > resize ( GUI : : Desktop : : the ( ) . rect ( ) . width ( ) / 3 , { } ) ; } ) ;
2023-01-11 13:31:10 -03:00
} ) ) ;
2021-08-14 17:35:38 -03:00
db . on_new_results = [ & ] ( auto results ) {
2023-09-28 03:03:22 -03:00
if ( results . is_empty ( ) ) {
2021-08-14 17:35:38 -03:00
app_state . selected_index = { } ;
2023-09-28 03:03:22 -03:00
} else if ( app_state . selected_index . has_value ( ) ) {
auto current_selected_result = app_state . results [ app_state . selected_index . value ( ) ] ;
auto new_selected_index = results . find_first_index ( current_selected_result ) . value_or ( 0 ) ;
if ( new_selected_index > = Assistant : : MAX_SEARCH_RESULTS ) {
new_selected_index = 0 ;
}
app_state . selected_index = new_selected_index ;
} else {
2021-08-14 17:35:38 -03:00
app_state . selected_index = 0 ;
2023-09-28 03:03:22 -03:00
}
2021-08-14 17:35:38 -03:00
app_state . results = results ;
2023-04-12 13:38:08 -03:00
app_state . visible_result_count = min ( results . size ( ) , Assistant : : MAX_SEARCH_RESULTS ) ;
2021-08-14 17:35:38 -03:00
update_ui_timer - > restart ( ) ;
2021-06-16 22:55:44 -03:00
} ;
2022-11-17 12:34:31 -03:00
window - > set_window_type ( GUI : : WindowType : : Popup ) ;
2021-07-04 18:11:06 -03:00
window - > set_forced_shadow ( true ) ;
2022-11-25 12:40:33 -03:00
window - > resize ( GUI : : Desktop : : the ( ) . rect ( ) . width ( ) / 3 , { } ) ;
2021-06-16 22:55:44 -03:00
window - > center_on_screen ( ) ;
window - > move_to ( window - > x ( ) , window - > y ( ) - ( GUI : : Desktop : : the ( ) . rect ( ) . height ( ) * 0.33 ) ) ;
window - > show ( ) ;
return app - > exec ( ) ;
}