diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
new file mode 100644
index 0000000..d7202f0
--- /dev/null
+++ b/.idea/go.imports.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app.go b/app.go
index fefa3b8..274a987 100644
--- a/app.go
+++ b/app.go
@@ -2,29 +2,56 @@ package main
import (
"context"
+ "fmt"
"log"
+ "sort"
+ "strings"
"github.com/wailsapp/wails/v2/pkg/runtime"
"lmika.dev/pkg/modash/moslice"
+ "ucl.lmika.dev/ucl"
+ "ucl.lmika.dev/ucl/builtins"
)
// App struct
type App struct {
+ uclInst *ucl.Inst
+
ctx context.Context
}
// NewApp creates a new App application struct
func NewApp() *App {
- return &App{}
+ uclInst := ucl.New(
+ ucl.WithModule(builtins.CSV(nil)),
+ ucl.WithModule(builtins.Fns()),
+ ucl.WithModule(builtins.FS(nil)),
+ ucl.WithModule(builtins.Itrs()),
+ ucl.WithModule(builtins.Lists()),
+ ucl.WithModule(builtins.Log(nil)),
+ ucl.WithModule(builtins.OS()),
+ ucl.WithModule(builtins.Strs()),
+ ucl.WithModule(builtins.Time()),
+ )
+ return &App{
+ uclInst: uclInst,
+ }
}
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
- a.ctx = ctx
+ a.ctx = context.WithValue(ctx, uclInstKey, a.uclInst)
+}
+
+func (a *App) ListProcessors() (resp []ListProcessorsResponse) {
+ for k, v := range TextFilters {
+ resp = append(resp, ListProcessorsResponse{Name: k, Label: v.Label})
+ }
+ sort.Slice(resp, func(i, j int) bool { return resp[i].Label < resp[j].Label })
+ return resp
}
-// Greet returns a greeting for the given name
func (a *App) ProcessText(req ProcessTextRequest) {
filter, ok := TextFilters[req.Action]
if !ok {
@@ -32,23 +59,53 @@ func (a *App) ProcessText(req ProcessTextRequest) {
return
}
- resp, err := moslice.MapWithError(req.Input, func(span TextSpan) (TextSpan, error) {
- outStr, err := filter(span.Text)
- if err != nil {
- return TextSpan{}, err
+ switch {
+ case filter.Analyze != nil:
+ inBfr := strings.Builder{}
+ for _, span := range req.Input {
+ if inBfr.Len() > 0 {
+ inBfr.WriteString("\n")
+ }
+ inBfr.WriteString(span.Text)
}
- return TextSpan{
- Text: outStr,
- Pos: span.Pos,
- Len: span.Len,
- }, nil
- })
- if err != nil {
- log.Printf("Error running filter: %s", err)
- return
- }
- runtime.EventsEmit(a.ctx, "process-text-response", ProcessTextResponse{
- Output: resp,
- })
+ msg, err := filter.Analyze(a.ctx, inBfr.String())
+ if err != nil {
+ runtime.EventsEmit(a.ctx, "set-statusbar-message", SetStatusbarMessage{
+ Message: fmt.Sprintf("Error running analysis: %v", err.Error()),
+ Error: true,
+ })
+ return
+ }
+
+ runtime.EventsEmit(a.ctx, "set-statusbar-message", SetStatusbarMessage{
+ Message: msg,
+ })
+ case filter.Filter != nil:
+ resp, err := moslice.MapWithError(req.Input, func(span TextSpan) (TextSpan, error) {
+ outRes, err := filter.Filter(a.ctx, span.Text)
+ if err != nil {
+ return TextSpan{}, err
+ }
+
+ return TextSpan{
+ Text: outRes.Output,
+ Pos: span.Pos,
+ Len: span.Len,
+ Append: outRes.Append,
+ }, nil
+ })
+ if err != nil {
+ runtime.EventsEmit(a.ctx, "set-statusbar-message", SetStatusbarMessage{
+ Message: fmt.Sprintf("Error running filter: %v", err.Error()),
+ Error: true,
+ })
+ return
+
+ }
+
+ runtime.EventsEmit(a.ctx, "process-text-response", ProcessTextResponse{
+ Output: resp,
+ })
+ }
}
diff --git a/appctx.go b/appctx.go
new file mode 100644
index 0000000..9701f3f
--- /dev/null
+++ b/appctx.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "context"
+
+ "ucl.lmika.dev/ucl"
+)
+
+type uclInstKeyType struct{}
+
+var uclInstKey = uclInstKeyType{}
+
+func uclInstFromContext(ctx context.Context) *ucl.Inst {
+ return ctx.Value(uclInstKey).(*ucl.Inst)
+}
diff --git a/frontend/index.html b/frontend/index.html
index 1ee5df5..4951b76 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -6,21 +6,18 @@
dequoter
-
+
+
+
Cmd+P: open command palette. Shift+Cmd+P: rerun last command.
+
diff --git a/frontend/src/app.css b/frontend/src/app.css
index 2cfbffe..c18533a 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -1,3 +1,26 @@
+#app {
+ display: flex;
+ flex-direction: column;
+}
+
+.editor-mountpoint {
+ flex-grow: 1;
+ flex-shrink: 1;
+}
+
+.status-bar {
+ height: 2em;
+ background-color: rgb(245, 245, 245);
+ border-top: solid thin rgb(200, 200, 200);
+
+ align-content: center;
+ padding-inline: 8px;
+}
+
+.status-bar.deemphasized {
+ opacity: 0.5;
+}
+
.cm-editor {
height: 100%;
width: 100%;
diff --git a/frontend/src/cmplugins.js b/frontend/src/cmplugins.js
index e3c4440..7ee146f 100644
--- a/frontend/src/cmplugins.js
+++ b/frontend/src/cmplugins.js
@@ -37,4 +37,12 @@ export const commandPalette = keymap.of([{
return true;
}
-}]);
\ No newline at end of file
+}, {
+ key: "Cmd-P",
+ run: () => {
+ let event = new CustomEvent('dq-rerunlastcommand');
+ window.dispatchEvent(event);
+
+ return true;
+ }
+}]);
diff --git a/frontend/src/controllers/commands_controller.js b/frontend/src/controllers/commands_controller.js
index 5a04574..622caec 100644
--- a/frontend/src/controllers/commands_controller.js
+++ b/frontend/src/controllers/commands_controller.js
@@ -1,3 +1,5 @@
+import {ListProcessors} from "../../wailsjs/go/main/App";
+
import { Controller } from "@hotwired/stimulus"
import { textProcessor } from "../services.js";
@@ -8,7 +10,16 @@ export class CommandsController extends Controller {
"commandSelect",
];
- connect() {
+ async connect() {
+ this._lastCommand = null;
+
+ let processors = await ListProcessors();
+ processors.forEach((processor) => {
+ let option = document.createElement("option");
+ option.value = processor.name;
+ option.text = processor.label;
+ this.commandSelectTarget.appendChild(option);
+ });
this._options = Array.from(this.commandSelectTarget.options);
}
@@ -29,13 +40,29 @@ export class CommandsController extends Controller {
this.element.close();
}
+ focusSelect(ev) {
+ this.commandSelectTarget.focus();
+ }
+
runCommand(ev) {
ev.preventDefault();
textProcessor.runTextCommand(this.commandSelectTarget.value);
+ this._lastCommand = this.commandSelectTarget.value;
+
this.element.close();
}
+ rerunLastCommand(ev) {
+ ev.preventDefault();
+
+ if (this._lastCommand === null) {
+ return;
+ }
+
+ textProcessor.runTextCommand(this._lastCommand);
+ }
+
_filterOptions(filterText) {
let inputText = filterText.toLowerCase();
diff --git a/frontend/src/controllers/status_controller.js b/frontend/src/controllers/status_controller.js
new file mode 100644
index 0000000..99504f5
--- /dev/null
+++ b/frontend/src/controllers/status_controller.js
@@ -0,0 +1,14 @@
+import { Controller } from "@hotwired/stimulus"
+
+export class StatusController extends Controller {
+ connect() {
+ window.runtime.EventsOn("set-statusbar-message", (data) => {
+ this._setStatusbarMessage(data.message);
+ });
+ }
+
+ _setStatusbarMessage(message) {
+ this.element.textContent = message;
+ this.element.classList.remove("deemphasized");
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/main.js b/frontend/src/main.js
index d313ede..063030a 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -7,11 +7,13 @@ import {Application} from "@hotwired/stimulus";
import {textProcessor} from "./services.js";
import {multiCursorKeymap, commandPalette} from "./cmplugins.js";
+import {StatusController} from "./controllers/status_controller.js";
import {CommandsController} from "./controllers/commands_controller.js";
+
const view = new EditorView({
- parent: document.querySelector("#app"),
+ parent: document.querySelector("#app .editor-mountpoint"),
doc: "",
extensions: [
basicSetup,
@@ -23,6 +25,7 @@ const view = new EditorView({
window.Stimulus = Application.start()
Stimulus.register("commands", CommandsController);
+Stimulus.register("status", StatusController);
textProcessor.setCodeMirrorEditor(view);
diff --git a/frontend/src/services.js b/frontend/src/services.js
index c968a4e..7a149f5 100644
--- a/frontend/src/services.js
+++ b/frontend/src/services.js
@@ -4,11 +4,21 @@ class TextProcessor {
setCodeMirrorEditor(editor) {
this._editor = editor;
window.runtime.EventsOn("process-text-response", (data) => {
- const changes = data.output.map(span => ({
- from: span.pos,
- to: span.pos + span.len,
- insert: span.text,
- }));
+ const changes = data.output.map(span => {
+ if (span.append) {
+ return {
+ from: span.pos + span.len,
+ to: span.pos + span.len,
+ insert: span.text,
+ };
+ } else {
+ return {
+ from: span.pos,
+ to: span.pos + span.len,
+ insert: span.text,
+ };
+ }
+ });
this._editor.dispatch({ changes: changes });
});
}
diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts
index a164699..a381b11 100755
--- a/frontend/wailsjs/go/main/App.d.ts
+++ b/frontend/wailsjs/go/main/App.d.ts
@@ -2,4 +2,6 @@
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
+export function ListProcessors():Promise>;
+
export function ProcessText(arg1:main.ProcessTextRequest):Promise;
diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js
index b8eff0c..f39aa5b 100755
--- a/frontend/wailsjs/go/main/App.js
+++ b/frontend/wailsjs/go/main/App.js
@@ -2,6 +2,10 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
+export function ListProcessors() {
+ return window['go']['main']['App']['ListProcessors']();
+}
+
export function ProcessText(arg1) {
return window['go']['main']['App']['ProcessText'](arg1);
}
diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts
index 0315b64..a1f3071 100755
--- a/frontend/wailsjs/go/models.ts
+++ b/frontend/wailsjs/go/models.ts
@@ -1,9 +1,24 @@
export namespace main {
+ export class ListProcessorsResponse {
+ name: string;
+ label: string;
+
+ static createFrom(source: any = {}) {
+ return new ListProcessorsResponse(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.name = source["name"];
+ this.label = source["label"];
+ }
+ }
export class TextSpan {
text: string;
pos: number;
len: number;
+ append: boolean;
static createFrom(source: any = {}) {
return new TextSpan(source);
@@ -14,6 +29,7 @@ export namespace main {
this.text = source["text"];
this.pos = source["pos"];
this.len = source["len"];
+ this.append = source["append"];
}
}
export class ProcessTextRequest {
diff --git a/go.mod b/go.mod
index 9d0b5a4..47a3602 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,16 @@
module dequoter
-go 1.23.3
-
-toolchain go1.24.3
+go 1.25
require (
github.com/wailsapp/wails/v2 v2.10.2
- lmika.dev/pkg/modash v0.0.0-20250729120720-cdaa1c8abbe3
+ gopkg.in/yaml.v3 v3.0.1
+ lmika.dev/pkg/modash v0.1.0
+ ucl.lmika.dev v0.1.2
)
require (
+ github.com/alecthomas/participle/v2 v2.1.1 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
diff --git a/go.sum b/go.sum
index ea6cc63..7eee4e2 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,9 @@
+github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
+github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
+github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8=
+github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
+github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
+github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -10,8 +16,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
+github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
@@ -34,6 +44,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -77,7 +89,12 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-lmika.dev/pkg/modash v0.0.0-20250729120720-cdaa1c8abbe3 h1:EqYZdM6ui++F2NPdFCjKAboOr14eVTIIiRfngBgg/WA=
-lmika.dev/pkg/modash v0.0.0-20250729120720-cdaa1c8abbe3/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=
+lmika.dev/pkg/modash v0.1.0 h1:fltroSvP0nKj9K0E6G+S9LULvB9Qhj47+SZ2b9v/v/c=
+lmika.dev/pkg/modash v0.1.0/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=
+ucl.lmika.dev v0.1.2 h1:dTqLKGw/pPqE7UrkrJd5qPu2i6BTDzJLaM0cRkJGn6A=
+ucl.lmika.dev v0.1.2/go.mod h1:f5RzeCTyBO+4k6LYFuDkwGRujnj4/4ONM60AEtQj02k=
diff --git a/models.go b/models.go
index 06f33cf..3734a75 100644
--- a/models.go
+++ b/models.go
@@ -1,9 +1,10 @@
package main
type TextSpan struct {
- Text string `json:"text"`
- Pos int `json:"pos"`
- Len int `json:"len"`
+ Text string `json:"text"`
+ Pos int `json:"pos"`
+ Len int `json:"len"`
+ Append bool `json:"append"`
}
type ProcessTextRequest struct {
@@ -11,6 +12,16 @@ type ProcessTextRequest struct {
Input []TextSpan `json:"input"`
}
+type ListProcessorsResponse struct {
+ Name string `json:"name"`
+ Label string `json:"label"`
+}
+
+type SetStatusbarMessage struct {
+ Message string `json:"message"`
+ Error bool `json:"error"`
+}
+
type ProcessTextResponse struct {
Output []TextSpan `json:"output"`
}
diff --git a/textfilters.go b/textfilters.go
index 10fcf53..5381747 100644
--- a/textfilters.go
+++ b/textfilters.go
@@ -3,44 +3,185 @@ package main
import (
"bufio"
"bytes"
+ "context"
"encoding/json"
+ "errors"
+ "fmt"
"strconv"
"strings"
+
+ "gopkg.in/yaml.v3"
+ "ucl.lmika.dev/ucl"
)
-type TextFilter func(input string) (output string, err error)
+type TextProcessor struct {
+ Label string
+ Description string
-var TextFilters = map[string]TextFilter{
- "upper-case": func(input string) (output string, err error) {
- return strings.ToUpper(input), nil
- },
- "lower-case": func(input string) (output string, err error) {
- return strings.ToLower(input), nil
- },
- "unquote": func(input string) (output string, err error) {
- return strconv.Unquote(input)
- },
- "format-json": func(input string) (output string, err error) {
- var dst bytes.Buffer
+ // Filters the supplied text. This is used for manipulating the text input.
+ Filter TextFilter
- scnr := bufio.NewScanner(strings.NewReader(input))
- for scnr.Scan() {
- line := scnr.Text()
- if err := json.Indent(&dst, []byte(line), "", " "); err == nil {
- dst.WriteString("\n")
- } else {
+ // Analyses the supplied text. This is used for extracting information from the text input. The result
+ // will be displayed in the status bar. Multiple selected regions will be returned as a single string separated by line numbers.
+ Analyze TextAnalyzer
+}
+
+type TextFilter func(ctx context.Context, input string) (resp TextFilterResponse, err error)
+type TextAnalyzer func(ctx context.Context, input string) (resp string, err error)
+
+type TextFilterResponse struct {
+ Output string
+ Append bool
+}
+
+var TextFilters = map[string]TextProcessor{
+ "upper-case": {
+ Label: "String: To Upper Case",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ return TextFilterResponse{Output: strings.ToUpper(input)}, nil
+ },
+ },
+ "lower-case": {
+ Label: "String: To Lower Case",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ return TextFilterResponse{Output: strings.ToLower(input)}, nil
+ },
+ },
+ "unquote": {
+ Label: "String: Unquote",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ out, err := strconv.Unquote(input)
+ if err != nil {
+ return TextFilterResponse{}, err
+ }
+ return TextFilterResponse{Output: out}, nil
+ },
+ },
+ "join-lines-with-commas": {
+ Label: "Lines: Join with Commas",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ return TextFilterResponse{Output: strings.Replace(input, "\n", ",", -1)}, nil
+ },
+ },
+ "split-lines-on-commas": {
+ Label: "Lines: Split on Commas",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ return TextFilterResponse{Output: strings.Replace(input, ",", "\n", -1)}, nil
+ },
+ },
+ "trim-space": {
+ Label: "Lines: Trim Spaces",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ lines := strings.Split(input, "\n")
+ for i, line := range lines {
+ lines[i] = strings.TrimSpace(line)
+ }
+ result := strings.Join(lines, "\n")
+ return TextFilterResponse{Output: result}, nil
+ },
+ },
+ "format-json": {
+ Label: "JSON: Format",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ var dst bytes.Buffer
+
+ scnr := bufio.NewScanner(strings.NewReader(input))
+ for scnr.Scan() {
+ line := scnr.Text()
+ if err := json.Indent(&dst, []byte(line), "", " "); err == nil {
+ dst.WriteString("\n")
+ } else {
+ return TextFilterResponse{}, err
+ }
+ }
+
+ return TextFilterResponse{Output: dst.String()}, nil
+ },
+ },
+ "convert-json-to-yaml": {
+ Label: "Convert: JSON to YAML",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ var data interface{}
+ if err := json.Unmarshal([]byte(input), &data); err != nil {
+ return TextFilterResponse{}, err
+ }
+
+ var dst bytes.Buffer
+ if err := yaml.NewEncoder(&dst).Encode(data); err != nil {
+ return TextFilterResponse{}, err
+ }
+
+ return TextFilterResponse{Output: dst.String()}, nil
+ },
+ },
+ "convert-yaml-to-json": {
+ Label: "Convert: YAML to JSON",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ var data interface{}
+ if err := yaml.Unmarshal([]byte(input), &data); err != nil {
+ return TextFilterResponse{}, err
+ }
+
+ jsonBytes, err := json.MarshalIndent(data, "", " ")
+ if err != nil {
+ return TextFilterResponse{}, err
+ }
+
+ return TextFilterResponse{Output: string(jsonBytes)}, nil
+ },
+ },
+ "lorem-ipsum": {
+ Label: "Generate: Lorem Ipsum",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ return TextFilterResponse{
+ Output: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " +
+ "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " +
+ "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
+ "dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " +
+ "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " +
+ "mollit anim id est laborum.",
+ Append: true,
+ }, nil
+ },
+ },
+
+ "lines": {
+ Label: "Lines: Count",
+ Analyze: func(ctx context.Context, input string) (result string, err error) {
+ lineCount := len(strings.Split(input, "\n"))
+ return fmt.Sprintf("Lines = %v", lineCount), nil
+ },
+ },
+
+ "ucl-evaluate": {
+ Label: "UCL: Evaluate",
+ Description: "Evaluates the input as a UCL expression and displays the result in the status bar.",
+ Analyze: func(ctx context.Context, input string) (result string, err error) {
+ res, err := uclInstFromContext(ctx).EvalString(ctx, input)
+ if err != nil {
+ if errors.Is(err, ucl.ErrNotConvertable) {
+ return "Evaluated successfully. No result.", err
+ }
return "", err
}
- }
- return dst.String(), nil
+ return fmt.Sprintf("Result: %v", res), nil
+ },
},
- "lorem-ipsum": func(input string) (output string, err error) {
- return "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " +
- "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " +
- "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
- "dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " +
- "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " +
- "mollit anim id est laborum.", nil
+
+ "ucl-replace": {
+ Label: "UCL: Replace",
+ Description: "Evaluates the input as a UCL expression and replaces it with the result.",
+ Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
+ res, err := uclInstFromContext(ctx).EvalString(ctx, input)
+ if err != nil {
+ if errors.Is(err, ucl.ErrNotConvertable) {
+ return TextFilterResponse{}, err
+ }
+ return TextFilterResponse{}, err
+ }
+
+ return TextFilterResponse{Output: fmt.Sprint(res)}, nil
+ },
},
}