Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support config backlinks sort type #645

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
18 changes: 18 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,23 @@
"type": "object",
"title": "Memo",
"properties": {
"memo.backlinksPanel.sortOrder": {
"enum": [
"path",
"alphabet",
"last-modified",
"last-modified-refs"
],
"enumDescriptions": [
"Backlinks are sorted by their paths.",
"Backlinks are sorted by their directionary order.",
"Backlinks are sorted by their last modified time.",
"Backlinks are sorted by their last modified reference's time."
],
"default": "path",
"scope": "resource",
"description": "Controls the sorting order of backlinks."
},
"memo.backlinksPanel.collapseParentItems": {
"default": false,
"scope": "resource",
Expand Down Expand Up @@ -305,6 +322,7 @@
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"vsce": "^2.15.0",
"vscode-uri": "^3.0.8",
"wait-for-expect": "^3.0.2",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
Expand Down
3 changes: 2 additions & 1 deletion src/features/BacklinksTreeDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ export default class BacklinksTreeDataProvider implements vscode.TreeDataProvide
({ location }) => location.uri.fsPath,
);

const pathsSorted = sortPaths(Object.keys(referencesByPath), { shallowFirst: true });
const sortOrder = getMemoConfigProperty('backlinksPanel.sortOrder', 'path');
const pathsSorted = await sortPaths(sortOrder)(referencesByPath);

if (!pathsSorted.length) {
return [];
Expand Down
6 changes: 3 additions & 3 deletions src/features/ReferenceRenameProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
containsUnknownExt,
findFilesByExts,
extractExt,
sortPaths,
crossPathSort,
} from '../utils';

const openingBracketsLength = 2;
Expand All @@ -33,7 +33,7 @@ export default class ReferenceRenameProvider implements RenameProvider {
const unknownUris = containsUnknownExt(ref) ? await findFilesByExts([extractExt(ref)]) : [];

const augmentedUris = unknownUris.length
? sortPaths([...cache.getWorkspaceCache().allUris, ...unknownUris], {
? crossPathSort([...cache.getWorkspaceCache().allUris, ...unknownUris], {
pathKey: 'path',
shallowFirst: true,
})
Expand Down Expand Up @@ -69,7 +69,7 @@ export default class ReferenceRenameProvider implements RenameProvider {
const unknownUris = containsUnknownExt(ref) ? await findFilesByExts([extractExt(ref)]) : [];

const augmentedUris = unknownUris.length
? sortPaths([...cache.getWorkspaceCache().allUris, ...unknownUris], {
? crossPathSort([...cache.getWorkspaceCache().allUris, ...unknownUris], {
pathKey: 'path',
shallowFirst: true,
})
Expand Down
6 changes: 3 additions & 3 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { sort as sortPaths } from 'cross-path-sort';

import { default as createDailyQuickPick } from './createDailyQuickPick';
import { readClipboard } from './clipboardUtils';

export { sortPaths, createDailyQuickPick, readClipboard };
export { sort as crossPathSort } from 'cross-path-sort';
export { createDailyQuickPick, readClipboard };

export * from './utils';
export * from './externalUtils';
export * from './replaceUtils';
export * from './searchUtils';
export * from './sortUtils';
72 changes: 72 additions & 0 deletions src/utils/sortUtils.pure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type * as vscode from 'vscode';
import { sort as _sortByPath } from 'cross-path-sort';

import { FoundRefT } from '../types';

export type GetMTimeOfFile = (uri: vscode.Uri) => Promise<number>;
type PickedFoundRefT = {
location: Pick<FoundRefT['location'], 'uri'>;
};

const sortByPath = async (referencesByPath: Record<string, PickedFoundRefT[]>) => {
// sort keys by path
return _sortByPath(Object.keys(referencesByPath), { shallowFirst: true });
};

const sortByAlphabetical = async (referencesByPath: Record<string, PickedFoundRefT[]>) => {
// sort keys by alphabetical
const pathsSorted = Object.keys(referencesByPath).sort();
return pathsSorted;
};

const getMTimeOfLastModifiedRef =
(getMTimeOfFile: GetMTimeOfFile) => async (refs: PickedFoundRefT[]) => {
// find the last modified ref's mtime in refs
const refsWithModified = await Promise.all(refs.map((ref) => getMTimeOfFile(ref.location.uri)));
return Math.max(...refsWithModified);
};

const withModifiedTime =
(getMTimeOfFile: GetMTimeOfFile, file: (path: string) => vscode.Uri) =>
(type: SortPathsByModifiedType) =>
async ([path, ref]: [string, PickedFoundRefT[]]): Promise<Readonly<[number, string]>> => {
const lastModified =
type === 'last-modified'
? await getMTimeOfFile(file(path))
: await getMTimeOfLastModifiedRef(getMTimeOfFile)(ref);
return [lastModified, path] as const;
};

const sortByLastModified =
(getMTimeOfFile: GetMTimeOfFile, file: (path: string) => vscode.Uri) =>
(type: SortPathsByModifiedType) =>
async (referencesByPath: Record<string, PickedFoundRefT[]>): Promise<string[]> => {
// sort keys by last modified
const refsWithModifiedTime = await Promise.all(
Object.entries(referencesByPath).map(withModifiedTime(getMTimeOfFile, file)(type)),
);

const pathsSorted = refsWithModifiedTime.sort(([a], [b]) => b - a).map(([, path]) => path);
return pathsSorted;
};

export type SortFunction = (
referencesByPath: Record<string, PickedFoundRefT[]>,
) => Promise<string[]>;
export type SortPathsType = 'path' | 'alphabet' | SortPathsByModifiedType;
export type SortPathsByModifiedType = 'last-modified' | 'last-modified-refs';
export type SortPathsDefaultType = typeof sortPathsDefaultType;
export const sortPathsDefaultType = 'path' satisfies SortPathsType;

export const _sortPaths = (getMTimeOfFile: GetMTimeOfFile, file: (path: string) => vscode.Uri) => {
const sortMap = {
path: sortByPath,
alphabet: sortByAlphabetical,
'last-modified': sortByLastModified(getMTimeOfFile, file)('last-modified'),
'last-modified-refs': sortByLastModified(getMTimeOfFile, file)('last-modified-refs'),
} as const satisfies Record<SortPathsType, SortFunction>;

return (order: SortPathsType) => (referencesByPath: Record<string, PickedFoundRefT[]>) => {
return sortMap[order](referencesByPath);
};
};
138 changes: 138 additions & 0 deletions src/utils/sortUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { URI } from 'vscode-uri';

import { GetMTimeOfFile, _sortPaths } from './sortUtils.pure';

const getMTimeOfFile: GetMTimeOfFile = async (uri: URI) => {
// will inject this prop in uri init
return (uri as any).lastModified;
};

const sortPaths = _sortPaths(getMTimeOfFile, URI.file);
const sortByPath = sortPaths('path');
const sortByAlphabetical = sortPaths('alphabet');
// const sortByLastModified = sortPaths('last-modified'); sortByLastModified is special (It will call URI.file in function).
const sortByLastModifiedRef = sortPaths('last-modified-refs');

const file: (modifiedTimeMap: Record<string, number>) => (path: string) => URI =
(map) => (path) => {
const uri = URI.file(path);
(uri as any).lastModified = map[path];
return uri;
};
const createURIWithLastModified = (lastModified: number): URI => {
const uri = URI.file(`/path/to/${lastModified}`);
(uri as any).lastModified = lastModified;
return uri;
};

describe('sortUtils', () => {
describe('sortByPath', () => {
it('should sort references by path in ascending order', async () => {
const referencesByPath = {
'b/path': [{ location: { uri: createURIWithLastModified(100) } }],
'a/path': [{ location: { uri: createURIWithLastModified(200) } }],
};

const result = await sortByPath(referencesByPath);
expect(result).toEqual(['a/path', 'b/path']);
});

it('should handle an empty referencesByPath object', async () => {
const referencesByPath: Record<string, any[]> = {};
const result = await sortByPath(referencesByPath);
expect(result).toEqual([]);
});
});

describe('sortByAlphabetical', () => {
it('should sort references by path in alphabetical order', async () => {
const referencesByPath = {
'b/path': [{ location: { uri: createURIWithLastModified(100) } }],
'a/path': [{ location: { uri: createURIWithLastModified(200) } }],
};

const result = await sortByAlphabetical(referencesByPath);
expect(result).toEqual(['a/path', 'b/path']);
});

it('should handle an empty referencesByPath object', async () => {
const referencesByPath: Record<string, any[]> = {};
const result = await sortByAlphabetical(referencesByPath);
expect(result).toEqual([]);
});
});

describe('sortByLastModified', () => {
it('should sort references by the last modified time', async () => {
const sortPaths = _sortPaths(getMTimeOfFile, file({ path1: 100, path2: 300 }));
const sortByLastModified = sortPaths('last-modified');
const referencesByPath = {
path1: [],
path2: [],
};

const result = await sortByLastModified(referencesByPath);
expect(result).toEqual(['path2', 'path1']);
});

it('should handle an empty referencesByPath object', async () => {
const sortPaths = _sortPaths(getMTimeOfFile, file({}));
const sortByLastModified = sortPaths('last-modified');

const referencesByPath: Record<string, any[]> = {};
const result = await sortByLastModified(referencesByPath);
expect(result).toEqual([]);
});

it('should correctly handle multiple file with the same modification time', async () => {
const referencesByPath = {
path1: [],
path2: [],
};

const sortPaths = _sortPaths(getMTimeOfFile, file({ path1: 0, path2: 0 }));
const sortByLastModified = sortPaths('last-modified');

// each result is accepted
expect(async () => {
await sortByLastModified(referencesByPath);
}).not.toThrow();
});
});

describe('sortByLastModifiedRef', () => {
it('should sort references by the last modified time of the reference in ascending order', async () => {
const references = {
path1: [
{ location: { uri: createURIWithLastModified(150) } },
{ location: { uri: createURIWithLastModified(1000) } },
],
path2: [
{ location: { uri: createURIWithLastModified(150) } },
{ location: { uri: createURIWithLastModified(2000) } },
],
};

const result = await sortByLastModifiedRef(references);
expect(result).toEqual(['path2', 'path1']);
});

it('should handle an empty array of references', async () => {
const references: Record<string, any> = {};
const result = await sortByLastModifiedRef(references);
expect(result).toEqual([]);
});

it('should correctly handle multiple references with the same modification time', async () => {
const references = {
path1: [{ location: { uri: createURIWithLastModified(150) } }],
path2: [{ location: { uri: createURIWithLastModified(150) } }],
};

// each result is accepted
expect(async () => {
await sortByLastModifiedRef(references);
}).not.toThrow();
});
});
});
9 changes: 9 additions & 0 deletions src/utils/sortUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as vscode from 'vscode';

import { _sortPaths, GetMTimeOfFile } from './sortUtils.pure';

const getMTimeOfFile: GetMTimeOfFile = async (uri: vscode.Uri) => {
const stat = await vscode.workspace.fs.stat(uri);
return stat.mtime;
};
export const sortPaths = _sortPaths(getMTimeOfFile, vscode.Uri.file);
6 changes: 6 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import fs from 'fs';
import { RefT, FoundRefT, LinkRuleT, ExtractedRefT } from '../types';
import { cache } from '../workspace';
import { isInCodeSpan, isInFencedCodeBlock } from './externalUtils';
import { SortPathsDefaultType, SortPathsType } from './sortUtils.pure';

const markdownExtRegex = /\.md$/i;

Expand Down Expand Up @@ -165,6 +166,11 @@ export function getMemoConfigProperty(
fallback: 'short',
): 'short' | 'long';

export function getMemoConfigProperty(
property: 'backlinksPanel.sortOrder',
fallback: SortPathsDefaultType,
): SortPathsType;

export function getMemoConfigProperty(
property: 'backlinksPanel.collapseParentItems',
fallback: null | boolean,
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7690,6 +7690,11 @@ vsce@^2.15.0:
yauzl "^2.3.1"
yazl "^2.2.2"

vscode-uri@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==

wait-for-expect@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463"
Expand Down