Files
Spark 348036daf3 feat(gui): Quick Picker, Gestionnaire, Éditeur, formulaire de variables, Paramètres
- Picker : recherche instantanée, aperçu, navigation clavier complète,
  raccourcis actifs seulement dans sa fenêtre (HotIfWinActive)
- Gestionnaire : arbre des catégories, favoris/récents/erreurs, aperçu,
  création depuis le presse-papiers, suppression par la corbeille
- Éditeur : métadonnées, insertion/déclaration de variables synchronisées,
  validation en direct, Tester (prévisualisation résolue, sans injection)
- VarForm : un contrôle par type de variable, requis validés, Échap annule
  toute l'injection et rend le focus
2026-10-11 10:17:30 -04:00

251 lines
10 KiB
AutoHotkey

; Editor.ahk — Éditeur de prompt intégré (architecture §9.2).
; Métadonnées + corps + déclaration des variables, validation en direct, bouton Tester.
class PromptEditor {
GuiObj := ""
Item := "" ; PromptItem en cours d'édition ("" = nouveau, créé à l'enregistrement)
PendingVars := [] ; déclarations de variables en cours
F := Map() ; contrôles nommés
OnSaved := "" ; Func(item)
Show(item, onSaved := "") {
this.Item := item
this.OnSaved := onSaved
this.PendingVars := []
if (item != "" && item.Meta.Has("variables")) {
for v in item.Meta["variables"]
this.PendingVars.Push(v)
}
if (this.GuiObj != "")
this.GuiObj.Destroy()
g := Gui("-MaximizeBox +Resize", item = "" ? "Nouveau prompt" : "Modifier — " item.Title)
g.SetFont("s10", "Segoe UI")
this.GuiObj := g
F := this.F
g.Add("Text", "xm w90", "Titre :")
F["title"] := g.Add("Edit", "x+5 w420", item != "" ? item.Title : "")
g.Add("Text", "xm w90", "Catégorie :")
cats := App.Store.Categories()
F["category"] := g.Add("ComboBox", "x+5 w420", cats)
F["category"].Text := item != "" ? item.Category : "Sans catégorie"
g.Add("Text", "xm w90", "Étiquettes :")
F["tags"] := g.Add("Edit", "x+5 w420", item != "" ? Join(item.Tags, ", ") : "")
g.Add("Text", "xm w90", "Profils :")
F["targets"] := g.Add("Edit", "x+5 w420", item != "" ? Join(item.Targets, ", ") : "")
F["favorite"] := g.Add("CheckBox", "xm", "Favori ★")
F["favorite"].Value := item != "" ? item.Favorite : 0
F["pinned"] := g.Add("Edit", "x+30 w40 Number", item != "" ? item.Pinned : 0)
g.Add("Text", "x+5", "Épinglé n° (0-9)")
F["sensitive"] := g.Add("CheckBox", "x+30", "Sensible (vider le presse-papiers après)")
F["sensitive"].Value := item != "" ? item.Sensitive : 0
g.Add("Text", "xm w90", "Raccourci :")
F["hotkey"] := g.Add("Edit", "x+5 w200", item != "" ? item.Hotkey : "")
g.Add("Text", "x+15 w90", "Hotstring :")
F["hotstring"] := g.Add("Edit", "x+5 w200", item != "" ? item.Hotstring : "")
g.Add("Text", "xm", "Corps du prompt ({{variable}} pour un emplacement, Ctrl+J pour insérer) :")
F["body"] := g.Add("Edit", "xm w760 r16 -Wrap", item != "" ? item.Body : "")
F["varsInfo"] := g.Add("Text", "xm w760", "")
this.RefreshVarsInfo()
btnVar := g.Add("Button", "xm w200", "Insérer une variable (Ctrl+J)")
btnVar.OnEvent("Click", (*) => this.InsertVariable())
btnDeclare := g.Add("Button", "x+10 w200", "Déclarer une variable…")
btnDeclare.OnEvent("Click", (*) => this.DeclareVariable())
btnTest := g.Add("Button", "x+10 w140", "Tester")
btnTest.OnEvent("Click", (*) => this.TestPrompt())
btnExt := g.Add("Button", "xm w200", "Ouvrir dans l'éditeur externe")
btnExt.OnEvent("Click", (*) => this.OpenExternal())
btnSave := g.Add("Button", "x+10 w160 Default", "Enregistrer")
btnSave.OnEvent("Click", (*) => this.Save())
btnCancel := g.Add("Button", "x+10 w120", "Annuler")
btnCancel.OnEvent("Click", (*) => this.GuiObj.Destroy())
g.OnEvent("Close", (*) => this.GuiObj.Destroy())
; Ctrl+J dans l'éditeur.
HotIfWinActive("ahk_id " g.Hwnd)
Hotkey("^j", (*) => this.InsertVariable(), "On")
HotIfWinActive()
g.Show("w800")
}
RefreshVarsInfo() {
names := []
for v in this.PendingVars {
if v.Has("name")
names.Push(v["name"] " (" (v.Has("type") ? v["type"] : "text") ")")
}
used := VariableEngine.FindVars(this.F["body"].Value)
warns := []
for u in used {
if VariableEngine.IsAuto(u)
continue
declared := false
for v in this.PendingVars {
if (v.Has("name") && StrLower(String(v["name"])) = u) {
declared := true
break
}
}
if !declared
warns.Push("« " u " » utilisé mais non déclaré (traité comme texte libre)")
}
txt := "Variables déclarées : " (names.Length ? Join(names, ", ") : "aucune")
if (warns.Length)
txt .= " · ⚠ " Join(warns, " ; ")
this.F["varsInfo"].Text := txt
}
InsertVariable() {
ib := InputBox("Nom de la variable à insérer (ex. ton, langage) :", "Insérer une variable")
if (ib.Result != "OK" || Trim(ib.Value) = "")
return
name := StrLower(Trim(ib.Value))
body := this.F["body"]
body.Focus()
SendText("{{" name "}}")
; Si elle n'est pas déclarée, proposer de la déclarer.
for v in this.PendingVars {
if (v.Has("name") && StrLower(String(v["name"])) = name)
return
}
r := MsgBox("La variable « " name " » n'est pas déclarée. La déclarer maintenant ?", "PromptKey", "YesNo")
if (r = "Yes")
this.DeclareVariable(name)
this.RefreshVarsInfo()
}
DeclareVariable(presetName := "") {
ibName := InputBox("Nom de la variable :", "Déclarer une variable", , presetName)
if (ibName.Result != "OK" || Trim(ibName.Value) = "")
return
name := StrLower(Trim(ibName.Value))
ibLabel := InputBox("Libellé affiché dans le formulaire :", "Déclarer une variable", , name)
if (ibLabel.Result != "OK")
return
ibType := InputBox("Type (text, multiline, enum, number, date, bool, password) :", "Déclarer une variable", , "text")
if (ibType.Result != "OK")
return
type := StrLower(Trim(ibType.Value))
v := Map("name", name, "label", ibLabel.Value, "type", type)
if (type = "enum") {
ibChoices := InputBox("Choix, séparés par des virgules :", "Déclarer une variable")
if (ibChoices.Result != "OK")
return
choices := []
for c in StrSplit(ibChoices.Value, ",")
choices.Push(Trim(c))
v["choices"] := choices
if (choices.Length)
v["default"] := choices[1]
} else {
ibDef := InputBox("Valeur par défaut (optionnel) :", "Déclarer une variable")
if (ibDef.Result = "OK" && ibDef.Value != "")
v["default"] := ibDef.Value
}
; Remplacer une déclaration existante du même nom.
newVars := []
for existing in this.PendingVars {
if !(existing.Has("name") && StrLower(String(existing["name"])) = name)
newVars.Push(existing)
}
newVars.Push(v)
this.PendingVars := newVars
this.RefreshVarsInfo()
}
; Prévisualisation du prompt résolu avec les valeurs par défaut (aucune injection, §9.2).
TestPrompt() {
item := this.CollectToItem(false)
if (item = "")
return
defs := VariableEngine.ManualVars(item)
defaults := Map()
for d in defs
defaults[d["name"]] := d["default"]
ctx := Map("selection", "(texte sélectionné d'exemple)", "clipboardText", "", "process", "application")
resolved := VariableEngine.Resolve(item.Body, defaults, ctx)
g := Gui("+AlwaysOnTop", "Aperçu — " item.Title)
g.SetFont("s10", "Segoe UI")
g.Add("Edit", "w640 r18 ReadOnly -Wrap", resolved["text"])
if (resolved["unresolved"].Length)
g.Add("Text", "w640 cRed", "Variables non résolues : " Join(resolved["unresolved"], ", "))
btn := g.Add("Button", "Default w120", "Fermer")
btn.OnEvent("Click", (*) => g.Destroy())
g.OnEvent("Escape", (*) => g.Destroy())
g.Show()
}
OpenExternal() {
item := this.Item
if (item = "" || item.Path = "") {
TrayTip("Enregistrez d'abord le prompt pour l'ouvrir dans l'éditeur externe.", "PromptKey")
return
}
editor := App.Settings.ExternalEditor
try {
if (editor != "")
Run(editor " `"" item.Path "`"")
else
Run(item.Path)
} catch as e {
MsgBox("Ouverture impossible : " e.Message, "PromptKey")
}
}
; Construit / met à jour le PromptItem depuis les contrôles. Crée le fichier seulement si save=true et nouveau.
CollectToItem(save) {
F := this.F
title := Trim(F["title"].Value)
if (title = "") {
MsgBox("Le titre est requis.", "PromptKey")
return ""
}
item := this.Item
if (item = "") {
if !save
item := PromptItem("", Trim(F["category"].Text), Map("meta", Map(), "body", "", "errors", [], "hasHeader", false))
else
item := App.Store.CreateNew(title, Trim(F["category"].Text), F["body"].Value)
}
item.Title := title
item.Category := Trim(F["category"].Text)
item.Tags := SplitCsv(F["tags"].Value)
item.Targets := SplitCsv(F["targets"].Value)
item.Favorite := F["favorite"].Value
item.Pinned := F["pinned"].Value != "" ? Integer(F["pinned"].Value) : 0
item.Sensitive := F["sensitive"].Value
item.Hotkey := Trim(F["hotkey"].Value)
item.Hotstring := Trim(F["hotstring"].Value)
item.Body := F["body"].Value
if (this.PendingVars.Length)
item.Meta["variables"] := this.PendingVars
else if item.Meta.Has("variables")
item.Meta.Delete("variables")
return item
}
Save() {
item := this.CollectToItem(true)
if (item = "")
return
App.Store.Save(item)
this.Item := item
TrayTip("Prompt « " item.Title " » enregistré.", "PromptKey")
this.GuiObj.Destroy()
App.RefreshAfterLibraryChange()
if (this.OnSaved != "")
this.OnSaved(item)
}
}
SplitCsv(s) {
out := []
for part in StrSplit(s, ",") {
part := Trim(part)
if (part != "")
out.Push(part)
}
return out
}