Skip to content

Commit

Permalink
fix: listed devices didn't refresh after config update
Browse files Browse the repository at this point in the history
This fix provides a cleaner approach to refreshing provider views
closes: #221
  • Loading branch information
jakobrosenberg committed Apr 26, 2022
1 parent 2e3cecc commit 82da7c1
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 15 deletions.
6 changes: 4 additions & 2 deletions src/Device.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,17 @@ class Device {
this.updateHideStatus();
if (!this.config.hidden) this.updateConnection();
subscribe(() => this.onChanged());
this.pymakr.config.subscribe(() => this.updateHideStatus());
}

/**
* Hides / unhides this device depending on how it matches user's config.devices.include and config.devices.exclude
*/
updateHideStatus() {
const oldStatus = this.config.hidden;
const { include, exclude } = this.pymakr.config.get().get("devices");
this.config.hidden = !createIsIncluded(include, exclude)(serializeKeyValuePairs(this.raw));
if (oldStatus != this.config.hidden) this.onChanged();
}

/**
Expand Down Expand Up @@ -254,8 +257,7 @@ class Device {
*/
onChanged() {
this.state.save();
this.pymakr.devicesProvider.refresh();
this.pymakr.projectsProvider.refresh();
this.pymakr.refreshProvidersThrottled();
}

/**
Expand Down
13 changes: 10 additions & 3 deletions src/PyMakr.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { resolve } = require("path");
const { FileSystemProvider } = require("./providers/FilesystemProvider");
const { createLogger } = require("./utils/createLogger");
const { writable } = require("./utils/store");
const { coerceDisposable } = require("./utils/misc");
const { coerceDisposable, createThrottledFunction } = require("./utils/misc");
const manifest = require("../package.json");
const { createVSCodeHelpers } = require("./utils/vscodeHelpers");
const { TextDocumentProvider } = require("./providers/TextDocumentProvider");
Expand All @@ -28,6 +28,8 @@ class PyMakr {
* @param {vscode.ExtensionContext} context
*/
constructor(context) {
this.refreshProvidersThrottled = createThrottledFunction(this.refreshProviders.bind(this));

/** Reactive Pymakr user configuration */
this.config = writable(vscode.workspace.getConfiguration("pymakr"));

Expand All @@ -39,7 +41,7 @@ class PyMakr {
this.log.info(`${manifest.name} v${manifest.version}`);

// avoid port collisions between multiple vscode instances running on the same machine
this.terminalPort = 5364 + (Math.random() * 10240 | 0);
this.terminalPort = 5364 + ((Math.random() * 10240) | 0);

this.onUpdatedConfig("silent");
this.context = context;
Expand All @@ -64,7 +66,7 @@ class PyMakr {
/** Provides device access for the file explorer */
this.fileSystemProvider = new FileSystemProvider(this);

this.textDocumentProvider = new TextDocumentProvider(this)
this.textDocumentProvider = new TextDocumentProvider(this);

this.registerWithIde();
this.setup();
Expand Down Expand Up @@ -128,6 +130,11 @@ class PyMakr {
async registerProjects() {
await this.projectsStore.refresh();
}

refreshProviders() {
this.devicesProvider.refresh();
this.projectsProvider.refresh();
}
}

module.exports = { PyMakr };
43 changes: 39 additions & 4 deletions src/utils/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const mapEnumsToQuickPick = (descriptions) => (_enum, index) => ({
* @returns {{[P in K]: T[P]}}
*/
const cherryPick = (obj, props) =>
props.reduce((newObj, key) => ({ ...newObj, [key]: obj[key] }), /** @type {obj} */({}));
props.reduce((newObj, key) => ({ ...newObj, [key]: obj[key] }), /** @type {obj} */ ({}));

/**
* Curried function. Returns the nearest parent from an array of folders
Expand Down Expand Up @@ -139,7 +139,7 @@ const readJsonFile = (path) => JSON.parse(readFileSync(path, "utf8"));
const getNearestPymakrConfig = (path) => {
if (!path) return null;
const projectPath = getNearestPymakrProjectDir(path);
if (projectPath) return readJsonFile(join(projectPath, 'pymakr.conf'));
if (projectPath) return readJsonFile(join(projectPath, "pymakr.conf"));
else return null;
};

Expand All @@ -149,7 +149,7 @@ const getNearestPymakrConfig = (path) => {
* @returns {string}
*/
const getNearestPymakrProjectDir = (path) => {
const configPath = join(path, 'pymakr.conf');
const configPath = join(path, "pymakr.conf");
if (existsSync(configPath)) return path;
else {
const parentDir = dirname(path);
Expand Down Expand Up @@ -189,7 +189,7 @@ const createIsIncluded = (includes, excludes, cb = (x) => x) => {
* Serializes flat object
* @example default behavior
* ```javascript
* serializeKeyValuePairs ({foo: 123, bar: 'bar'})
* serializeKeyValuePairs ({foo: 123, bar: 'bar'})
* // foo=123
* // bar=bar
* ```
Expand All @@ -216,6 +216,40 @@ const resolvablePromise = () => {
return Object.assign(origPromise, { resolve, reject });
};

/**
* Subsequent calls to an active throttled function will return the same promise as the first call.
* A function is active until it's first call is resolved
* @template {Function} T
* @param {T} fn callback
* @param {number=} time leave at 0 to only throttle calls made within the same cycle
* @returns {(...params: Parameters<T>)=>Promise<ReturnType<T>>}
*/
const createThrottledFunction = (fn, time) => {
let isRunning = false;
/** @type {{resolve: any, reject: any}[]} */
const subs = [];
const fnWrapper = (...params) =>
new Promise((resolve, reject) => {
subs.push({ resolve, reject });
if (!isRunning) {
isRunning = true;
setTimeout(async () => {
this._isRefreshingProviders = false;
try {
const result = await fn(...params);
subs.forEach((sub) => sub.resolve(result));
} catch (err) {
subs.forEach((sub) => sub.reject(err));
}
subs.splice(0)
isRunning = false
}, time);
}
});
return fnWrapper;
};


module.exports = {
once,
coerceArray,
Expand All @@ -234,4 +268,5 @@ module.exports = {
createIsIncluded,
arrayToRegexStr,
resolvablePromise,
createThrottledFunction
};
21 changes: 15 additions & 6 deletions src/utils/specs/misc.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
arrayToRegexStr,
cherryPick,
createIsIncluded,
createThrottledFunction,
getDifference,
getNearestParent,
getNearestPymakrConfig,
Expand Down Expand Up @@ -65,15 +66,14 @@ test("cherryPick", () => {

test("getNearestParent + relative", () => {
// use different test paths on windows / linux
if (process.platform === "win32"){
if (process.platform === "win32") {
const parents = ["c:\\some\\folder\\path", "c:\\some\\folder", "c:\\some"];
const child = "c:\\some\\folder\\child\\path";

assert.equal(getNearestParent(parents)(child), "c:\\some\\folder");
assert.equal(getRelativeFromNearestParent(parents)(child), "child\\path");
assert.equal(getRelativeFromNearestParentPosix(parents)(child), "child/path");

} else{
} else {
const parents = ["/some/folder/path", "/some/folder", "/some"];
const child = "/some/folder/child/path";

Expand Down Expand Up @@ -121,9 +121,18 @@ test("createIsIncluded", () => {
});

test("specific excludes excludes only specific matches", () => {
const result = items.filter(
createIsIncluded([".*"], ["someField=exclude-me"], serializeKeyValuePairs)
);
const result = items.filter(createIsIncluded([".*"], ["someField=exclude-me"], serializeKeyValuePairs));
assert.deepEqual(result, [items[0], items[1]]);
});
});

test("createThrottledFunction", async () => {
const getRandom = () => Math.random();
const throttledRandom = createThrottledFunction(getRandom);
const call1 = throttledRandom()
const call2 = throttledRandom()
const call3 = throttledRandom()
const [r1, r2, r3] = await Promise.all([call1, call2, call3])
assert.equal(r1, r2)
assert.equal(r2, r3)
});

0 comments on commit 82da7c1

Please sign in to comment.