mirror of https://github.com/Wox-launcher/Wox
feat(script_plugin): enhance script plugin functionality
- Increased notification display duration from 3 to 8 seconds in API implementation. - Refactored script execution logic in host_script.go to improve error handling and streamline execution. - Added checks for existing script plugins in WPM plugin creation process, providing options to open or overwrite. - Enhanced user feedback with localized messages for plugin creation and actions. - Updated script templates (JavaScript, Python, Bash) to include environment variable documentation and improved action handling. - Improved UI responsiveness in Flutter by implementing AnimatedSwitcher for list view updates. - Added validation for viewport height in WoxListController to prevent scrolling issues during initialization.
This commit is contained in:
parent
d488d98eec
commit
21d8d83a1f
|
@ -65,7 +65,7 @@ func (a *APIImpl) Notify(ctx context.Context, message string) {
|
||||||
GetPluginManager().GetUI().Notify(ctx, common.NotifyMsg{
|
GetPluginManager().GetUI().Notify(ctx, common.NotifyMsg{
|
||||||
PluginId: a.pluginInstance.Metadata.Id,
|
PluginId: a.pluginInstance.Metadata.Id,
|
||||||
Text: a.GetTranslation(ctx, message),
|
Text: a.GetTranslation(ctx, message),
|
||||||
DisplaySeconds: 3,
|
DisplaySeconds: 8,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"wox/plugin"
|
"wox/plugin"
|
||||||
"wox/util"
|
"wox/util"
|
||||||
|
"wox/util/shell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -110,52 +111,12 @@ func (sp *ScriptPlugin) Query(ctx context.Context, query plugin.Query) []plugin.
|
||||||
|
|
||||||
// executeScript executes the script with the given JSON-RPC request and returns the results
|
// executeScript executes the script with the given JSON-RPC request and returns the results
|
||||||
func (sp *ScriptPlugin) executeScript(ctx context.Context, request map[string]interface{}) ([]plugin.QueryResult, error) {
|
func (sp *ScriptPlugin) executeScript(ctx context.Context, request map[string]interface{}) ([]plugin.QueryResult, error) {
|
||||||
// Convert request to JSON
|
// Execute script and get raw response
|
||||||
requestJSON, err := json.Marshal(request)
|
response, err := sp.executeScriptRaw(ctx, request)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the interpreter based on file extension
|
|
||||||
interpreter, err := sp.getInterpreter()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare command
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if interpreter != "" {
|
|
||||||
cmd = exec.CommandContext(ctx, interpreter, sp.scriptPath)
|
|
||||||
} else {
|
|
||||||
cmd = exec.CommandContext(ctx, sp.scriptPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up stdin with the JSON-RPC request
|
|
||||||
cmd.Stdin = strings.NewReader(string(requestJSON))
|
|
||||||
|
|
||||||
// Set timeout for script execution
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
cmd = exec.CommandContext(timeoutCtx, cmd.Args[0], cmd.Args[1:]...)
|
|
||||||
cmd.Stdin = strings.NewReader(string(requestJSON))
|
|
||||||
|
|
||||||
// Execute script
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("script execution failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse JSON-RPC response
|
|
||||||
var response map[string]interface{}
|
|
||||||
if err := json.Unmarshal(output, &response); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse script response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for JSON-RPC error
|
|
||||||
if errorData, exists := response["error"]; exists {
|
|
||||||
return nil, fmt.Errorf("script returned error: %v", errorData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract results
|
// Extract results
|
||||||
result, exists := response["result"]
|
result, exists := response["result"]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
@ -231,52 +192,70 @@ func (sp *ScriptPlugin) executeAction(ctx context.Context, actionData map[string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeScriptAction executes the script for action requests
|
// executeScriptRaw executes the script with the given JSON-RPC request and returns the raw response
|
||||||
func (sp *ScriptPlugin) executeScriptAction(ctx context.Context, request map[string]interface{}) error {
|
func (sp *ScriptPlugin) executeScriptRaw(ctx context.Context, request map[string]interface{}) (map[string]interface{}, error) {
|
||||||
// Convert request to JSON
|
// Convert request to JSON
|
||||||
requestJSON, err := json.Marshal(request)
|
requestJSON, err := json.Marshal(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal request: %w", err)
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the interpreter based on file extension
|
// Determine the interpreter based on file extension
|
||||||
interpreter, err := sp.getInterpreter()
|
interpreter, err := sp.getInterpreter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare command
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if interpreter != "" {
|
|
||||||
cmd = exec.CommandContext(ctx, interpreter, sp.scriptPath)
|
|
||||||
} else {
|
|
||||||
cmd = exec.CommandContext(ctx, sp.scriptPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up stdin with the JSON-RPC request
|
|
||||||
cmd.Stdin = strings.NewReader(string(requestJSON))
|
|
||||||
|
|
||||||
// Set timeout for script execution
|
// Set timeout for script execution
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cmd = exec.CommandContext(timeoutCtx, cmd.Args[0], cmd.Args[1:]...)
|
|
||||||
|
// Prepare command
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if interpreter != "" {
|
||||||
|
cmd = exec.CommandContext(timeoutCtx, interpreter, sp.scriptPath)
|
||||||
|
} else {
|
||||||
|
cmd = exec.CommandContext(timeoutCtx, sp.scriptPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up environment variables for script plugins
|
||||||
|
cmd.Env = append(os.Environ(),
|
||||||
|
"WOX_DIRECTORY_USER_SCRIPT_PLUGINS="+util.GetLocation().GetUserScriptPluginsDirectory(),
|
||||||
|
"WOX_DIRECTORY_USER_DATA="+util.GetLocation().GetUserDataDirectory(),
|
||||||
|
"WOX_DIRECTORY_WOX_DATA="+util.GetLocation().GetWoxDataDirectory(),
|
||||||
|
"WOX_DIRECTORY_PLUGINS="+util.GetLocation().GetPluginDirectory(),
|
||||||
|
"WOX_DIRECTORY_THEMES="+util.GetLocation().GetThemeDirectory(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set up stdin with the JSON-RPC request
|
||||||
cmd.Stdin = strings.NewReader(string(requestJSON))
|
cmd.Stdin = strings.NewReader(string(requestJSON))
|
||||||
|
|
||||||
// Execute script
|
// Execute script
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("script execution failed: %w", err)
|
return nil, fmt.Errorf("script execution failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse JSON-RPC response
|
// Parse JSON-RPC response
|
||||||
var response map[string]interface{}
|
var response map[string]interface{}
|
||||||
if err := json.Unmarshal(output, &response); err != nil {
|
if err := json.Unmarshal(output, &response); err != nil {
|
||||||
return fmt.Errorf("failed to parse script response: %w", err)
|
return nil, fmt.Errorf("failed to parse script response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for JSON-RPC error
|
// Check for JSON-RPC error
|
||||||
if errorData, exists := response["error"]; exists {
|
if errorData, exists := response["error"]; exists {
|
||||||
return fmt.Errorf("script returned error: %v", errorData)
|
return nil, fmt.Errorf("script returned error: %v", errorData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeScriptAction executes the script for action requests
|
||||||
|
func (sp *ScriptPlugin) executeScriptAction(ctx context.Context, request map[string]interface{}) error {
|
||||||
|
// Execute script and get raw response
|
||||||
|
response, err := sp.executeScriptRaw(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle action result if present
|
// Handle action result if present
|
||||||
|
@ -300,6 +279,16 @@ func (sp *ScriptPlugin) handleActionResult(ctx context.Context, result map[strin
|
||||||
// TODO: Implement URL opening functionality
|
// TODO: Implement URL opening functionality
|
||||||
util.GetLogger().Info(ctx, fmt.Sprintf("Script plugin %s requested to open URL: %s", sp.metadata.Name, url))
|
util.GetLogger().Info(ctx, fmt.Sprintf("Script plugin %s requested to open URL: %s", sp.metadata.Name, url))
|
||||||
}
|
}
|
||||||
|
case "open-directory":
|
||||||
|
path := getStringFromMap(result, "path")
|
||||||
|
if path != "" {
|
||||||
|
// Open directory using shell.Open
|
||||||
|
if err := shell.Open(path); err != nil {
|
||||||
|
util.GetLogger().Error(ctx, fmt.Sprintf("Script plugin %s failed to open directory %s: %s", sp.metadata.Name, path, err.Error()))
|
||||||
|
} else {
|
||||||
|
util.GetLogger().Info(ctx, fmt.Sprintf("Script plugin %s opened directory: %s", sp.metadata.Name, path))
|
||||||
|
}
|
||||||
|
}
|
||||||
case "notify":
|
case "notify":
|
||||||
message := getStringFromMap(result, "message")
|
message := getStringFromMap(result, "message")
|
||||||
if message != "" {
|
if message != "" {
|
||||||
|
|
|
@ -61,7 +61,6 @@ func init() {
|
||||||
|
|
||||||
type WPMPlugin struct {
|
type WPMPlugin struct {
|
||||||
api plugin.API
|
api plugin.API
|
||||||
creatingProcess string
|
|
||||||
localPluginDirectories []string
|
localPluginDirectories []string
|
||||||
localPlugins []localPlugin
|
localPlugins []localPlugin
|
||||||
reloadPluginTimers *util.HashMap[string, *time.Timer]
|
reloadPluginTimers *util.HashMap[string, *time.Timer]
|
||||||
|
@ -298,18 +297,15 @@ func (w *WPMPlugin) Query(ctx context.Context, query plugin.Query) []plugin.Quer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WPMPlugin) createCommand(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
func (w *WPMPlugin) createCommand(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
||||||
if w.creatingProcess != "" {
|
// Check if user has entered a plugin name
|
||||||
|
pluginName := strings.TrimSpace(query.Search)
|
||||||
|
if pluginName == "" {
|
||||||
return []plugin.QueryResult{
|
return []plugin.QueryResult{
|
||||||
{
|
{
|
||||||
Id: uuid.NewString(),
|
Id: uuid.NewString(),
|
||||||
Title: w.creatingProcess,
|
Title: "i18n:plugin_wpm_enter_plugin_name",
|
||||||
SubTitle: "i18n:plugin_wpm_please_wait",
|
SubTitle: "i18n:plugin_wpm_enter_plugin_name_subtitle",
|
||||||
Icon: wpmIcon,
|
Icon: wpmIcon,
|
||||||
RefreshInterval: 300,
|
|
||||||
OnRefresh: func(ctx context.Context, current plugin.RefreshableResult) plugin.RefreshableResult {
|
|
||||||
current.Title = w.creatingProcess
|
|
||||||
return current
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,13 +342,48 @@ func (w *WPMPlugin) createCommand(ctx context.Context, query plugin.Query) []plu
|
||||||
// Add script plugin templates with group
|
// Add script plugin templates with group
|
||||||
for _, template := range scriptPluginTemplates {
|
for _, template := range scriptPluginTemplates {
|
||||||
templateCopy := template // Create a copy for the closure
|
templateCopy := template // Create a copy for the closure
|
||||||
results = append(results, plugin.QueryResult{
|
|
||||||
Id: uuid.NewString(),
|
// Check if script plugin already exists
|
||||||
Title: fmt.Sprintf("Create %s", template.Name),
|
exists, fileName := w.checkScriptPluginExists(pluginName, template.Url)
|
||||||
SubTitle: fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_plugin_name"), query.Search),
|
|
||||||
Icon: wpmIcon,
|
var title, subtitle string
|
||||||
Group: "Script Plugins",
|
var actions []plugin.QueryResultAction
|
||||||
Actions: []plugin.QueryResultAction{
|
|
||||||
|
if exists {
|
||||||
|
title = fmt.Sprintf("⚠️ %s (Already exists)", template.Name)
|
||||||
|
subtitle = fmt.Sprintf("File '%s' already exists. Choose an action below.", fileName)
|
||||||
|
// When file exists, provide actions to open or overwrite the existing file
|
||||||
|
actions = []plugin.QueryResultAction{
|
||||||
|
{
|
||||||
|
Name: "Open existing file",
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
userScriptPluginDirectory := util.GetLocation().GetUserScriptPluginsDirectory()
|
||||||
|
scriptFilePath := path.Join(userScriptPluginDirectory, fileName)
|
||||||
|
openErr := shell.Open(scriptFilePath)
|
||||||
|
if openErr != nil {
|
||||||
|
w.api.Notify(ctx, fmt.Sprintf("Failed to open file: %s", openErr.Error()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Overwrite existing file",
|
||||||
|
PreventHideAfterAction: true,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
pluginName := query.Search
|
||||||
|
util.Go(ctx, "overwrite script plugin", func() {
|
||||||
|
w.createScriptPluginWithTemplate(ctx, templateCopy, pluginName, query)
|
||||||
|
})
|
||||||
|
w.api.ChangeQuery(ctx, common.PlainQuery{
|
||||||
|
QueryType: plugin.QueryTypeInput,
|
||||||
|
QueryText: fmt.Sprintf("%s create ", query.TriggerKeyword),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
title = fmt.Sprintf("Create %s", template.Name)
|
||||||
|
subtitle = fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_plugin_name"), query.Search)
|
||||||
|
actions = []plugin.QueryResultAction{
|
||||||
{
|
{
|
||||||
Name: "i18n:plugin_wpm_create",
|
Name: "i18n:plugin_wpm_create",
|
||||||
PreventHideAfterAction: true,
|
PreventHideAfterAction: true,
|
||||||
|
@ -367,7 +398,17 @@ func (w *WPMPlugin) createCommand(ctx context.Context, query plugin.Query) []plu
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, plugin.QueryResult{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
Title: title,
|
||||||
|
SubTitle: subtitle,
|
||||||
|
Icon: wpmIcon,
|
||||||
|
Group: "Script Plugins",
|
||||||
|
Actions: actions,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -602,33 +643,33 @@ func (w *WPMPlugin) removeDevCommand(ctx context.Context, query plugin.Query) []
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WPMPlugin) createPlugin(ctx context.Context, template pluginTemplate, pluginName string, query plugin.Query) {
|
func (w *WPMPlugin) createPlugin(ctx context.Context, template pluginTemplate, pluginName string, query plugin.Query) {
|
||||||
w.creatingProcess = "i18n:plugin_wpm_downloading_template"
|
w.api.Notify(ctx, "i18n:plugin_wpm_downloading_template")
|
||||||
|
|
||||||
tempPluginDirectory := path.Join(os.TempDir(), uuid.NewString())
|
tempPluginDirectory := path.Join(os.TempDir(), uuid.NewString())
|
||||||
if err := util.GetLocation().EnsureDirectoryExist(tempPluginDirectory); err != nil {
|
if err := util.GetLocation().EnsureDirectoryExist(tempPluginDirectory); err != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to create temp plugin directory: %s", err.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to create temp plugin directory: %s", err.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_create_temp_dir_failed"), err.Error())
|
w.api.Notify(ctx, fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_create_temp_dir_failed"), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.creatingProcess = fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_downloading_template_to"), template.Runtime, tempPluginDirectory)
|
w.api.Notify(ctx, fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_downloading_template_to"), template.Runtime, tempPluginDirectory))
|
||||||
tempZipPath := path.Join(tempPluginDirectory, "template.zip")
|
tempZipPath := path.Join(tempPluginDirectory, "template.zip")
|
||||||
err := util.HttpDownload(ctx, template.Url, tempZipPath)
|
err := util.HttpDownload(ctx, template.Url, tempZipPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to download template: %s", err.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to download template: %s", err.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_download_template_failed"), err.Error())
|
w.api.Notify(ctx, fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_download_template_failed"), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.creatingProcess = "i18n:plugin_wpm_extracting_template"
|
w.api.Notify(ctx, "i18n:plugin_wpm_extracting_template")
|
||||||
err = util.Unzip(tempZipPath, tempPluginDirectory)
|
err = util.Unzip(tempZipPath, tempPluginDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to extract template: %s", err.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to extract template: %s", err.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_extract_template_failed"), err.Error())
|
w.api.Notify(ctx, fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_wpm_extract_template_failed"), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.creatingProcess = "i18n:plugin_wpm_choose_directory_prompt"
|
w.api.Notify(ctx, "i18n:plugin_wpm_choose_directory_prompt")
|
||||||
pluginDirectories := plugin.GetPluginManager().GetUI().PickFiles(ctx, common.PickFilesParams{IsDirectory: true})
|
pluginDirectories := plugin.GetPluginManager().GetUI().PickFiles(ctx, common.PickFilesParams{IsDirectory: true})
|
||||||
if len(pluginDirectories) == 0 {
|
if len(pluginDirectories) == 0 {
|
||||||
w.api.Notify(ctx, "You need to choose a directory to create the plugin")
|
w.api.Notify(ctx, "You need to choose a directory to create the plugin")
|
||||||
|
@ -640,7 +681,7 @@ func (w *WPMPlugin) createPlugin(ctx context.Context, template pluginTemplate, p
|
||||||
cpErr := cp.Copy(path.Join(tempPluginDirectory, template.Name+"-main"), pluginDirectory)
|
cpErr := cp.Copy(path.Join(tempPluginDirectory, template.Name+"-main"), pluginDirectory)
|
||||||
if cpErr != nil {
|
if cpErr != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to copy template: %s", cpErr.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to copy template: %s", cpErr.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf("Failed to copy template: %s", cpErr.Error())
|
w.api.Notify(ctx, fmt.Sprintf("Failed to copy template: %s", cpErr.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,7 +690,7 @@ func (w *WPMPlugin) createPlugin(ctx context.Context, template pluginTemplate, p
|
||||||
pluginJson, readErr := os.ReadFile(pluginJsonPath)
|
pluginJson, readErr := os.ReadFile(pluginJsonPath)
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to read plugin.json: %s", readErr.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to read plugin.json: %s", readErr.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf("Failed to read plugin.json: %s", readErr.Error())
|
w.api.Notify(ctx, fmt.Sprintf("Failed to read plugin.json: %s", readErr.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,7 +703,7 @@ func (w *WPMPlugin) createPlugin(ctx context.Context, template pluginTemplate, p
|
||||||
writeErr := os.WriteFile(pluginJsonPath, []byte(pluginJsonString), 0644)
|
writeErr := os.WriteFile(pluginJsonPath, []byte(pluginJsonString), 0644)
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to write plugin.json: %s", writeErr.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to write plugin.json: %s", writeErr.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf("Failed to write plugin.json: %s", writeErr.Error())
|
w.api.Notify(ctx, fmt.Sprintf("Failed to write plugin.json: %s", writeErr.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,7 +713,7 @@ func (w *WPMPlugin) createPlugin(ctx context.Context, template pluginTemplate, p
|
||||||
packageJson, readPackageErr := os.ReadFile(packageJsonPath)
|
packageJson, readPackageErr := os.ReadFile(packageJsonPath)
|
||||||
if readPackageErr != nil {
|
if readPackageErr != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to read package.json: %s", readPackageErr.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to read package.json: %s", readPackageErr.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf("Failed to read package.json: %s", readPackageErr.Error())
|
w.api.Notify(ctx, fmt.Sprintf("Failed to read package.json: %s", readPackageErr.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,15 +724,15 @@ func (w *WPMPlugin) createPlugin(ctx context.Context, template pluginTemplate, p
|
||||||
writePackageErr := os.WriteFile(packageJsonPath, []byte(packageJsonString), 0644)
|
writePackageErr := os.WriteFile(packageJsonPath, []byte(packageJsonString), 0644)
|
||||||
if writePackageErr != nil {
|
if writePackageErr != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to write package.json: %s", writePackageErr.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to write package.json: %s", writePackageErr.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf("Failed to write package.json: %s", writePackageErr.Error())
|
w.api.Notify(ctx, fmt.Sprintf("Failed to write package.json: %s", writePackageErr.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.creatingProcess = ""
|
|
||||||
w.localPluginDirectories = append(w.localPluginDirectories, pluginDirectory)
|
w.localPluginDirectories = append(w.localPluginDirectories, pluginDirectory)
|
||||||
w.saveLocalPluginDirectories(ctx)
|
w.saveLocalPluginDirectories(ctx)
|
||||||
w.loadDevPlugin(ctx, pluginDirectory)
|
w.loadDevPlugin(ctx, pluginDirectory)
|
||||||
|
w.api.Notify(ctx, fmt.Sprintf("Plugin created successfully: %s", pluginName))
|
||||||
w.api.ChangeQuery(ctx, common.PlainQuery{
|
w.api.ChangeQuery(ctx, common.PlainQuery{
|
||||||
QueryType: plugin.QueryTypeInput,
|
QueryType: plugin.QueryTypeInput,
|
||||||
QueryText: fmt.Sprintf("%s dev ", query.TriggerKeyword),
|
QueryText: fmt.Sprintf("%s dev ", query.TriggerKeyword),
|
||||||
|
@ -743,9 +784,38 @@ func (w *WPMPlugin) reloadLocalDistPlugin(ctx context.Context, localPlugin plugi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkScriptPluginExists checks if a script plugin with the given name already exists
|
||||||
|
func (w *WPMPlugin) checkScriptPluginExists(pluginName string, templateFile string) (bool, string) {
|
||||||
|
cleanPluginName := strings.TrimSpace(pluginName)
|
||||||
|
if cleanPluginName == "" {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileExtension string
|
||||||
|
switch templateFile {
|
||||||
|
case "template.js":
|
||||||
|
fileExtension = ".js"
|
||||||
|
case "template.py":
|
||||||
|
fileExtension = ".py"
|
||||||
|
case "template.sh":
|
||||||
|
fileExtension = ".sh"
|
||||||
|
default:
|
||||||
|
fileExtension = ".js" // Default fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
userScriptPluginDirectory := util.GetLocation().GetUserScriptPluginsDirectory()
|
||||||
|
scriptFileName := strings.ReplaceAll(strings.ToLower(cleanPluginName), " ", "-") + fileExtension
|
||||||
|
scriptFilePath := path.Join(userScriptPluginDirectory, scriptFileName)
|
||||||
|
|
||||||
|
if _, err := os.Stat(scriptFilePath); err == nil {
|
||||||
|
return true, scriptFileName
|
||||||
|
}
|
||||||
|
return false, scriptFileName
|
||||||
|
}
|
||||||
|
|
||||||
// createScriptPluginWithTemplate creates a script plugin from a specific template
|
// createScriptPluginWithTemplate creates a script plugin from a specific template
|
||||||
func (w *WPMPlugin) createScriptPluginWithTemplate(ctx context.Context, template pluginTemplate, pluginName string, query plugin.Query) {
|
func (w *WPMPlugin) createScriptPluginWithTemplate(ctx context.Context, template pluginTemplate, pluginName string, query plugin.Query) {
|
||||||
w.creatingProcess = "Creating script plugin..."
|
w.api.Notify(ctx, "i18n:plugin_wpm_creating_script_plugin")
|
||||||
|
|
||||||
// Get user script plugins directory
|
// Get user script plugins directory
|
||||||
userScriptPluginDirectory := util.GetLocation().GetUserScriptPluginsDirectory()
|
userScriptPluginDirectory := util.GetLocation().GetUserScriptPluginsDirectory()
|
||||||
|
@ -753,7 +823,7 @@ func (w *WPMPlugin) createScriptPluginWithTemplate(ctx context.Context, template
|
||||||
// Ensure the directory exists
|
// Ensure the directory exists
|
||||||
if err := util.GetLocation().EnsureDirectoryExist(userScriptPluginDirectory); err != nil {
|
if err := util.GetLocation().EnsureDirectoryExist(userScriptPluginDirectory); err != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to create user script plugin directory: %s", err.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to create user script plugin directory: %s", err.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf("Failed to create script plugin directory: %s", err.Error())
|
w.api.Notify(ctx, fmt.Sprintf("i18n:plugin_wpm_create_script_dir_failed: %s", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -772,7 +842,7 @@ func (w *WPMPlugin) createScriptPluginWithTemplate(ctx context.Context, template
|
||||||
fileExtension = ".js" // Default fallback
|
fileExtension = ".js" // Default fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
w.creatingProcess = "Copying template..."
|
w.api.Notify(ctx, "i18n:plugin_wpm_copying_template")
|
||||||
|
|
||||||
// Read template from embedded resources
|
// Read template from embedded resources
|
||||||
scriptTemplateDirectory := util.GetLocation().GetScriptPluginTemplatesDirectory()
|
scriptTemplateDirectory := util.GetLocation().GetScriptPluginTemplatesDirectory()
|
||||||
|
@ -781,24 +851,25 @@ func (w *WPMPlugin) createScriptPluginWithTemplate(ctx context.Context, template
|
||||||
templateContent, err := os.ReadFile(templatePath)
|
templateContent, err := os.ReadFile(templatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to read template file: %s", err.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to read template file: %s", err.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf("Failed to read template: %s", err.Error())
|
w.api.Notify(ctx, fmt.Sprintf("i18n:plugin_wpm_read_template_failed: %s", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate script file name
|
// Generate script file name
|
||||||
cleanPluginName := strings.TrimSpace(pluginName)
|
cleanPluginName := strings.TrimSpace(pluginName)
|
||||||
|
// Plugin name should not be empty at this point due to validation in createCommand
|
||||||
if cleanPluginName == "" {
|
if cleanPluginName == "" {
|
||||||
cleanPluginName = "my-plugin"
|
w.api.Log(ctx, plugin.LogLevelError, "Plugin name is empty")
|
||||||
|
w.api.Notify(ctx, "i18n:plugin_wpm_plugin_name_empty")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptFileName := strings.ReplaceAll(strings.ToLower(cleanPluginName), " ", "-") + fileExtension
|
scriptFileName := strings.ReplaceAll(strings.ToLower(cleanPluginName), " ", "-") + fileExtension
|
||||||
scriptFilePath := path.Join(userScriptPluginDirectory, scriptFileName)
|
scriptFilePath := path.Join(userScriptPluginDirectory, scriptFileName)
|
||||||
|
|
||||||
// Check if file already exists
|
// Check if file already exists and notify user
|
||||||
if _, err := os.Stat(scriptFilePath); err == nil {
|
if _, err := os.Stat(scriptFilePath); err == nil {
|
||||||
w.api.Notify(ctx, fmt.Sprintf("Script plugin file already exists: %s", scriptFileName))
|
w.api.Notify(ctx, fmt.Sprintf("i18n:plugin_wpm_overwriting_script_plugin: %s", scriptFileName))
|
||||||
w.creatingProcess = ""
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace template variables
|
// Replace template variables
|
||||||
|
@ -827,17 +898,48 @@ func (w *WPMPlugin) createScriptPluginWithTemplate(ctx context.Context, template
|
||||||
err = os.WriteFile(scriptFilePath, []byte(templateString), 0755)
|
err = os.WriteFile(scriptFilePath, []byte(templateString), 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to write script file: %s", err.Error()))
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to write script file: %s", err.Error()))
|
||||||
w.creatingProcess = fmt.Sprintf("Failed to create script file: %s", err.Error())
|
w.api.Notify(ctx, fmt.Sprintf("i18n:plugin_wpm_create_script_file_failed: %s", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.creatingProcess = ""
|
// Show success notification
|
||||||
w.api.Notify(ctx, fmt.Sprintf("Script plugin created successfully: %s", scriptFileName))
|
w.api.Notify(ctx, fmt.Sprintf("i18n:plugin_wpm_script_plugin_created_success: %s", scriptFileName))
|
||||||
w.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("Created script plugin: %s", scriptFilePath))
|
w.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("Created script plugin: %s", scriptFilePath))
|
||||||
|
|
||||||
// Change query to show script plugins or open the file
|
// Actively trigger script plugin loading instead of waiting
|
||||||
w.api.ChangeQuery(ctx, common.PlainQuery{
|
util.Go(ctx, "load script plugin immediately", func() {
|
||||||
QueryType: plugin.QueryTypeInput,
|
// Trigger immediate reload of script plugins
|
||||||
QueryText: fmt.Sprintf("%s ", triggerKeyword),
|
pluginManager := plugin.GetPluginManager()
|
||||||
|
|
||||||
|
// Parse the script metadata to get plugin ID
|
||||||
|
metadata, parseErr := pluginManager.ParseScriptMetadata(ctx, scriptFilePath)
|
||||||
|
if parseErr != nil {
|
||||||
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to parse script metadata: %s", parseErr.Error()))
|
||||||
|
w.api.Notify(ctx, fmt.Sprintf("i18n:plugin_wpm_script_plugin_manual_try: %s", triggerKeyword))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create metadata with directory for loading
|
||||||
|
virtualDirectory := path.Join(userScriptPluginDirectory, metadata.Id)
|
||||||
|
metadataWithDirectory := plugin.MetadataWithDirectory{
|
||||||
|
Metadata: metadata,
|
||||||
|
Directory: virtualDirectory,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ReloadPlugin to load the plugin immediately
|
||||||
|
loadErr := pluginManager.ReloadPlugin(ctx, metadataWithDirectory)
|
||||||
|
if loadErr != nil {
|
||||||
|
w.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to load script plugin: %s", loadErr.Error()))
|
||||||
|
w.api.Notify(ctx, fmt.Sprintf("i18n:plugin_wpm_script_plugin_manual_try: %s", triggerKeyword))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("Successfully loaded script plugin: %s", metadata.Name))
|
||||||
|
|
||||||
|
// Change query to the new plugin
|
||||||
|
w.api.ChangeQuery(ctx, common.PlainQuery{
|
||||||
|
QueryType: plugin.QueryTypeInput,
|
||||||
|
QueryText: fmt.Sprintf("%s ", triggerKeyword),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,6 +267,8 @@
|
||||||
"plugin_wpm_command_install": "Install Wox plugins",
|
"plugin_wpm_command_install": "Install Wox plugins",
|
||||||
"plugin_wpm_command_uninstall": "Uninstall Wox plugins",
|
"plugin_wpm_command_uninstall": "Uninstall Wox plugins",
|
||||||
"plugin_wpm_command_create": "Create Wox plugin",
|
"plugin_wpm_command_create": "Create Wox plugin",
|
||||||
|
"plugin_wpm_enter_plugin_name": "Please enter a plugin name",
|
||||||
|
"plugin_wpm_enter_plugin_name_subtitle": "Type the name of the plugin you want to create",
|
||||||
"plugin_wpm_command_dev_list": "List local Wox plugins",
|
"plugin_wpm_command_dev_list": "List local Wox plugins",
|
||||||
"plugin_wpm_command_dev_add": "Add existing Wox plugin directory",
|
"plugin_wpm_command_dev_add": "Add existing Wox plugin directory",
|
||||||
"plugin_wpm_command_dev_remove": "Remove local Wox plugin, followed by a directory",
|
"plugin_wpm_command_dev_remove": "Remove local Wox plugin, followed by a directory",
|
||||||
|
|
|
@ -265,6 +265,8 @@
|
||||||
"plugin_wpm_command_install": "Instalar plugins do Wox",
|
"plugin_wpm_command_install": "Instalar plugins do Wox",
|
||||||
"plugin_wpm_command_uninstall": "Desinstalar plugins do Wox",
|
"plugin_wpm_command_uninstall": "Desinstalar plugins do Wox",
|
||||||
"plugin_wpm_command_create": "Criar plugin do Wox",
|
"plugin_wpm_command_create": "Criar plugin do Wox",
|
||||||
|
"plugin_wpm_enter_plugin_name": "Por favor, digite o nome do plugin",
|
||||||
|
"plugin_wpm_enter_plugin_name_subtitle": "Digite o nome do plugin que você deseja criar",
|
||||||
"plugin_wpm_command_dev_list": "Listar plugins locais do Wox",
|
"plugin_wpm_command_dev_list": "Listar plugins locais do Wox",
|
||||||
"plugin_wpm_command_dev_add": "Adicionar diretório de plugin existente do Wox",
|
"plugin_wpm_command_dev_add": "Adicionar diretório de plugin existente do Wox",
|
||||||
"plugin_wpm_command_dev_remove": "Remover plugin local do Wox, seguido de um diretório",
|
"plugin_wpm_command_dev_remove": "Remover plugin local do Wox, seguido de um diretório",
|
||||||
|
|
|
@ -265,6 +265,8 @@
|
||||||
"plugin_wpm_command_install": "Установить плагины Wox",
|
"plugin_wpm_command_install": "Установить плагины Wox",
|
||||||
"plugin_wpm_command_uninstall": "Удалить плагины Wox",
|
"plugin_wpm_command_uninstall": "Удалить плагины Wox",
|
||||||
"plugin_wpm_command_create": "Создать плагин Wox",
|
"plugin_wpm_command_create": "Создать плагин Wox",
|
||||||
|
"plugin_wpm_enter_plugin_name": "Пожалуйста, введите имя плагина",
|
||||||
|
"plugin_wpm_enter_plugin_name_subtitle": "Введите имя плагина, который вы хотите создать",
|
||||||
"plugin_wpm_command_dev_list": "Список локальных плагинов Wox",
|
"plugin_wpm_command_dev_list": "Список локальных плагинов Wox",
|
||||||
"plugin_wpm_command_dev_add": "Добавить существующий каталог плагинов Wox",
|
"plugin_wpm_command_dev_add": "Добавить существующий каталог плагинов Wox",
|
||||||
"plugin_wpm_command_dev_remove": "Удалить локальный плагин Wox, указав каталог",
|
"plugin_wpm_command_dev_remove": "Удалить локальный плагин Wox, указав каталог",
|
||||||
|
|
|
@ -267,6 +267,8 @@
|
||||||
"plugin_wpm_command_install": "安装 Wox 插件",
|
"plugin_wpm_command_install": "安装 Wox 插件",
|
||||||
"plugin_wpm_command_uninstall": "卸载 Wox 插件",
|
"plugin_wpm_command_uninstall": "卸载 Wox 插件",
|
||||||
"plugin_wpm_command_create": "创建 Wox 插件",
|
"plugin_wpm_command_create": "创建 Wox 插件",
|
||||||
|
"plugin_wpm_enter_plugin_name": "请输入插件名称",
|
||||||
|
"plugin_wpm_enter_plugin_name_subtitle": "输入您要创建的插件名称",
|
||||||
"plugin_wpm_command_dev_list": "列出本地 Wox 插件",
|
"plugin_wpm_command_dev_list": "列出本地 Wox 插件",
|
||||||
"plugin_wpm_command_dev_add": "添加现有的 Wox 插件目录",
|
"plugin_wpm_command_dev_add": "添加现有的 Wox 插件目录",
|
||||||
"plugin_wpm_command_dev_remove": "移除本地 Wox 插件,后跟目录",
|
"plugin_wpm_command_dev_remove": "移除本地 Wox 插件,后跟目录",
|
||||||
|
|
|
@ -13,15 +13,22 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wox Script Plugin Template
|
* Wox Script Plugin Template
|
||||||
*
|
*
|
||||||
* This is a template for creating Wox script plugins.
|
* This is a template for creating Wox script plugins.
|
||||||
* Script plugins are single-file plugins that are executed once per query.
|
* Script plugins are single-file plugins that are executed once per query.
|
||||||
*
|
*
|
||||||
* Communication with Wox is done via JSON-RPC over stdin/stdout.
|
* Communication with Wox is done via JSON-RPC over stdin/stdout.
|
||||||
*
|
*
|
||||||
* Available methods:
|
* Available methods:
|
||||||
* - query: Process user queries and return results
|
* - query: Process user queries and return results
|
||||||
* - action: Handle user selection of a result
|
* - action: Handle user selection of a result
|
||||||
|
*
|
||||||
|
* Available environment variables:
|
||||||
|
* - WOX_DIRECTORY_USER_SCRIPT_PLUGINS: Directory where script plugins are stored
|
||||||
|
* - WOX_DIRECTORY_USER_DATA: User data directory
|
||||||
|
* - WOX_DIRECTORY_WOX_DATA: Wox application data directory
|
||||||
|
* - WOX_DIRECTORY_PLUGINS: Plugin directory
|
||||||
|
* - WOX_DIRECTORY_THEMES: Theme directory
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Parse input from command line or stdin
|
// Parse input from command line or stdin
|
||||||
|
@ -89,30 +96,19 @@ switch (request.method) {
|
||||||
* @param {Object} request - The JSON-RPC request
|
* @param {Object} request - The JSON-RPC request
|
||||||
*/
|
*/
|
||||||
function handleQuery(request) {
|
function handleQuery(request) {
|
||||||
const query = request.params.search || "";
|
// Generate results
|
||||||
|
|
||||||
// Generate results based on the query
|
|
||||||
const results = [
|
const results = [
|
||||||
{
|
{
|
||||||
title: `You searched for: ${query}`,
|
title: "Open Plugin Directory",
|
||||||
subtitle: "This is a template result",
|
subtitle: "Open the script plugins directory in file manager",
|
||||||
score: 100,
|
score: 100,
|
||||||
action: {
|
action: {
|
||||||
id: "example-action",
|
id: "open-plugin-directory",
|
||||||
data: query
|
data: ""
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Another result",
|
|
||||||
subtitle: "With a different action",
|
|
||||||
score: 90,
|
|
||||||
action: {
|
|
||||||
id: "open-url",
|
|
||||||
data: "https://github.com/Wox-launcher/Wox"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Return results
|
// Return results
|
||||||
console.log(JSON.stringify({
|
console.log(JSON.stringify({
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
|
@ -129,28 +125,16 @@ function handleQuery(request) {
|
||||||
*/
|
*/
|
||||||
function handleAction(request) {
|
function handleAction(request) {
|
||||||
const actionId = request.params.id;
|
const actionId = request.params.id;
|
||||||
const actionData = request.params.data;
|
|
||||||
|
|
||||||
// Handle different action types
|
// Handle different action types
|
||||||
switch (actionId) {
|
switch (actionId) {
|
||||||
case "example-action":
|
case "open-plugin-directory":
|
||||||
// Example action that returns a message
|
// Open plugin directory action
|
||||||
console.log(JSON.stringify({
|
console.log(JSON.stringify({
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
result: {
|
result: {
|
||||||
action: "notify",
|
action: "open-directory",
|
||||||
message: `You selected: ${actionData}`
|
path: process.env.WOX_DIRECTORY_USER_SCRIPT_PLUGINS
|
||||||
},
|
|
||||||
id: request.id
|
|
||||||
}));
|
|
||||||
break;
|
|
||||||
case "open-url":
|
|
||||||
// Open URL action
|
|
||||||
console.log(JSON.stringify({
|
|
||||||
jsonrpc: "2.0",
|
|
||||||
result: {
|
|
||||||
action: "open-url",
|
|
||||||
url: actionData
|
|
||||||
},
|
},
|
||||||
id: request.id
|
id: request.id
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -22,47 +22,44 @@ Communication with Wox is done via JSON-RPC over stdin/stdout.
|
||||||
Available methods:
|
Available methods:
|
||||||
- query: Process user queries and return results
|
- query: Process user queries and return results
|
||||||
- action: Handle user selection of a result
|
- action: Handle user selection of a result
|
||||||
|
|
||||||
|
Available environment variables:
|
||||||
|
- WOX_DIRECTORY_USER_SCRIPT_PLUGINS: Directory where script plugins are stored
|
||||||
|
- WOX_DIRECTORY_USER_DATA: User data directory
|
||||||
|
- WOX_DIRECTORY_WOX_DATA: Wox application data directory
|
||||||
|
- WOX_DIRECTORY_PLUGINS: Plugin directory
|
||||||
|
- WOX_DIRECTORY_THEMES: Theme directory
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
def handle_query(params, request_id):
|
def handle_query(params, request_id):
|
||||||
"""
|
"""
|
||||||
Handle query requests
|
Handle query requests
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
params: The parameters from the JSON-RPC request
|
params: The parameters from the JSON-RPC request (unused in this template)
|
||||||
request_id: The ID of the JSON-RPC request
|
request_id: The ID of the JSON-RPC request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A JSON-RPC response with the query results
|
A JSON-RPC response with the query results
|
||||||
"""
|
"""
|
||||||
query = params.get("search", "")
|
# Generate results
|
||||||
|
|
||||||
# Generate results based on the query
|
|
||||||
results = [
|
results = [
|
||||||
{
|
{
|
||||||
"title": f"You searched for: {query}",
|
"title": "Open Plugin Directory",
|
||||||
"subtitle": "This is a template result",
|
"subtitle": "Open the script plugins directory in file manager",
|
||||||
"score": 100,
|
"score": 100,
|
||||||
"action": {
|
"action": {
|
||||||
"id": "example-action",
|
"id": "open-plugin-directory",
|
||||||
"data": query
|
"data": ""
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Another result",
|
|
||||||
"subtitle": "With a different action",
|
|
||||||
"score": 90,
|
|
||||||
"action": {
|
|
||||||
"id": "open-url",
|
|
||||||
"data": "https://github.com/Wox-launcher/Wox"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
# Return results
|
# Return results
|
||||||
return {
|
return {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
|
@ -76,35 +73,24 @@ def handle_query(params, request_id):
|
||||||
def handle_action(params, request_id):
|
def handle_action(params, request_id):
|
||||||
"""
|
"""
|
||||||
Handle action requests
|
Handle action requests
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
params: The parameters from the JSON-RPC request
|
params: The parameters from the JSON-RPC request
|
||||||
request_id: The ID of the JSON-RPC request
|
request_id: The ID of the JSON-RPC request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A JSON-RPC response with the action result
|
A JSON-RPC response with the action result
|
||||||
"""
|
"""
|
||||||
action_id = params.get("id", "")
|
action_id = params.get("id", "")
|
||||||
action_data = params.get("data", "")
|
|
||||||
|
|
||||||
# Handle different action types
|
# Handle different action types
|
||||||
if action_id == "example-action":
|
if action_id == "open-plugin-directory":
|
||||||
# Example action that returns a message
|
# Open plugin directory action
|
||||||
return {
|
return {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"action": "notify",
|
"action": "open-directory",
|
||||||
"message": f"You selected: {action_data}"
|
"path": os.environ.get("WOX_DIRECTORY_USER_SCRIPT_PLUGINS", "")
|
||||||
},
|
|
||||||
"id": request_id
|
|
||||||
}
|
|
||||||
elif action_id == "open-url":
|
|
||||||
# Open URL action
|
|
||||||
return {
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"result": {
|
|
||||||
"action": "open-url",
|
|
||||||
"url": action_data
|
|
||||||
},
|
},
|
||||||
"id": request_id
|
"id": request_id
|
||||||
}
|
}
|
||||||
|
@ -143,7 +129,7 @@ def main():
|
||||||
"id": None
|
"id": None
|
||||||
}))
|
}))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Validate JSON-RPC request
|
# Validate JSON-RPC request
|
||||||
if request.get("jsonrpc") != "2.0":
|
if request.get("jsonrpc") != "2.0":
|
||||||
print(json.dumps({
|
print(json.dumps({
|
||||||
|
@ -156,12 +142,12 @@ def main():
|
||||||
"id": request.get("id")
|
"id": request.get("id")
|
||||||
}))
|
}))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Handle different methods
|
# Handle different methods
|
||||||
method = request.get("method")
|
method = request.get("method")
|
||||||
params = request.get("params", {})
|
params = request.get("params", {})
|
||||||
request_id = request.get("id")
|
request_id = request.get("id")
|
||||||
|
|
||||||
if method == "query":
|
if method == "query":
|
||||||
response = handle_query(params, request_id)
|
response = handle_query(params, request_id)
|
||||||
elif method == "action":
|
elif method == "action":
|
||||||
|
@ -177,7 +163,7 @@ def main():
|
||||||
},
|
},
|
||||||
"id": request_id
|
"id": request_id
|
||||||
}
|
}
|
||||||
|
|
||||||
# Output response
|
# Output response
|
||||||
print(json.dumps(response))
|
print(json.dumps(response))
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -21,6 +21,13 @@
|
||||||
# Available methods:
|
# Available methods:
|
||||||
# - query: Process user queries and return results
|
# - query: Process user queries and return results
|
||||||
# - action: Handle user selection of a result
|
# - action: Handle user selection of a result
|
||||||
|
#
|
||||||
|
# Available environment variables:
|
||||||
|
# - WOX_DIRECTORY_USER_SCRIPT_PLUGINS: Directory where script plugins are stored
|
||||||
|
# - WOX_DIRECTORY_USER_DATA: User data directory
|
||||||
|
# - WOX_DIRECTORY_WOX_DATA: Wox application data directory
|
||||||
|
# - WOX_DIRECTORY_PLUGINS: Plugin directory
|
||||||
|
# - WOX_DIRECTORY_THEMES: Theme directory
|
||||||
|
|
||||||
# Read input from command line or stdin
|
# Read input from command line or stdin
|
||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
|
@ -56,27 +63,18 @@ fi
|
||||||
case "$METHOD" in
|
case "$METHOD" in
|
||||||
"query")
|
"query")
|
||||||
# Handle query request
|
# Handle query request
|
||||||
# Generate results based on the query
|
# Generate results
|
||||||
cat << EOF
|
cat << EOF
|
||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"title": "You searched for: $SEARCH",
|
"title": "Open Plugin Directory",
|
||||||
"subtitle": "This is a template result",
|
"subtitle": "Open the script plugins directory in file manager",
|
||||||
"score": 100,
|
"score": 100,
|
||||||
"action": {
|
"action": {
|
||||||
"id": "example-action",
|
"id": "open-plugin-directory",
|
||||||
"data": "$SEARCH"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "System Information",
|
|
||||||
"subtitle": "Show system information",
|
|
||||||
"score": 90,
|
|
||||||
"action": {
|
|
||||||
"id": "system-info",
|
|
||||||
"data": ""
|
"data": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,31 +87,14 @@ EOF
|
||||||
"action")
|
"action")
|
||||||
# Handle action request
|
# Handle action request
|
||||||
case "$ACTION_ID" in
|
case "$ACTION_ID" in
|
||||||
"example-action")
|
"open-plugin-directory")
|
||||||
# Example action that returns a message
|
# Open plugin directory action
|
||||||
cat << EOF
|
cat << EOF
|
||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"action": "notify",
|
"action": "open-directory",
|
||||||
"message": "You selected: $ACTION_DATA"
|
"path": "$WOX_DIRECTORY_USER_SCRIPT_PLUGINS"
|
||||||
},
|
|
||||||
"id": $ID
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
;;
|
|
||||||
"system-info")
|
|
||||||
# Get system information
|
|
||||||
OS=$(uname -s)
|
|
||||||
HOSTNAME=$(hostname)
|
|
||||||
UPTIME=$(uptime)
|
|
||||||
|
|
||||||
cat << EOF
|
|
||||||
{
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"result": {
|
|
||||||
"action": "notify",
|
|
||||||
"message": "System: $OS\nHostname: $HOSTNAME\nUptime: $UPTIME"
|
|
||||||
},
|
},
|
||||||
"id": $ID
|
"id": $ID
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,8 @@ func (u *uiImpl) isNotifyInToolbar(ctx context.Context, pluginId string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *uiImpl) PickFiles(ctx context.Context, params common.PickFilesParams) []string {
|
func (u *uiImpl) PickFiles(ctx context.Context, params common.PickFilesParams) []string {
|
||||||
|
|
|
@ -48,57 +48,64 @@ class WoxListView<T> extends StatelessWidget {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => ListView.builder(
|
() => AnimatedSwitcher(
|
||||||
shrinkWrap: true,
|
duration: Duration.zero,
|
||||||
controller: controller.scrollController,
|
child: ListView.builder(
|
||||||
physics: const ClampingScrollPhysics(),
|
key: ValueKey(controller.items.length),
|
||||||
itemCount: controller.items.length,
|
shrinkWrap: true,
|
||||||
itemExtent: WoxThemeUtil.instance.getResultListViewHeightByCount(1),
|
controller: controller.scrollController,
|
||||||
itemBuilder: (context, index) {
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
var item = controller.items[index];
|
itemCount: controller.items.length,
|
||||||
return MouseRegion(
|
itemExtent: WoxThemeUtil.instance.getResultListViewHeightByCount(1),
|
||||||
onEnter: (_) {
|
addAutomaticKeepAlives: false,
|
||||||
if (controller.isMouseMoved && !item.value.isGroup) {
|
addRepaintBoundaries: false,
|
||||||
Logger.instance.info(const UuidV4().generate(), "MOUSE: onenter, is mouse moved: ${controller.isMouseMoved}, is group: ${item.value.isGroup}");
|
addSemanticIndexes: false,
|
||||||
controller.updateHoveredIndex(index);
|
itemBuilder: (context, index) {
|
||||||
}
|
var item = controller.items[index];
|
||||||
},
|
return MouseRegion(
|
||||||
onHover: (_) {
|
onEnter: (_) {
|
||||||
if (!controller.isMouseMoved && !item.value.isGroup) {
|
if (controller.isMouseMoved && !item.value.isGroup) {
|
||||||
Logger.instance.info(const UuidV4().generate(), "MOUSE: onHover, is mouse moved: ${controller.isMouseMoved}, is group: ${item.value.isGroup}");
|
Logger.instance.info(const UuidV4().generate(), "MOUSE: onenter, is mouse moved: ${controller.isMouseMoved}, is group: ${item.value.isGroup}");
|
||||||
controller.isMouseMoved = true;
|
controller.updateHoveredIndex(index);
|
||||||
controller.updateHoveredIndex(index);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onExit: (_) {
|
|
||||||
if (!item.value.isGroup && controller.hoveredIndex.value == index) {
|
|
||||||
controller.clearHoveredResult();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
if (!item.value.isGroup) {
|
|
||||||
controller.updateActiveIndex(const UuidV4().generate(), index);
|
|
||||||
controller.onItemActive?.call(const UuidV4().generate(), item.value);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDoubleTap: () {
|
onHover: (_) {
|
||||||
if (!item.value.isGroup) {
|
if (!controller.isMouseMoved && !item.value.isGroup) {
|
||||||
controller.onItemExecuted?.call(const UuidV4().generate(), item.value);
|
Logger.instance.info(const UuidV4().generate(), "MOUSE: onHover, is mouse moved: ${controller.isMouseMoved}, is group: ${item.value.isGroup}");
|
||||||
|
controller.isMouseMoved = true;
|
||||||
|
controller.updateHoveredIndex(index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Obx(
|
onExit: (_) {
|
||||||
() => WoxListItemView(
|
if (!item.value.isGroup && controller.hoveredIndex.value == index) {
|
||||||
item: item.value,
|
controller.clearHoveredResult();
|
||||||
woxTheme: WoxThemeUtil.instance.currentTheme.value,
|
}
|
||||||
isActive: controller.activeIndex.value == index,
|
},
|
||||||
isHovered: controller.hoveredIndex.value == index,
|
child: GestureDetector(
|
||||||
listViewType: listViewType,
|
onTap: () {
|
||||||
|
if (!item.value.isGroup) {
|
||||||
|
controller.updateActiveIndex(const UuidV4().generate(), index);
|
||||||
|
controller.onItemActive?.call(const UuidV4().generate(), item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDoubleTap: () {
|
||||||
|
if (!item.value.isGroup) {
|
||||||
|
controller.onItemExecuted?.call(const UuidV4().generate(), item.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Obx(
|
||||||
|
() => WoxListItemView(
|
||||||
|
item: item.value,
|
||||||
|
woxTheme: WoxThemeUtil.instance.currentTheme.value,
|
||||||
|
isActive: controller.activeIndex.value == index,
|
||||||
|
isHovered: controller.hoveredIndex.value == index,
|
||||||
|
listViewType: listViewType,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -119,6 +119,13 @@ class WoxListController<T> extends GetxController {
|
||||||
|
|
||||||
// Calculate how many items can be displayed in the current viewport
|
// Calculate how many items can be displayed in the current viewport
|
||||||
final viewportHeight = scrollController.position.viewportDimension;
|
final viewportHeight = scrollController.position.viewportDimension;
|
||||||
|
|
||||||
|
// If viewport height is 0 or invalid, skip scrolling (this can happen during initialization)
|
||||||
|
if (viewportHeight <= 0) {
|
||||||
|
Logger.instance.debug(traceId, "Invalid viewport height: $viewportHeight, skipping scroll sync");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final visibleItemCount = (viewportHeight / itemHeight).floor();
|
final visibleItemCount = (viewportHeight / itemHeight).floor();
|
||||||
|
|
||||||
// If all items can be displayed in the viewport, no need to scroll
|
// If all items can be displayed in the viewport, no need to scroll
|
||||||
|
|
Loading…
Reference in New Issue