up su Gitea

This commit is contained in:
2026-04-19 17:07:18 +02:00
parent e78ce720bb
commit fe54b28378
298 changed files with 23460 additions and 0 deletions
@@ -0,0 +1,448 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
Item {
id: root
property var pluginApi: null
// Provider metadata
property string name: pluginApi?.tr("provider.name")
property var launcher: null
property bool handleSearch: false
property string supportedLayouts: "list"
property bool supportsAutoPaste: false
// Search state
property var currentResults: []
property string currentQuery: ""
property bool searching: false
property int nextRequestId: 0
property int activeRequestId: 0
property int fileProcessRequestId: 0
property int dirProcessRequestId: 0
property int pendingProcessCount: 0
property bool currentRequestFailed: false
property var pendingResultsByType: ({ "files": [], "dirs": [] })
property string fdCommandPath: ""
property bool fdAvailable: false
// Settings shortcuts
property var cfg: pluginApi?.pluginSettings || ({})
property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
property bool showHidden: cfg.showHidden ?? defaults.showHidden ?? false
property int maxResults: cfg.maxResults ?? defaults.maxResults ?? 0
property string fileOpener: cfg.fileOpener ?? defaults.fileOpener ?? "xdg-open"
property string fdCommand: cfg.fdCommand ?? defaults.fdCommand ?? "fd"
property string searchDirectory: cfg.searchDirectory ?? defaults.searchDirectory ?? "~"
Process {
id: fileSearchProcess
running: false
stdout: StdioCollector {
id: fileStdoutCollector
}
stderr: StdioCollector {
id: fileStderrCollector
}
onExited: function(exitCode) {
root.handleSearchProcessExit("files", fileProcessRequestId, exitCode, fileStdoutCollector.text, fileStderrCollector.text);
}
}
Process {
id: dirSearchProcess
running: false
stdout: StdioCollector {
id: dirStdoutCollector
}
stderr: StdioCollector {
id: dirStderrCollector
}
onExited: function(exitCode) {
root.handleSearchProcessExit("dirs", dirProcessRequestId, exitCode, dirStdoutCollector.text, dirStderrCollector.text);
}
}
// Debounce timer for search
Timer {
id: searchDebouncer
interval: 300
repeat: false
onTriggered: root.executeSearch(root.currentQuery)
}
function init() {
Logger.i("FileSearch", "Initializing plugin");
fdCommandPath = fdCommand;
fdAvailable = true;
Logger.i("FileSearch", "Using fd command:", fdCommandPath);
}
function handleCommand(searchText) {
return searchText.startsWith(">file");
}
function commands() {
return [{
"name": ">file",
"description": pluginApi?.tr("launcher.command.description"),
"icon": "file-search",
"isTablerIcon": true,
"isImage": false,
"onActivate": function() {
launcher.setSearchText(">file ");
}
}];
}
function getResults(searchText) {
if (!searchText.startsWith(">file")) {
return [];
}
if (!fdAvailable) {
return [{
"name": pluginApi?.tr("launcher.errors.fdNotFound.title"),
"description": pluginApi?.tr("launcher.errors.fdNotFound.description"),
"icon": "alert-circle",
"isTablerIcon": true,
"isImage": false,
"onActivate": function() {}
}];
}
var query = searchText.slice(5).trim();
if (query === "") {
return [{
"name": pluginApi?.tr("launcher.prompts.emptyQuery.title"),
"description": pluginApi?.tr("launcher.prompts.emptyQuery.description", { "root": displaySearchDirectory() }),
"icon": "file-search",
"isTablerIcon": true,
"isImage": false,
"onActivate": function() {}
}];
}
if (query !== currentQuery) {
currentQuery = query;
activeRequestId = 0;
currentRequestFailed = false;
searching = true;
searchDebouncer.restart();
return [{
"name": pluginApi?.tr("launcher.prompts.searching.title"),
"description": pluginApi?.tr("launcher.prompts.searching.description", { "query": query }),
"icon": "refresh",
"isTablerIcon": true,
"isImage": false,
"onActivate": function() {}
}];
}
if (searching) {
return [{
"name": pluginApi?.tr("launcher.prompts.searching.title"),
"description": pluginApi?.tr("launcher.prompts.searching.description", { "query": query }),
"icon": "refresh",
"isTablerIcon": true,
"isImage": false,
"onActivate": function() {}
}];
}
return currentResults;
}
function executeSearch(query) {
if (!fdAvailable || query === "") {
return;
}
Logger.d("FileSearch", "Executing search for:", query);
if (fileSearchProcess.running) {
fileSearchProcess.running = false;
}
if (dirSearchProcess.running) {
dirSearchProcess.running = false;
}
var expandedDir = expandHomePath(searchDirectory);
nextRequestId += 1;
var requestId = nextRequestId;
activeRequestId = requestId;
currentRequestFailed = false;
pendingProcessCount = 2;
pendingResultsByType = ({ "files": [], "dirs": [] });
var commonArgs = [];
if (showHidden) {
commonArgs.push("--hidden");
}
if (maxResults > 0) {
commonArgs.push("--max-results", maxResults.toString());
}
commonArgs.push("--base-directory", expandedDir);
commonArgs.push("--absolute-path");
commonArgs.push("--color", "never");
commonArgs.push(query);
var fileArgs = ["--type", "f"].concat(commonArgs);
var dirArgs = ["--type", "d"].concat(commonArgs);
fileProcessRequestId = requestId;
fileSearchProcess.command = [fdCommandPath].concat(fileArgs);
fileSearchProcess.running = true;
dirProcessRequestId = requestId;
dirSearchProcess.command = [fdCommandPath].concat(dirArgs);
dirSearchProcess.running = true;
Logger.d("FileSearch", "Running file command:", fdCommandPath, fileArgs.join(" "));
Logger.d("FileSearch", "Running dir command:", fdCommandPath, dirArgs.join(" "));
}
function handleSearchProcessExit(kind, requestId, exitCode, stdoutText, stderrText) {
if (requestId !== activeRequestId || currentRequestFailed) {
return;
}
if (exitCode !== 0) {
currentRequestFailed = true;
searching = false;
pendingProcessCount = 0;
Logger.e("FileSearch", "fd command failed with exit code:", exitCode);
Logger.e("FileSearch", "stderr:", stderrText);
currentResults = [{
"name": pluginApi?.tr("launcher.errors.fdNotFound.title"),
"description": pluginApi?.tr("launcher.errors.fdNotFound.description"),
"icon": "alert-circle",
"isTablerIcon": true,
"onActivate": function() {}
}];
if (launcher) {
launcher.updateResults();
}
return;
}
pendingResultsByType[kind] = parseRawPaths(stdoutText);
pendingProcessCount -= 1;
if (pendingProcessCount <= 0) {
finalizeSearchResults(requestId);
}
}
function finalizeSearchResults(requestId) {
if (requestId !== activeRequestId || currentRequestFailed) {
return;
}
var results = [];
for (var i = 0; i < pendingResultsByType.dirs.length; i++) {
results.push(formatFileEntry(pendingResultsByType.dirs[i], true));
}
for (var j = 0; j < pendingResultsByType.files.length; j++) {
results.push(formatFileEntry(pendingResultsByType.files[j], false));
}
results = sortResults(results, currentQuery);
if (maxResults > 0 && results.length > maxResults) {
results = results.slice(0, maxResults);
}
if (results.length === 0) {
results.push({
"name": pluginApi?.tr("launcher.prompts.noResults.title"),
"description": pluginApi?.tr("launcher.prompts.noResults.description", { "query": currentQuery }),
"icon": "file-off",
"isTablerIcon": true,
"isImage": false,
"onActivate": function() {}
});
searching = false;
currentResults = results;
if (launcher) {
launcher.updateResults();
}
return;
}
searching = false;
currentResults = results;
Logger.d("FileSearch", "Found", results.length, "results");
if (launcher) {
launcher.updateResults();
}
}
function parseRawPaths(output) {
var trimmed = output.trim();
if (trimmed === "") {
return [];
}
return trimmed.split("\n").filter(function(line) { return line.trim() !== ""; });
}
function expandHomePath(pathValue) {
var expandedPath = pathValue;
if (expandedPath.startsWith("~")) {
expandedPath = Quickshell.env("HOME") + expandedPath.substring(1);
}
return expandedPath;
}
function displaySearchDirectory() {
var expandedPath = expandHomePath(searchDirectory);
var homeDir = Quickshell.env("HOME");
if (expandedPath.startsWith(homeDir)) {
return "~" + expandedPath.slice(homeDir.length);
}
return expandedPath;
}
function sortResults(results, query) {
var queryLower = query.toLowerCase();
results.sort(function(a, b) {
var rankA = resultRank(a, queryLower);
var rankB = resultRank(b, queryLower);
if (rankA !== rankB) {
return rankA - rankB;
}
var nameA = (a.name || "").toLowerCase();
var nameB = (b.name || "").toLowerCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return (a.description || "").length - (b.description || "").length;
});
return results;
}
function resultRank(result, queryLower) {
var name = (result.name || "").toLowerCase();
var description = (result.description || "").toLowerCase();
var fullPath = description + "/" + name;
if (name === queryLower) {
return 0;
}
if (name.startsWith(queryLower)) {
return 1;
}
if (name.indexOf(queryLower) !== -1) {
return 2;
}
if (fullPath.indexOf(queryLower) !== -1) {
return 3;
}
return 4;
}
function formatFileEntry(filePath, forcedIsDirectory) {
var normalizedPath = filePath;
while (normalizedPath.length > 1 && normalizedPath.endsWith("/")) {
normalizedPath = normalizedPath.slice(0, -1);
}
var isDirectory = (forcedIsDirectory !== undefined) ? forcedIsDirectory : normalizedPath !== filePath;
var parts = normalizedPath.split("/");
var filename = parts[parts.length - 1];
var parentPath = parts.slice(0, -1).join("/");
if (filename === "") {
filename = normalizedPath;
}
var homeDir = Quickshell.env("HOME");
if (parentPath.startsWith(homeDir)) {
parentPath = "~" + parentPath.slice(homeDir.length);
}
return {
"name": filename,
"description": parentPath,
"icon": isDirectory ? "folder" : getFileIcon(filename),
"isTablerIcon": true,
"isImage": false,
"singleLine": false,
"onActivate": function() {
root.openFile(normalizedPath);
}
};
}
function getFileIcon(filename) {
var ext = filename.split(".").pop().toLowerCase();
// Images
if (["jpg", "jpeg", "png", "gif", "svg", "webp", "bmp", "ico"].indexOf(ext) !== -1) {
return "photo";
}
// Documents
if (["txt", "md", "pdf", "doc", "docx", "odt", "rtf"].indexOf(ext) !== -1) {
return "file-text";
}
// Code files
if (["js", "ts", "py", "java", "cpp", "c", "h", "qml", "rs", "go", "rb", "php", "html", "css", "json", "xml", "yaml", "yml"].indexOf(ext) !== -1) {
return "code";
}
// Archives
if (["zip", "tar", "gz", "bz2", "xz", "7z", "rar"].indexOf(ext) !== -1) {
return "file-zip";
}
// Audio
if (["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"].indexOf(ext) !== -1) {
return "music";
}
// Video
if (["mp4", "mkv", "avi", "mov", "wmv", "flv", "webm"].indexOf(ext) !== -1) {
return "video";
}
// Spreadsheets
if (["xls", "xlsx", "ods", "csv"].indexOf(ext) !== -1) {
return "table";
}
// Presentations
if (["ppt", "pptx", "odp"].indexOf(ext) !== -1) {
return "presentation";
}
// Default
return "file";
}
function openFile(filePath) {
Logger.i("FileSearch", "Opening file:", filePath);
Quickshell.execDetached([fileOpener, filePath]);
launcher.close();
}
}
+68
View File
@@ -0,0 +1,68 @@
import QtQuick
import Quickshell.Io
import qs.Commons
import qs.Services.UI
Item {
property var pluginApi: null
Component.onCompleted: {
if (pluginApi) {
Logger.i("FileSearch", "Plugin initialized");
}
}
IpcHandler {
target: "plugin:file-search"
// Toggle launcher in file search mode
function toggle() {
if (!pluginApi) return;
pluginApi.withCurrentScreen(screen => {
var launcherPanel = PanelService.getPanel("launcherPanel", screen);
if (!launcherPanel) {
Logger.e("FileSearch", "Could not get launcher panel");
return;
}
var searchText = launcherPanel.searchText || "";
var isInFileMode = searchText.startsWith(">file");
if (!launcherPanel.isPanelOpen) {
// Launcher closed - open with file search
Logger.i("FileSearch", "Opening launcher in file search mode");
launcherPanel.open();
launcherPanel.setSearchText(">file ");
} else if (isInFileMode) {
// Already in file mode - close launcher
Logger.i("FileSearch", "Closing launcher (toggle off)");
launcherPanel.close();
} else {
// Launcher open but different mode - switch to file search
Logger.i("FileSearch", "Switching to file search mode");
launcherPanel.setSearchText(">file ");
}
});
}
// Open launcher with file search and specific query
function search(query: string) {
if (!pluginApi) return;
pluginApi.withCurrentScreen(screen => {
var launcherPanel = PanelService.getPanel("launcherPanel", screen);
if (!launcherPanel) {
Logger.e("FileSearch", "Could not get launcher panel");
return;
}
var searchQuery = query || "";
Logger.i("FileSearch", "Opening launcher with search query:", searchQuery);
launcherPanel.open();
launcherPanel.setSearchText(">file " + searchQuery);
});
}
}
}
+25
View File
@@ -0,0 +1,25 @@
# File Search Plugin
File search from the launcher.
## Requirements
This plugin requires [fd](https://github.com/sharkdp/fd#installation) to be installed.
## Usage
**Access from launcher:**
Type `>file` in the Noctalia launcher to activate file search.
**Toggle file search:**
```bash
noctalia-shell ipc call plugin:file-search toggle
```
**Search with pre-filled query:**
```bash
noctalia-shell ipc call plugin:file-search search "eko"
```
+142
View File
@@ -0,0 +1,142 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
property var pluginApi: null
property var cfg: pluginApi?.pluginSettings || ({})
property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
property bool valueShowHidden: cfg.showHidden ?? defaults.showHidden
property int valueMaxResults: cfg.maxResults ?? defaults.maxResults
property string valueFileOpener: cfg.fileOpener ?? defaults.fileOpener
property string valueFdCommand: cfg.fdCommand ?? defaults.fdCommand
property string valueSearchDirectory: cfg.searchDirectory ?? defaults.searchDirectory
spacing: Style.marginL
Component.onCompleted: {
Logger.d("FileSearch", "Settings UI loaded");
}
ColumnLayout {
spacing: Style.marginM
Layout.fillWidth: true
// Show Hidden Files Toggle
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS
NText {
text: pluginApi?.tr("settings.showHidden.label")
font.pointSize: Style.fontSizeL
font.weight: Font.Medium
color: Color.mOnSurface
Layout.fillWidth: true
}
}
NToggle {
checked: root.valueShowHidden
onToggled: root.valueShowHidden = checked
}
}
// File Opener Input
NTextInput {
Layout.fillWidth: true
label: pluginApi?.tr("settings.fileOpener.label")
description: pluginApi?.tr("settings.fileOpener.description")
placeholderText: pluginApi?.tr("settings.fileOpener.placeholder")
text: root.valueFileOpener
onTextChanged: root.valueFileOpener = text
}
// Search Directory Input
NTextInput {
Layout.fillWidth: true
label: pluginApi?.tr("settings.searchDirectory.label")
description: pluginApi?.tr("settings.searchDirectory.description")
placeholderText: pluginApi?.tr("settings.searchDirectory.placeholder")
text: root.valueSearchDirectory
onTextChanged: root.valueSearchDirectory = text
}
// fd Command Path Input
NTextInput {
Layout.fillWidth: true
label: pluginApi?.tr("settings.fdCommand.label")
description: pluginApi?.tr("settings.fdCommand.description")
placeholderText: pluginApi?.tr("settings.fdCommand.placeholder")
text: root.valueFdCommand
onTextChanged: root.valueFdCommand = text
}
}
// Max Results Slider
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS
RowLayout {
Layout.fillWidth: true
NText {
text: pluginApi?.tr("settings.maxResults.label")
font.pointSize: Style.fontSizeL
font.weight: Font.Medium
color: Color.mOnSurface
Layout.fillWidth: true
}
NText {
text: root.valueMaxResults === 0 ? pluginApi?.tr("settings.maxResults.unlimited") : root.valueMaxResults.toString()
font.pointSize: Style.fontSizeM
font.weight: Font.Medium
color: Color.mPrimary
}
}
NText {
text: pluginApi?.tr("settings.maxResults.description")
font.pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
NSlider {
Layout.fillWidth: true
from: 0
to: 200
stepSize: 10
value: root.valueMaxResults
onMoved: root.valueMaxResults = Math.round(value)
}
}
function saveSettings() {
if (!pluginApi) {
Logger.e("FileSearch", "Cannot save settings: pluginApi is null");
return;
}
pluginApi.pluginSettings.showHidden = root.valueShowHidden;
pluginApi.pluginSettings.maxResults = root.valueMaxResults;
pluginApi.pluginSettings.fileOpener = root.valueFileOpener;
pluginApi.pluginSettings.searchDirectory = root.valueSearchDirectory;
pluginApi.pluginSettings.fdCommand = root.valueFdCommand;
pluginApi.saveSettings();
Logger.d("FileSearch", "Settings saved successfully");
}
}
+55
View File
@@ -0,0 +1,55 @@
{
"provider": {
"name": "File Search"
},
"launcher": {
"command": {
"description": "Search files and folders"
},
"errors": {
"fdNotFound": {
"title": "fd not found",
"description": "Please install fd to use file search"
}
},
"prompts": {
"emptyQuery": {
"title": "Type to search files and folders",
"description": "Start typing to search in {{root}}"
},
"searching": {
"title": "Searching...",
"description": "Looking for files and folders matching: {{query}}"
},
"noResults": {
"title": "No results found",
"description": "No files or folders matching '{{query}}'"
}
}
},
"settings": {
"showHidden": {
"label": "Include hidden files"
},
"fileOpener": {
"label": "File opener command",
"description": "Command used to open files and folders",
"placeholder": "xdg-open"
},
"searchDirectory": {
"label": "Search directory",
"description": "Directory to search for files and folders",
"placeholder": "~"
},
"fdCommand": {
"label": "fd command path",
"description": "Command name or full path",
"placeholder": "fd"
},
"maxResults": {
"label": "Maximum results",
"unlimited": "Unlimited",
"description": "Limit the number of search results displayed (set to 0 for unlimited)"
}
}
}
@@ -0,0 +1,31 @@
{
"id": "file-search",
"name": "File Search",
"version": "1.0.1",
"minNoctaliaVersion": "4.1.2",
"author": "ericbreh",
"license": "MIT",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "File search from the launcher.",
"tags": [
"Launcher",
"Productivity"
],
"entryPoints": {
"main": "Main.qml",
"launcherProvider": "LauncherProvider.qml",
"settings": "Settings.qml"
},
"dependencies": {
"plugins": []
},
"metadata": {
"defaultSettings": {
"showHidden": false,
"maxResults": 0,
"fileOpener": "xdg-open",
"searchDirectory": "~",
"fdCommand": "fd"
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 965 KiB