Trying to uderstand Thennables in the VSCode API. Are these equivalent typescript code snippets? - javascript

I want to execute a series of edits to a document in the VSCode API. The function that makes it all happen is Workspace.applyEdit which returns a Thennable. This is my first time working with those, and the one being returned by this function is does not work as I expect.
Snippet 1:
import { window, workspace, WorkspaceEdit, Position } from 'vscode';
//doesn't work, only makes first insertion, althouh all info text prints
export function applyEditReprex() {
let text = "\ntest\n";
let target = window.activeTextEditor.document.uri;
let positions = [
new Position(10, 1),
new Position(15, 1),
new Position(20, 1)
];
positions.reduce((applyThennable, position) => {
return (
applyThennable.then(() => {
console.info("Making new edit");
let edit = new WorkspaceEdit();
edit.insert(target, position, text);
workspace.applyEdit(edit);
}))
},
Promise.resolve()
).then(() => {
console.info("Finished edits.");
})
}
Only a single instance of "test" appears in the target document on line 12. The log reports:
Making new edit
Making new edit
Making new edit
Finished edits.
Snippet 2:
My attempt to unroll above into straight chained calls:
import { window, workspace, WorkspaceEdit, Position } from 'vscode';
export function applyEditReprex2() {
let text = "\ntest\n";
let target = window.activeTextEditor.document.uri;
let positions = [
new Position(10, 1),
new Position(15, 1),
new Position(20, 1)
];
console.info("Making new edit");
let edit = new WorkspaceEdit();
edit.insert(target, positions[0], text);
workspace.applyEdit(edit).then(() => {
console.info("Making new edit");
let edit = new WorkspaceEdit();
edit.insert(target, positions[1], text);
workspace.applyEdit(edit).then(() => {
console.info("Making new edit");
let edit = new WorkspaceEdit();
edit.insert(target, positions[2], text);
workspace.applyEdit(edit).then(() => {
console.info("Finished edits.");
})
})
})
}
3 instances of "test" appear in the target file, on lines 12, 17, 22.
The log reports:
Making new edit
Making new edit
Making new edit
Finished edits.
Question
Are there any intricacies of reduce or fat arrow functions that that I may be unaware of that could be causing the first snippet to behave differently from the unrolled version? Or another way: is the unrolled version not equivalent to the reduce in some important way?

You forgot to return the thenable object from the promise's .then() callback, which is essential for promise chaining:
positions.reduce((prevPromise, position) => {
return prevPromise.then(() => {
console.info("Making new edit");
const edit = new WorkspaceEdit();
edit.insert(target, position, text);
const applyThenable = workspace.applyEdit(edit);
return applyThenable;
// ^^^^^^^^^^^^^^^^^^^^
});
}, Promise.resolve())
Btw, my understanding of the API from the documentation you linked is that you should only make a single WorkspaceEdit with multiple insertions:
const positions = [
new Position(10, 1),
new Position(15, 1),
new Position(20, 1)
];
const edit = new WorkspaceEdit();
for (const position in positions) {
edit.insert(target, position, text);
}
workspace.applyEdit(edit).then(() => {
console.info("Finished multi-edit.");
})

Related

Why are Mongo ObjectID's not unique?

According to the MongoDB docs ObjectID's are suppose to be created by
ObjectID is a 96-bit number which is composed as follows:
a 4-byte timestamp value representing the seconds since the Unix epoch (which will not run out of seconds until the year 2106)
a 5-byte random value, and
a 3-byte incrementing counter, starting with a random value.
So when I do
const mongoose = require('mongoose');
const carSchema = new mongoose.Schema({ driver: mongoose.ObjectId });
const Car = mongoose.model('Car', carSchema);
const car = new Car();
let i = 0;
while (i < 1000) {
car.driver = new mongoose.Types.ObjectId();
console.log(car.driver.toString());
i++;
}
I expect to see the 3 different parts change. However what I see is only one part increment.
~/tmp$ head t2
638f7d3f37664dec556b0491
638f7d3f37664dec556b0492
638f7d3f37664dec556b0493
638f7d3f37664dec556b0494
638f7d3f37664dec556b0495
638f7d3f37664dec556b0496
638f7d3f37664dec556b0497
638f7d3f37664dec556b0498
638f7d3f37664dec556b0499
638f7d3f37664dec556b049a
~/tmp$ tail t2
638f7d3f37664dec556b086f
638f7d3f37664dec556b0870
638f7d3f37664dec556b0871
638f7d3f37664dec556b0872
638f7d3f37664dec556b0873
638f7d3f37664dec556b0874
638f7d3f37664dec556b0875
638f7d3f37664dec556b0876
638f7d3f37664dec556b0877
638f7d3f37664dec556b0878
Question
Can someone figure out why I don't get the 3 different parts change?
Bonus question
When I do
const mongoose = require('mongoose');
const carSchema = new mongoose.Schema({ driver: mongoose.ObjectId });
const carSchema2 = new mongoose.Schema({ driver: mongoose.ObjectId });
const Car = mongoose.model('Car', carSchema);
const Car2 = mongoose.model('Car2', carSchema2);
const car = new Car();
const car2 = new Car2();
const splitElements = id => {
return {
seconds: parseInt(id.slice(0, 8), 16),
random: parseInt(id.slice(8, 18), 16),
counter: parseInt(id.slice(18, 24), 16),
};
};
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms));
};
(async () => {
let i = 0;
while (i < 5) {
car.driver = new mongoose.Types.ObjectId();
console.log(car.driver.toString(), JSON.stringify(splitElements(car.driver.toString())));
await sleep(100);
i++;
}
})();
(async () => {
let i = 0;
while (i < 5) {
car2.driver = new mongoose.Types.ObjectId();
console.log(car2.driver.toString(), JSON.stringify(splitElements(car2.driver.toString())));
await sleep(100);
i++;
}
})();
I see this
638fe308b0a1fb8a3c22e2e9 {"seconds":1670374152,"random":758631860796,"counter":2286313}
638fe308b0a1fb8a3c22e2ea {"seconds":1670374152,"random":758631860796,"counter":2286314}
638fe308b0a1fb8a3c22e2eb {"seconds":1670374152,"random":758631860796,"counter":2286315}
638fe308b0a1fb8a3c22e2ec {"seconds":1670374152,"random":758631860796,"counter":2286316}
638fe308b0a1fb8a3c22e2ed {"seconds":1670374152,"random":758631860796,"counter":2286317}
638fe308b0a1fb8a3c22e2ee {"seconds":1670374152,"random":758631860796,"counter":2286318}
638fe308b0a1fb8a3c22e2ef {"seconds":1670374152,"random":758631860796,"counter":2286319}
638fe308b0a1fb8a3c22e2f0 {"seconds":1670374152,"random":758631860796,"counter":2286320}
638fe308b0a1fb8a3c22e2f1 {"seconds":1670374152,"random":758631860796,"counter":2286321}
638fe308b0a1fb8a3c22e2f2 {"seconds":1670374152,"random":758631860796,"counter":2286322}
where the counter continues from where the first left off. This I don't understand, how this can happen. I would have expected that each model object would get their own counter.
Why do they share the same counter?
Updated as question was clarified.
Use the following node.js code to test:
const mongoose = require('mongoose')
const carSchema = new mongoose.Schema({ driver: mongoose.ObjectId })
const Car = mongoose.model('Car', carSchema)
const car = new Car()
const splitElements = (id) => {
return {
seconds: parseInt(id.slice(0, 8), 16),
random: parseInt(id.slice(8, 18), 16),
counter: parseInt(id.slice(18, 24), 16)
}
}
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
const init = async () => {
let i = 0
while (i < 20) {
car.driver = new mongoose.Types.ObjectId()
console.log(car.driver.toString(), JSON.stringify(splitElements(car.driver.toString())))
await sleep(100)
i++
}
}
init()
You will notice that I added a small sleep in there as well as artificially parsing the id into it's constituent elements.
If you run this program 3 times you get something like this
FIRST TIME
638f9befbe5587e19f76c51c {"seconds":1670355951,"random":817478754719,"counter":7783708}
638f9befbe5587e19f76c51d {"seconds":1670355951,"random":817478754719,"counter":7783709}
638f9befbe5587e19f76c51e {"seconds":1670355951,"random":817478754719,"counter":7783710}
638f9befbe5587e19f76c51f {"seconds":1670355951,"random":817478754719,"counter":7783711}
638f9befbe5587e19f76c520 {"seconds":1670355951,"random":817478754719,"counter":7783712}
638f9befbe5587e19f76c521 {"seconds":1670355951,"random":817478754719,"counter":7783713}
638f9befbe5587e19f76c522 {"seconds":1670355951,"random":817478754719,"counter":7783714}
638f9befbe5587e19f76c523 {"seconds":1670355951,"random":817478754719,"counter":7783715}
638f9bf0be5587e19f76c524 {"seconds":1670355952,"random":817478754719,"counter":7783716}
638f9bf0be5587e19f76c525 {"seconds":1670355952,"random":817478754719,"counter":7783717}
638f9bf0be5587e19f76c526 {"seconds":1670355952,"random":817478754719,"counter":7783718}
638f9bf0be5587e19f76c527 {"seconds":1670355952,"random":817478754719,"counter":7783719}
638f9bf0be5587e19f76c528 {"seconds":1670355952,"random":817478754719,"counter":7783720}
638f9bf0be5587e19f76c529 {"seconds":1670355952,"random":817478754719,"counter":7783721}
638f9bf0be5587e19f76c52a {"seconds":1670355952,"random":817478754719,"counter":7783722}
638f9bf0be5587e19f76c52b {"seconds":1670355952,"random":817478754719,"counter":7783723}
638f9bf0be5587e19f76c52c {"seconds":1670355952,"random":817478754719,"counter":7783724}
638f9bf0be5587e19f76c52d {"seconds":1670355952,"random":817478754719,"counter":7783725}
638f9bf1be5587e19f76c52e {"seconds":1670355953,"random":817478754719,"counter":7783726}
638f9bf1be5587e19f76c52f {"seconds":1670355953,"random":817478754719,"counter":7783727}
SECOND TIME
638f9bf79c0221dcd6dd3363 {"seconds":1670355959,"random":670050671830,"counter":14496611}
638f9bf79c0221dcd6dd3364 {"seconds":1670355959,"random":670050671830,"counter":14496612}
638f9bf79c0221dcd6dd3365 {"seconds":1670355959,"random":670050671830,"counter":14496613}
638f9bf79c0221dcd6dd3366 {"seconds":1670355959,"random":670050671830,"counter":14496614}
638f9bf79c0221dcd6dd3367 {"seconds":1670355959,"random":670050671830,"counter":14496615}
638f9bf79c0221dcd6dd3368 {"seconds":1670355959,"random":670050671830,"counter":14496616}
638f9bf79c0221dcd6dd3369 {"seconds":1670355959,"random":670050671830,"counter":14496617}
638f9bf79c0221dcd6dd336a {"seconds":1670355959,"random":670050671830,"counter":14496618}
638f9bf79c0221dcd6dd336b {"seconds":1670355959,"random":670050671830,"counter":14496619}
638f9bf89c0221dcd6dd336c {"seconds":1670355960,"random":670050671830,"counter":14496620}
638f9bf89c0221dcd6dd336d {"seconds":1670355960,"random":670050671830,"counter":14496621}
638f9bf89c0221dcd6dd336e {"seconds":1670355960,"random":670050671830,"counter":14496622}
638f9bf89c0221dcd6dd336f {"seconds":1670355960,"random":670050671830,"counter":14496623}
638f9bf89c0221dcd6dd3370 {"seconds":1670355960,"random":670050671830,"counter":14496624}
638f9bf89c0221dcd6dd3371 {"seconds":1670355960,"random":670050671830,"counter":14496625}
638f9bf89c0221dcd6dd3372 {"seconds":1670355960,"random":670050671830,"counter":14496626}
638f9bf89c0221dcd6dd3373 {"seconds":1670355960,"random":670050671830,"counter":14496627}
638f9bf89c0221dcd6dd3374 {"seconds":1670355960,"random":670050671830,"counter":14496628}
638f9bf89c0221dcd6dd3375 {"seconds":1670355960,"random":670050671830,"counter":14496629}
638f9bf99c0221dcd6dd3376 {"seconds":1670355961,"random":670050671830,"counter":14496630
THIRD TIME
638f9bfab8f928518038320f {"seconds":1670355962,"random":794454151552,"counter":3682831}
638f9bfbb8f9285180383210 {"seconds":1670355963,"random":794454151552,"counter":3682832}
638f9bfbb8f9285180383211 {"seconds":1670355963,"random":794454151552,"counter":3682833}
638f9bfbb8f9285180383212 {"seconds":1670355963,"random":794454151552,"counter":3682834}
638f9bfbb8f9285180383213 {"seconds":1670355963,"random":794454151552,"counter":3682835}
638f9bfbb8f9285180383214 {"seconds":1670355963,"random":794454151552,"counter":3682836}
638f9bfbb8f9285180383215 {"seconds":1670355963,"random":794454151552,"counter":3682837}
638f9bfbb8f9285180383216 {"seconds":1670355963,"random":794454151552,"counter":3682838}
638f9bfbb8f9285180383217 {"seconds":1670355963,"random":794454151552,"counter":3682839}
638f9bfbb8f9285180383218 {"seconds":1670355963,"random":794454151552,"counter":3682840}
638f9bfbb8f9285180383219 {"seconds":1670355963,"random":794454151552,"counter":3682841}
638f9bfcb8f928518038321a {"seconds":1670355964,"random":794454151552,"counter":3682842}
638f9bfcb8f928518038321b {"seconds":1670355964,"random":794454151552,"counter":3682843}
638f9bfcb8f928518038321c {"seconds":1670355964,"random":794454151552,"counter":3682844}
638f9bfcb8f928518038321d {"seconds":1670355964,"random":794454151552,"counter":3682845}
638f9bfcb8f928518038321e {"seconds":1670355964,"random":794454151552,"counter":3682846}
638f9bfcb8f928518038321f {"seconds":1670355964,"random":794454151552,"counter":3682847}
638f9bfcb8f9285180383220 {"seconds":1670355964,"random":794454151552,"counter":3682848}
638f9bfcb8f9285180383221 {"seconds":1670355964,"random":794454151552,"counter":3682849}
638f9bfcb8f9285180383222 {"seconds":1670355964,"random":794454151552,"counter":3682850}
As the pause is introduced, you can see the seconds portion of the ObjectID does indeed increment, it is even consistently incrementing between runs.
The random 5 bytes are also changing between runs. Not on each invocation of new ObjectID(), but between application starts. See code bson, specifically looking where it sets the PROCESS_UNIQUE IF NOT NULL! Eg, this happens once and once only, so we don't expect it to change if our application is still running.
if (PROCESS_UNIQUE === null) {
PROCESS_UNIQUE = randomBytes(5);
}
Finally, the counter increments as expected. Starting on a random value.
So, in summary, they do change, but you have to go slowly (1-2 seconds), to notice a change in the time based elements, and application restarts are the only trigger for changing the random part.
Side note from comments: Keeping Track of the counter:
If you use new mongoose.Types.ObjectId(), that is just placeholder for the mongodb driver const {ObjectID} = require('mongodb'), which also points to the bson library, so they all use the same object.ts class to generate the ObjectID. BUT, this is all subject to change, as MongoDB ObjectID generation used to contain 4 elements, so you shouldn't really try to second guess the internal workings as they are internal. That being said the ObjectID class has a private static field for index, which is set to random on initialisation and when you create an ObjectID through the generate() method, it increments the index each time.
However, why you want to keep track of this, I don't know. If you wanted to keep track of it, I'd just use the splitElements method and parse it as binary unless you really want to overwrite the objectid.ts class in your node_modules to gain access to it.

How to take multiple screenshots with selenium with images being empty?

I am learning Selenium for the first time and I am trying to make a script that takes a screenshot of a google map with multiple criteria depending on the routes.
For Example localhost:3000/1 is one map and localhost:3000/2 is the second map.
Now, this is my code.
const { Builder, By, Key, until } = require("selenium-webdriver");
const driver = new Builder().forBrowser("firefox").build();
const urls = [
"http://localhost:3000/map/0",
"http://localhost:3000/map/1",
"http://localhost:3000/map/12",
];
// const title
const loop = () => {
return new Promise(async (resolve, reject) => {
for (let i = 0; i < 3; i++) {
await driver.get(urls[i]);
let ele = await driver.wait(
until.elementLocated(By.id("map-wrapper")),
10000
);
driver.takeScreenshot().then(function (image) {
require("fs").writeFileSync(
`./map-images/captured_image_${i}.png`,
image,
"base64"
);
});
}
resolve();
});
};
loop().then(() => driver.quit());
Now, I get all the images saved but after the first image, all the other ones are empty/greyed out. I am assuming it happens because the screenshot happens before the google map is loaded?
Is there any way I can wait for the map to load given that, it is the issue?
Any help is appreciated!

Vscode move to line X after openTextDocument

I'm developing a VS Code extension that jump to a specific file:num, but I'm stuck at the step of moving the cursor to a specific line after opening a file.
How can I achieve this :
export const openAndMoveToLine = async (file_line: string) => {
// /home/user/some/path.php:10
let [filename, line_number] = file_line.split(":")
// opening the file => OK
let setting: vscode.Uri = vscode.Uri.parse(filename)
let doc = await vscode.workspace.openTextDocument(setting)
vscode.window.showTextDocument(doc, 1, false);
// FIXME: After being opened, now move to the line X => NOK **/
await vscode.commands.executeCommand("cursorMove", {
to: "down", by:'wrappedLine',
value: parseInt(line_number)
});
}
Thank you
It can be done with the TextDocumentShowOptions easily:
const showDocOptions = {
preserveFocus: false,
preview: false,
viewColumn: 1,
// replace with your line_number's
selection: new vscode.Range(314, 0, 314, 0)
};
let doc = await vscode.window.showTextDocument(setting, showDocOptions);
You will first need to get access to the active editor. This is done by adding a .then to the showTextDocument call (which is a Thenable function) that returns a text editor object. You will then be able to use the textEditor variable (as in the example) to set the position of the cursor using the selection property as follows:
vscode.window.showTextDocument(doc, 1, false).then((textEditor: TextEditor) => {
const lineNumber = 1;
const characterNumberOnLine = 1;
const position = new vscode.Position(lineNumber, characterNumberOnLine);
const newSelection = new vscode.Selection(position, position);
textEditor.selection = newSelection;
});
Reference to selection API can be found here.
The usecase that you are exploring has been discussed in GitHub issue that can be found here.

Is there a way to trigger validation manually in monaco editor?

I'm using the default TypeScript service and the models are initialized asynchronously with one model depending on the other. There's a case where the two models cannot detect each other so it shows a semantic error. If I make some edits in the dependent model, which causes the model to be re-validated, the errors disappear.
I have tried to setModel manually, which solves the problems. However, it destroys the undo history.
Is there a way to re-validate the model manually?
That's my solution, which is extracted from monaco-typescript:
async function revalidateModel(model) {
if (!model || model.isDisposed()) return;
const getWorker = await monaco.languages.typescript.getTypeScriptWorker();
const worker = await getWorker(model.uri);
const diagnostics = (await Promise.all([
worker.getSyntacticDiagnostics(model.uri.toString()),
worker.getSemanticDiagnostics(model.uri.toString())
])).reduce((a, it) => a.concat(it));
const markers = diagnostics.map(d => {
const start = model.getPositionAt(d.start);
const end = model.getPositionAt(d.start + d.length);
return {
severity: monaco.MarkerSeverity.Error,
startLineNumber: start.lineNumber,
startColumn: start.column,
endLineNumber: end.lineNumber,
endColumn: end.column,
message: flattenDiagnosticMessageText(d.messageText, "\n")
};
});
const owner = model.getLanguageIdentifier().language;
monaco.editor.setModelMarkers(model, owner, markers);
}
Call the function above when model is created asynchronizedly.
This is what I did to fix it:
setInterval(() => {
const range = new monaco.Range(1,1,1,1);
const addEmptySpace = {forceMoveMarkers: true, range, text: ' '};
for (const m of monaco.editor.getModels()) {
const toInvert = m.applyEdits([addEmptySpace]);
m.applyEdits(toInvert);
}
}, 50*1000)
Every fifty seconds you insert and immediately remove a space. I don't like it, but it works.

Implementing Stream in JavaScript

I want to implement a stream object that can do this:
// a -------1------2----3
// map -----\------\----\
// b --------2------4----6
const a = new Stream();
const b = a.map(value => value * 2);
b.subscribe(console.log);
a.push(1);
// 2
a.push(2);
// 4
a.push(3);
// 6
The idea here is that the object b can subscribe new callbacks to stream a. The map function should listen when push is called and apply the mapped out function as well as the originally subscribed one. This is the implementation I have so far:
class Stream {
constructor(queue = []) {
this.queue = queue;
}
subscribe(action) {
if (typeof action === 'function') {
this.queue.push(action);
}
}
map(callback) {
this.queue = this.queue.map(
actionFn => arg => action(callback(arg))
);
return this;
}
push(value) {
this.queue.forEach(actionFn => {
actionFn.call(this, value);
});
}
}
The problem with current implementation is that originally the queue in class Stream is empty so it doesn't go through it. Would appreciate any suggestions or help. I would like to not use any library for this.
Your map needs to create a new Transform stream and return it. Instead of the subscribe you could simply use the standard on('data') event or, better use the read method.
Lastly - you could simply use my work and have your map method already efficiently implemented by using scramjet, which does exactly what you shown above - and moreover it supports async functions. :)
Here's how you'd use it (in some getStream function):
const {DataStream} = require('scramjet');
const stream = new DataStream();
stream.write(1); // you can also use await stream.whenWrote(1);
stream.write(2);
stream.write(3);
return stream.map(x => x * 2);
and then read it somewhere else:
stream.on('data', x => console.log(`x: ${x}`));
// x: 2
// x: 4
// x: 6
Take a look at the scramjet docs here
After such a long time of asking this question I was able to go back to the problem and come up with a simple solution. Since one stream should listen to the one it is subscribed to, we should return the instance of the original in order to preserve the values from the previous stream. Here is the code that I found to be working well:
class Stream {
constructor() {
this.subscriptions = [];
this.mappedActions = [];
}
subscribe(callback) {
this.subscriptions.push(callback);
}
map(actionFunc) {
this.mappedActions.push(actionFunc);
return this;
}
push(opValue) {
this.subscriptions.forEach(cb => {
if (this.mappedActions.length) {
this.mappedActions.forEach(action => {
cb(action.call(this, opValue));
});
} else {
cb(opValue);
}
});
}
}
const a = new Stream();
const b = a.map(value => value * 1 / 2);
const c = b.map(value => value * 3);
c.subscribe(console.log);
c.push(1);
c.push(2);
c.push(3);
// expected output in the console:
// 0.5
// 3
// 1
// 6
// 1.5
// 9
Hope anyone who stumbles upon this interesting problem will find my solution useful. If there is any changes you would like to make, feel free to do so or ping me!

Categories

Resources