Fork me on GitHub
#joyride
<
2022-10-11
>
dumrat10:10:22

Hi, I'm just curious. Is it possible to edit text in the editor with joyride? What I want is to type English and to convert that text to a different language (On a keypress - like space/tab/enter, etc).

borkdude10:10:02

Is there a VSCode API for translating text? Or perhaps you can call an API? cc @pez

pez10:10:36

Yes, entirely possible to edit text. Check out https://github.com/BetterThanTomorrow/joyride/blob/master/examples/.joyride/scripts/ignore_form.cljs for some inspiration. It uses https://github.com/BetterThanTomorrow/joyride/blob/master/examples/.joyride/scripts/z_joylib/editor_utils.cljs which also should be regarded as inspiration. (I think I would use .change on the edit builder rather than combining delete and insert.)

👍 1
dumrat10:10:10

@pez Thanks will check it out

pez10:10:28

If you want to update the examples with something that changes text, please do! I think this is on of the most general usages of Joyride there is.

👍 1
pez10:10:38

Also, if it is Clojure code you want to edit, consider using Calva's editor.replace API: https://calva.io/api/#editorreplace Where it could be good to know that the editor.currentForm API call gives the selection if there is one. (Calva treats any selection as the current form). https://calva.io/api/#rangescurrentform

dumrat10:10:34

@pez No, I do not want to edit Clojure code. All I want to do is transliterate English to Sinhala. It should work in the background replacing word by word without any key commands. Let me go through the samples. Looks promising. I was afraid I would have to write a VS extension. If I can get it done without that, I'd be happy.

🎉 2
dumrat12:10:59

@pez Is it possible to add other libs and use within the user script? I would like to use instaparse - otherwise I'd have to write the parser by hand.

pez12:10:17

I think our reach into the Clojure ecosystem is a bit limited. @U04V15CAJ knows more about this.

dumrat12:10:36

@pez, makes sense. Can I invoke a http request from a script then? That's the second option.

borkdude12:10:32

you can do anything that the VSCode API (and Node.js) allows you to do

dumrat12:10:21

ok let me dig more then

pez12:10:41

Maybe npm has a parser you can use?

borkdude12:10:52

@pez I don't think we have solved "loading a library from npm in joyride" yet?

pez12:10:47

Haha, maybe not. I haven't tried, tbh.

pez12:10:08

Can nbb do it?

pez12:10:19

So that should mean we know how to solve it in Joyride, right?

borkdude12:10:02

I think so. For workspace scripts, the joyride script should be able to load npm deps from the workspace.

borkdude12:10:31

We might first want to move to sci.async though since loading those deps happens asynchronously

borkdude12:10:47

although you could do it async with promesa as well

borkdude12:10:53

in user space

borkdude12:10:16

hmm, electron and ES6, this makes it more complicated though

borkdude12:10:46

it's at least worth a try, first in user space, then in joyride itself

borkdude13:10:48

so it seems we don't have to move to sci.async probably since es6 modules aren't supported in electron anyway

borkdude13:10:01

We could just load libraries via js/require synchronously

borkdude13:10:44

@pez What I do in nbb is use createRequire which receives the path of the script and then I call .resolve on that which returns the JS file of the library and then I load that using js/require

borkdude13:10:04

(well, I used to do that, now it's a bit more complicated, but this is how it should work in joyride / electron I think)

borkdude13:10:54

So in user space:

$ nbb
Welcome to nbb v1.0.136!
user=> (require '["module" :as m])
nil
user=> (require '["path" :as p])
nil
user=> (def req (m/createRequire (p/resolve "./script.cljs")))
#'user/req
user=> (.resolve req "squint-cljs")
"/Users/borkdude/dev/squint/index.js"

borkdude13:10:13

And then you can do (js/require resolved) iff the resolved file is not an ES6 module ;)

borkdude13:10:57

In joyride we have joyride.core/**file** which you can use as the script location

borkdude13:10:13

if I remember correctly

pez13:10:30

This would make Joyride close the gap to full extensions quite considerably, I think.

borkdude13:10:04

That should already work today

borkdude13:10:37

I'll try it locally

borkdude13:10:18

how do I make a top level folder in vscode in that left pane... dang, it creates it in a subfolder

borkdude13:10:56

I'll just use the terminal... fucking guis ;)

borkdude13:10:37

When I print in a joyride script, where do I see the output?

borkdude13:10:30

of course, in the developer tools

borkdude13:10:50

Hmm, it seems vscode itself doesn't expose require?

(js/eval "require")

borkdude13:10:57

=> require is not defined

borkdude13:10:53

Perhaps we can fix that in joyride to begin with

borkdude13:10:52

(with a local build of joyride)

borkdude13:10:22

I think we can support this in our load-fn

borkdude14:10:43

@pez I have one problem that you might be able to help with. joyride.core/*file* isn't bound in the REPL, but it should. I've tried a bunch of things but I'm getting stackoverflows presumably due to debug logs in the nREPL or so

borkdude14:10:56

let me put my work in a branch

borkdude14:10:27

ok, wait, it works :))))

pez14:10:12

Sorry for being so irresponsive. Been in a meeting (in a #CBE668G4R meeting as it happens 😄 )

pez15:10:33

So amazing and fantastic! joyride

dumrat17:10:45

@pez Ended up using vscode.WorkspaceEdit . It plays nicer than vscode.window.activeTextEditor the latter creating selections after editing which makes it not good for on the fly replacement. I have a lot of bug fixing to do, but managed to call a REST endpoint upon space/tab/newline and replace previous word with the transliteration. Loving calva btw. I can't use Clojure at my day job. But really enjoy the experience in VSCode. Thanks for all the effort you put in.

borkdude15:10:17

^ loading npm libraries in joyride. This needs a new release with the open PR which I'm sure @pez will merge somewhere this week ;)

borkdude16:10:32

@pez There have been some improvements to the promesa config in sci.configs. We could bump that + the promesa lib itself

1
borkdude16:10:45

E.g. there is now a p/doseq macro

pez16:10:01

joyride 2
borkdude 2
👍 2
dumrat18:10:12

This requires me first installing the relevant package with npm where the script resides is it?

pez18:10:53

We should document this. Packages can be installed either in ~/.config/joyride/ or in the workspace root.

seancorfield16:10:35

Can you explain how such modules would be declared as dependencies for the installation process? (I've never used npm and have avoided Node.js like the plague)

pez16:10:57

nodejs is no plague! 😃

seancorfield16:10:11

I'm allergic to JavaScript 😛

pez16:10:26

To use the example you need to have node. Then, in the workspace, you do:

$ npm init -y
$ npm install moment axios
BOOM.

seancorfield16:10:18

Ah, since it can require ["vscode" :as vscode] I was wondering how/where it would find such other modules -- is "vscode" treated specially?

pez16:10:21

npm init creates a minimum package.json for you. npm install create a node-modules directory which is similar to the .m2 maven thing, but local.

pez16:10:36

Yes, "vscode" is special.

seancorfield16:10:07

And there's a "global" (user-level) option? Since my Joyride scripts are user-level, not workspace-level.

pez16:10:31

But it still tricked me into forgetting that we didn't support npm requires yet. Until I uttered that we should and @U04V15CAJ just made it work.

pez16:10:49

Let me check up on the user level thing...

pez16:10:25

It seems to work to run those ^ commands in ~/.config/joyride. Maybe @U04V15CAJ can explain why it works. 😃

seancorfield16:10:50

OK, cool. So I can keep the rest of my system free of nodejs pollution then... Perhaps a note in the docs/readme about that?

pez16:10:03

My test might not have been sufficient. I just removed the dependency from my test workspace and ran the commands in the global folder. Then reloaded the vscode window and tried the examples in a user script. But you’ll find out, I guess.

borkdude17:10:46

The node modules are resolved relative to the script in question

borkdude17:10:03

so user level should work with user level node_modules

pez20:10:45

> The node modules are resolved relative to the script in question It seems, from my testing of createRequire and .resolve, that this should be read as that the first node_modules found from the script and up through it's directory ancestors... Hmmm, that got very hard to read. Anyway, I wonder if something like this in Joyride docs would describe it correctly and well enough: > npm packages that your scripts require should be npm installed in a node_modules/ directory somewhere in the requiring script's path. E.g. if you have node_modules in your system / (this is not a recommendation), then all Joyride scripts will be able to require packages from there. If you have node_modules side-by-side with a Joyride script, then only that script, sibling scripts and scripts in sub folders will be able to require from there. Some locations to consider are: > • For Workspace scripts: The workspace root. The .joyride/ folder in the workspace. > • For User scripts: ~/.config/joyride (or any subfolder there, if that makes sense for how you organize your user scripts)

pez20:10:36

Actually that with the system root folder doesn't seem to be true...

borkdude20:10:55

I think the suggestion should be that the node_modules should live as sibling of the script's directory or any of its parent directories.

borkdude20:10:27

Typically: the root of a project as dev dependencies (`npm install --save-dev`) (or one level higher, .joyride )or indeed the joyride user directory for user script

pez20:10:01

That's what I tried to suggest. 😃

borkdude20:10:27

very good then :)

pez20:10:52

Well, I'm a bit unsure if I succeeded. Haha.

pez20:10:20

And the example with the system root node_modules actually held true. I was making some mistakes testing it.

borkdude20:10:14

It's exactly the same with the .clj-kondo directory :)

pez20:10:55

But there it could even be a recommendation. 😃

borkdude20:10:27

I'm giving a CLJS-related presentation soon and joyride will also be appearing in it. If you have some nice example of how you are using it, please link it in the thread. A screenshot of the script in action which I can fit in a slide will also do.

metal 1
borkdude20:10:08

@U04V70XH6 Wanna share/screenshot your clojuredocs example?

pez20:10:17

I use this example quite a lot: https://github.com/BetterThanTomorrow/joyride/tree/master/examples#find-in-file-with-regexp-toggled-on Should be how VS Code works to begin with, but anyway. 😃

👍 1
pez20:10:33

Another one. Pasting the contents of the current file in a new, untitled, file/buffer:

{
        "key": "alt+ctrl+o p",
        "command": "joyride.runCode",
        "args": "(require '[promesa.core :as p]) (p/let [text (vscode/window.activeTextEditor.document.getText)\n doc (vscode/workspace.openTextDocument\n #js {:language \"clojure\", :content text})]\n (vscode/window.showTextDocument doc #js {:preview false, :preserveFocus false}))"
    },
It's good when I want to modify a library function, since VS Code won't let me edit the buffer when its showing a file from a jar file.

👍 1
borkdude20:10:21

lol. ok. if you continue, we will have enough material for the London Clojurians meeting

seancorfield20:10:11

Oh, that's the Java Docs version -- which is more complex. The ClojureDocs one just uses the current form as a selection to look up a URL on http://clojuredocs.org and open the Simple Browser. The Java Docs one requires you select a class name or expression (which will be eval'd) and then it gets the type/class of the result and gets the java docs URL and opens the Browser.

seancorfield20:10:53

What do y'all use for doing screencasts/recordings? @U04V15CAJ @pez

pez20:10:35

If I am recording VS Code I use an extension, Chronicler.

pez20:10:15

Then I narrate in Final Cut Pro.

borkdude21:10:03

Screencast: licecap or cmd-shift-5 (built into macOS which I know you're not using ;))

borkdude21:10:55

I've also used OBS when I want to record my screen and voice / webcam

seancorfield21:10:51

I tried OBS. It was horrible. Final Cut Pro is macOS @pez? I guess I could just use our Zoom account at work to create an ad hoc meeting, share my screen and record it 🙂 But editing afterward is still a pain (I have used YouTube's "studio" to post-edit videos after upload but... ugh! nasty!)

borkdude21:10:48

I just used OBS to record, but not edit and it worked for that. There are probably better (paid) solutions. I hate editing too. I found a program that let me cut out parts of the video. So I do everything in one take and when I make a mistake or want to do stuff over, I just cut out stuff from the 1 big take

borkdude21:10:42

I think I've also used Screenflow like almost a decode ago (on mac), a really polished product, but I think my license long expired

seancorfield21:10:08

I keep meaning try StreamYard to see if it's any better than other streaming/recording options... I created an account ages ago and did a couple of test casts but never got around to doing anything publishable...

borkdude21:10:34

Oh, I think Martin (@U5H74UNSF)mentioned an online solution to me a while back. I'll ask him

borkdude21:10:12

Probably recording in Zoom works just as well but then you will have to upload the video to youtube (or similar) and edit in some other program (if you want to edit)

skylize22:10:02

If on Linux, and you can put up with alpha-version bugs and glitches, I suggest trying Olive for editing.

dumrat01:10:51

Here's mine:

(ns hello-joyride-user-script
  (:require
   [joyride.core :as joyride]
   ["vscode" :as vscode]
   ["http" :as http]
   [promesa.core :as p]
   [clojure.string :as str]))

(defn current-line []
  (-> vscode/window .-activeTextEditor .-selection .-active .-line)) 

(defn current-language []
  (-> vscode/window .-activeTextEditor .-document .-languageId)) 

(defn get-current-line-text [line]
  (let [activeEditor (.-activeTextEditor vscode/window)
        selection-is-empty? (-> activeEditor .-selection .-isEmpty)]
    (if selection-is-empty?
      (.lineAt (.-document activeEditor) 
               line)
      nil)))

(defn get-last-word-info [ch]
  (let [line# (current-line)
        line# (if (= \newline ch) (dec line#) line#)
        line (.-_text (get-current-line-text line#))
        last-word (last (str/split line #"\s+"))]
    {:word last-word
     :line line#
     :length (count last-word)
     :start (- (count line) (count last-word))}))

(defn transliterate [word completion-fn]
  (let [data (atom "")]
    (http/get (str "" word) #js {}
               (fn [^js res]
                 (.on res "data" (fn [chunk]
                                   (swap! data str (.toString chunk))))
                 (.on res "end" (fn [] (completion-fn @data)))))))

(comment
  (vscode/workspace.onDidChangeTextDocument
   (fn [^js change]
     (when (and (= (current-language) "markdown")
                (= (.-document change) (-> vscode/window .-activeTextEditor .-document)))
       ; I don't know, does tab come as tab or spaces?
       (let [ch (#{\space \tab \newline "  " "    "} (-> change .-contentChanges (get 0) .-text))]
         (when ch
           (let [{:keys [word line start length]} (get-last-word-info ch)]
             (transliterate word
                            (fn [data]
                              (let [edit (vscode/WorkspaceEdit.)]
                                (.replace edit
                                          (-> change .-document .-uri)
                                          (vscode/Range. line (dec start) line (+ start length -1))
                                          data)
                                (.applyEdit vscode/workspace edit))))))))))
  )
And this at work below:

pez05:10:58

Awesome, @UC1DTFY1G! Is transliterating from one script representation (latin/english letters) to some other?

dumrat05:10:41

@pez Yes. The screencast above is transliteration from English to Sinhalese. Because it's a language only 16 million people use (and of a relatively poor people), there wasn't good support for Sinhalese for a long time so people used to just type whatever they wanted to say using the English alphabet although Sinhalese has it's own alphabet. The tools are getting better now, but the method of typing Sinhalese words using the English alphabet has remained. Hence, transliteration is a good choice for most people (myself included) when they don't want to memorize a new keyboard.

pez06:10:19

Thanks! What's the rationale behind the choice to transliterate word-for-word instead of character by character? I'm guessing it is not a 1->1 mapping?

borkdude06:10:48

This is awesome @UC1DTFY1G

❤️ 1
pez06:10:53

@UC1DTFY1G: In Typist (a VS Code extension) there is a mode where it simulates writing by pasting it in character by character. I have commands for toggling this on and off. Thinking you could do the something similar toggling that transliteration on and off via keyboard shortcuts. In Typist, I use an atom https://github.com/PEZ/paste-replaced/blob/master/src/paste_replaced/replacer.cljs For your script the commands could just install and uninstall the onchange handler. (If this is a desirable feature, but it seems to me that it would be.)

❤️ 1
pez06:10:11

@U04V15CAJ Typist is another example of how I use Joyride. I use it for prototyping when writing extensions. And for tweaking.

🎉 1
dumrat07:10:10

@pez You are right, it's not a 1 to 1 character mapping. A simple grammar I use with instaparse for transliteration now is below:

S=word (whitespace word)*;
word=(any|vowel|pureconsonant|consonant)+
vowel='e'|'E'|'a'|'U'|'O'|'i'|'u'|'A'|'I'|'o'|'aa'|'ii'|'ei'|'AA'|'ou'|'au'|'sru'|'srU';
pureconsonant='T'|'d'|'n'|'K'|'w'|'s'|'f'|'L'|'p'|'j'|'G'|'J'|'v'|'B'|'P'|'t'|'k'|'b'|'r'|'y'|'g'|'l'|'N'|'h'|'m'|'D'|'dh'|'ch'|'Dh'|'xd'|'xj'|'Sh'|'xb'|'sh'|'xg'|'Ch'|'Th'|'th'|'thh'|'xmb'|'chh'|'xdh'|'xgdh'|'xkdh';
consonant=pureconsonant,vowel;
any=#'.*';
whitespace=#'(\s|\n|\t)+'
But this doesn't capture all. For example a run of characters like wyaa should ideally map to ව්‍යා while the simple grammar above will map it to ව්යා. While not incorrect, this is considered uncultured 😄

dumrat08:10:41

@pez I haven't come to enabling/disabling the transliteration yet but yes. That's a must. So I will take a look at what you mentioned. Thanks. I was thinking I can just add and remove the listener to the text changed event on some key combo. Or is that impossible to do?

dumrat08:10:02

@pez As mentioned here: https://code.visualstudio.com/api/references/vscode-api#events All we'd need to do is have two key combinations referring to the same script and an atom to hold the callback for the event right? Or am I mistaken?

pez08:10:15

Yes, you can do it like that, similar to what we do in this example: https://github.com/BetterThanTomorrow/joyride/blob/master/examples/.joyride/scripts/workspace_activate.cljs Or you could have your handler check the status and be a noop if transcibation is not enabled.

borkdude09:10:07

(I realized I derailed my own thread here, but @U04V70XH6: this is what @U5H74UNSF uses, https://www.descript.com/, let's continue video editing tools in another thread from now on)

pez22:10:03

I might be presenting at a meetup next week. Preparing, I remember that I have made an extension (unpublished) that helps me step through Markdown Previews in VS Code. These are my slides, so that I can stay in VS Code as much as possible. Last time I presented anything there was no Joyride. Now that there is, I don't want to use a bloaty extension. 😃 So ported my extension to Joyride. The script:

(ns next-slide
  (:require ["vscode" :as vscode]
            [promesa.core :as p]
            [clojure.edn :as edn]))

(def ^:private !state (atom {:active? false
                             :active-slide 0}))

(defn- ws-root []
  (if (not= js/undefined
            vscode/workspace.workspaceFolders)
    (.-uri (first vscode.workspace.workspaceFolders))
    (vscode/Uri.parse ".")))

(defn next!
  ([]
   (next! true))
  ([forward?]
   (p/let [config-uri (vscode/Uri.joinPath (ws-root) "slides.edn")
           config-data (vscode/workspace.fs.readFile config-uri)
           config-text (-> (js/Buffer.from config-data) (.toString "utf-8"))
           config (edn/read-string config-text)
           slides (:slides config)
           next (if forward?
                  #(min (inc %) (dec (count slides)))
                  #(max (dec %) 0))]
     (swap! !state update :active-slide next)
     (vscode/commands.executeCommand "markdown.showPreview"
                                     (vscode/Uri.joinPath (ws-root)
                                                          (nth slides (:active-slide @!state)))))))

(defn toggle-active! []
  (swap! !state update :active? not)
  (vscode/commands.executeCommand "setContext" "next-slide:active" (:active? @!state))
  (vscode/window.showInformationMessage (str "next-slide:" (if (:active? @!state)
                                                             "activated"
                                                             "deactivated"))))
Keyboard shortcuts:
{
        "key": "ctrl+alt+cmd+space n",
        "command": "joyride.runCode",
        "args": "(next-slide/toggle-active!)"       
    },
    {
        "key": "right",
        "command": "joyride.runCode",
        "args": "(next-slide/next! true)",
        "when": "next-slide:active && !editorTextFocus"       
    },
    {
        "key": "left",
        "command": "joyride.runCode",
        "args": "(next-slide/next! false)",
        "when": "next-slide:active && !editorTextFocus"       
    },
Then I stick a file like this in my workspace:
{:slides ["hello.md",
          "vic-20.md",
          "brooks.md",
          "plotter.md",
          "clojure.md",
          "thanks.md"]}
This assumes those files are in the workspace root as well. Then when presenting, I: 1. Load the next_slide.cljs script (I have it as a User script). 2. ctrl+alt+cmd+space n to activate next-slide. 3. Flick back and forth through the slides with right and left.

pez22:10:30

I'll add this as an example to Joyride when I have some time.

pez22:10:36

The script would get simpler if we supported (slurp), btw. 😃 I think I'll add that some day soon too.

borkdude08:10:47

nbb also has slurp in nbb.core (which returns a promise)