Files
dotfiles/gnome/.local/share/gnome-shell/extensions/grimble@lmt.github.io/topBarSearchEntry.js

132 lines
5.2 KiB
JavaScript

import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import St from 'gi://St';
import {Button as PanelButton} from 'resource:///org/gnome/shell/ui/panelMenu.js';
import Clutter from 'gi://Clutter';
import {gnomeExecutables, autocomplete, clear} from './autocomplete.js';
import {launchApp} from './utils.js';
export class TopBarSearchEntry {
_searchButton;
_searchContainer;
_searchEntry;
_searchSuggestion;
_alive;
constructor(settings) {
this._alive = true;
this._searchButton = new PanelButton(0.0, 'searchEntry', false);
this._searchContainer = new St.Bin({
x_expand: true,
});
this._searchEntry = new St.Entry({
style_class: 'custom-search-entry',
can_focus: true,
hint_text: 'Type to search…',
track_hover: true,
x_expand: true,
});
this._searchSuggestion = new St.Label({
style_class: 'suggestion',
text: '',
x_expand: true,
});
this._searchEntry.clutter_text.connect('notify::mapped', actor => {
if (actor.mapped)
actor.grab_key_focus();
});
this._searchContainer.add_child(this._searchSuggestion);
this._searchContainer.add_child(this._searchEntry);
this._searchEntry.clutter_text.connect('text-changed', actor => {
let current = actor.get_text();
if (current.length > 1 && this._searchSuggestion) {
let matches = autocomplete(current);
if (matches.length > 0 && this._searchEntry) {
let match = matches[0];
const ct = this._searchEntry.get_clutter_text();
const layout = ct.get_layout();
const [textW] = layout.get_pixel_size();
const themeNode = this._searchEntry.get_theme_node();
const leftPad = themeNode.get_padding(St.Side.LEFT);
const x = leftPad + textW;
this._searchSuggestion.set_style(`color: rgba(255,255,255,0.35); margin-left: ${x + 4}px;`);
this._searchSuggestion.set_text(match.slice(current.length));
} else {
this._searchSuggestion.set_text('');
}
} else if (this._searchSuggestion) {
this._searchSuggestion.set_text('');
}
});
let completeText = () => {
const typed = this._searchEntry?.get_text();
const ct = this._searchEntry?.get_clutter_text();
if (!typed || !ct)
return;
let full = typed + this._searchSuggestion?.get_text();
this._searchEntry?.set_text(full);
ct.set_cursor_position(full.length);
this._searchSuggestion?.set_text('');
};
this._searchEntry.clutter_text.connect('key-press-event', (ct, event) => {
const key = event.get_key_symbol();
if (key === Clutter.KEY_KP_Right || key === Clutter.KEY_Right) {
// Only accept if cursor is at end and a suggestion exists
const typed = this._searchEntry?.get_text();
if (typed) {
completeText();
return Clutter.EVENT_STOP;
}
}
return Clutter.EVENT_PROPAGATE;
});
this._searchEntry.connect('captured-event', (actor, event) => {
if (event.type() !== Clutter.EventType.KEY_PRESS)
return Clutter.EVENT_PROPAGATE;
const sym = event.get_key_symbol();
if (sym === Clutter.KEY_Tab || sym === Clutter.KEY_ISO_Left_Tab) {
completeText();
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
});
this._searchEntry.clutter_text.connect('activate', actor => {
let query = actor.get_text().trim().toLowerCase();
if (query === '') {
this.destroy();
} else if (gnomeExecutables?.get(query)) {
gnomeExecutables.get(query)?.launch([], null);
this.destroy();
} else if (launchApp([query])) {
this.destroy();
} else {
actor.set_text('');
this._searchEntry?.set_style('border: 2px solid red;');
}
});
this._searchButton.add_child(this._searchContainer);
let positionInt = settings.get_int('search-entry-position');
let position;
if (positionInt === 0)
position = 'left';
else if (positionInt === 1)
position = 'center';
else
position = 'right';
Main.panel.addToStatusArea('SearchEntry', this._searchButton, 0, position);
}
isAlive() {
return this._alive;
}
destroy() {
this._alive = false;
if (this._searchButton) {
this._searchButton?.destroy();
this._searchContainer = undefined;
this._searchEntry = undefined;
this._searchButton = undefined;
this._searchSuggestion = undefined;
}
clear();
}
}