Compare commits

..

2 commits

Author SHA1 Message Date
5f221939e3 Add Cmd+L to rerun last command on current line(s)
Co-authored-by: Shelley <shelley@exe.dev>
2026-05-09 12:22:25 +00:00
7c9023ae20 Reuse last prompt value when rerunning last command
Co-authored-by: Shelley <shelley@exe.dev>
2026-05-06 04:13:44 +00:00
7 changed files with 69 additions and 80 deletions

View file

@ -21,14 +21,12 @@ jobs:
- name: Running Wails doctor - name: Running Wails doctor
run: | run: |
wails doctor wails doctor
- name: Installing Dependencies
run: |
npm install
- name: Build - name: Build
run: | run: |
npm install
wails build -clean -platform darwin/arm64 -ldflags "-X main.VersionNumber=`git describe --tags --abbrev=0`" wails build -clean -platform darwin/arm64 -ldflags "-X main.VersionNumber=`git describe --tags --abbrev=0`"
- name: Release - name: Release
uses: https://lmika.dev/actions/wails-release@v1.0.4 uses: https://lmika.dev/actions/wails-release@v1.0.3
with: with:
developer-id-cert-base64: ${{ secrets.MACOS_SIGN_P12 }} developer-id-cert-base64: ${{ secrets.MACOS_SIGN_P12 }}
developer-id-cert-password: ${{ secrets.MACOS_SIGN_PASSWORD }} developer-id-cert-password: ${{ secrets.MACOS_SIGN_PASSWORD }}
@ -37,7 +35,7 @@ jobs:
notarization-api-issuer-id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} notarization-api-issuer-id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
extra-build-flags: -ldflags "-X main.VersionNumber=${{ github.ref_name }}" extra-build-flags: -ldflags "-X main.VersionNumber=${{ github.ref_name }}"
s3-bucket: lmika-public-files s3-bucket: lmika-public-files
s3-key: 'Apps/Dequoter/{version}/{filename},Apps/Dequoter/latest/{filename}' s3-key: Apps/Dequoter/{version}/{filename}
s3-region: ap-southeast-2 s3-region: ap-southeast-2
s3-acl: public-read s3-acl: public-read
env: env:

View file

@ -20,7 +20,7 @@
</div> </div>
</dialog> </dialog>
<dialog id="command-dialog" data-controller="commands" <dialog id="command-dialog" data-controller="commands"
data-action="dq-showcommands@window->commands#showCommands dq-rerunlastcommand@window->commands#rerunLastCommand"> data-action="dq-showcommands@window->commands#showCommands dq-rerunlastcommand@window->commands#rerunLastCommand dq-rerunlastcommand-line@window->commands#rerunLastCommandOnLine">
<div class="dialog-body"> <div class="dialog-body">
<div class="command-input"> <div class="command-input">
<input data-commands-target="commandInput" type="text" placeholder="Enter command" <input data-commands-target="commandInput" type="text" placeholder="Enter command"

View file

@ -46,6 +46,14 @@ export const commandPalette = keymap.of([{
return true; return true;
} }
}, { }, {
key: "Cmd-l",
run: () => {
let event = new CustomEvent('dq-rerunlastcommand-line');
window.dispatchEvent(event);
return true;
}
}, {
key: "Cmd-k", key: "Cmd-k",
run: (view) => { run: (view) => {
const {state} = view; const {state} = view;

View file

@ -10,6 +10,10 @@ export class CommandsController extends Controller {
"commandSelect", "commandSelect",
]; ];
initialize() {
this._lineModeOnce = false;
}
async connect() { async connect() {
this._lastCommand = null; this._lastCommand = null;
@ -47,6 +51,7 @@ export class CommandsController extends Controller {
runCommand(ev) { runCommand(ev) {
ev.preventDefault(); ev.preventDefault();
this._promptController()?.clearUseLastValue();
textProcessor.runTextCommand(this.commandSelectTarget.value); textProcessor.runTextCommand(this.commandSelectTarget.value);
this._lastCommand = this.commandSelectTarget.value; this._lastCommand = this.commandSelectTarget.value;
@ -60,7 +65,21 @@ export class CommandsController extends Controller {
return; return;
} }
textProcessor.runTextCommand(this._lastCommand); this._promptController()?.useLastValueForNextPrompt();
const lineMode = this._lineModeOnce;
this._lineModeOnce = false;
textProcessor.runTextCommand(this._lastCommand, { lineMode });
}
rerunLastCommandOnLine(ev) {
this._lineModeOnce = true;
this.rerunLastCommand(ev);
}
_promptController() {
const el = document.getElementById("prompt-dialog");
if (!el) return null;
return this.application.getControllerForElementAndIdentifier(el, "prompt");
} }
_filterOptions(filterText) { _filterOptions(filterText) {

View file

@ -8,8 +8,16 @@ export class PromptController extends Controller {
connect() { connect() {
this._callback = null; this._callback = null;
this._lastValue = null;
this._useLastNext = false;
window.runtime.EventsOn("prompt-request", (data) => { window.runtime.EventsOn("prompt-request", (data) => {
if (this._useLastNext && this._lastValue !== null) {
this._useLastNext = false;
window.runtime.EventsEmit("prompt-response", this._lastValue);
return;
}
this._useLastNext = false;
this.prompt(data.label, (res) => { this.prompt(data.label, (res) => {
window.runtime.EventsEmit("prompt-response", res); window.runtime.EventsEmit("prompt-response", res);
}); });
@ -19,14 +27,16 @@ export class PromptController extends Controller {
prompt(label, callback) { prompt(label, callback) {
this._callback = callback; this._callback = callback;
this.labelTarget.textContent = label; this.labelTarget.textContent = label;
this.inputTarget.value = ""; this.inputTarget.value = this._lastValue || "";
this.element.showModal(); this.element.showModal();
this.inputTarget.select();
this.inputTarget.focus(); this.inputTarget.focus();
} }
submit(ev) { submit(ev) {
ev.preventDefault(); ev.preventDefault();
let value = this.inputTarget.value; let value = this.inputTarget.value;
this._lastValue = value;
this.element.close(); this.element.close();
if (this._callback) { if (this._callback) {
this._callback(value); this._callback(value);
@ -39,4 +49,12 @@ export class PromptController extends Controller {
this._callback = null; this._callback = null;
this.element.close(); this.element.close();
} }
useLastValueForNextPrompt() {
this._useLastNext = true;
}
clearUseLastValue() {
this._useLastNext = false;
}
} }

View file

@ -46,16 +46,31 @@ class TextProcessor {
}); });
} }
async runTextCommand(command) { async runTextCommand(command, opts) {
if (this._editor === undefined) { if (this._editor === undefined) {
return; return;
} }
const lineMode = opts && opts.lineMode;
let ranges = this._editor.state.selection.ranges; let ranges = this._editor.state.selection.ranges;
let shouldBeAll = ranges.reduce((a, r) => a && r.from === r.to, true); let hasSelection = ranges.some(r => r.from !== r.to);
let inputs = []; let inputs = [];
if (shouldBeAll) { if (lineMode && !hasSelection) {
this._appendInsertPos = undefined;
const doc = this._editor.state.doc;
const seen = new Set();
for (let r of ranges) {
const line = doc.lineAt(r.head);
if (seen.has(line.number)) continue;
seen.add(line.number);
inputs.push({
text: line.text,
pos: line.from,
len: line.to - line.from,
});
}
} else if (!hasSelection) {
this._appendInsertPos = this._editor.state.selection.main.head; this._appendInsertPos = this._editor.state.selection.main.head;
inputs.push({ inputs.push({
text: this._editor.state.doc.toString(), text: this._editor.state.doc.toString(),

View file

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -124,32 +123,6 @@ var TextFilters = map[string]TextProcessor{
return TextFilterResponse{Output: result}, nil return TextFilterResponse{Output: result}, nil
}, },
}, },
"compact-json": {
Label: "JSON: Compact",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
var (
dst bytes.Buffer
inBfr bytes.Buffer
outBfr bytes.Buffer
)
scnr := bufio.NewScanner(strings.NewReader(input))
for scnr.Scan() {
line := scnr.Text()
inBfr.WriteString(line)
inBfr.WriteString("\n")
if err := json.Compact(&outBfr, inBfr.Bytes()); err == nil {
dst.WriteString(outBfr.String())
dst.WriteString("\n")
inBfr.Reset()
outBfr.Reset()
}
}
return TextFilterResponse{Output: dst.String() + inBfr.String()}, nil
},
},
"format-json": { "format-json": {
Label: "JSON: Format", Label: "JSON: Format",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) { Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
@ -200,48 +173,6 @@ var TextFilters = map[string]TextProcessor{
return TextFilterResponse{Output: string(jsonBytes)}, nil return TextFilterResponse{Output: string(jsonBytes)}, nil
}, },
}, },
"jwt-decode": {
Label: "JWT: Decode",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
tokens := strings.Split(input, ".")
if len(tokens) != 3 {
return TextFilterResponse{}, errors.New("invalid JWT format")
}
header, err := base64.RawURLEncoding.DecodeString(tokens[0])
if err != nil {
return TextFilterResponse{}, err
}
payload, err := base64.RawURLEncoding.DecodeString(tokens[1])
if err != nil {
return TextFilterResponse{}, err
}
var headerJSON, payloadJSON bytes.Buffer
json.Indent(&headerJSON, header, "", " ")
json.Indent(&payloadJSON, payload, "", " ")
return TextFilterResponse{Output: headerJSON.String() + "\n" + payloadJSON.String()}, nil
},
},
"base64-decode": {
Label: "Base64: Decode",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
dst, err := base64.StdEncoding.DecodeString(input)
if err != nil {
return TextFilterResponse{}, err
}
return TextFilterResponse{Output: string(dst)}, nil
},
},
"base64-encode": {
Label: "Base64: Encode",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
dst := base64.StdEncoding.EncodeToString([]byte(input))
return TextFilterResponse{Output: dst}, nil
},
},
"lorem-ipsum": { "lorem-ipsum": {
Label: "Generate: Lorem Ipsum", Label: "Generate: Lorem Ipsum",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) { Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {