Skip to content
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
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ out/
plugins/
tasks/
test/
gulpfile.js
gulpfile.js
src/@types/vscode.d.ts
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Change Log
All notable changes to the "Projects" extension will be documented in this file.

## [Unreleased]

### Added

- Added drag and drop to the workspaces view. Drop one or more workspaces into a workspace group to assign them, or drop them on a workspace outside of all groups to remove them from their current group.

## [2.0.0] - 2024-09-30

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ If the key bindings don't work, please check `Preferences -> Keyboard Shortcuts`
### Workspaces

* `Click` - Open a workspace. If `l13Projects.openInNewWindow` is true the workspace opens in a new window.
* `Drag and Drop` - Drag one or more selected workspaces into a workspace group to add them, or drop them on a workspace outside of all groups to remove them from their current group. Dropping a workspace on another workspace inside a group adds it to the same group.
* Tooltip shows path of the workspace.

### Workspaces Context Icons
Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"theme": "dark"
},
"engines": {
"vscode": "^1.56.0"
"vscode": "^1.66.0"
},
"categories": [
"Other"
Expand Down
30 changes: 30 additions & 0 deletions src/@types/vscode.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Type augmentation for the VS Code tree drag-and-drop API introduced in 1.66.0.
// The project keeps @types/vscode at 1.56.0 to avoid breaking changes in TreeItem.iconPath,
// so the missing drag-and-drop types are declared here.

declare module 'vscode' {

export class DataTransferItem {
constructor(value: string | object);
public asString(): Thenable<string>;
public asFile(): DataTransferFile | undefined;
}

export class DataTransfer {
public get(mimeType: string): DataTransferItem | undefined;
public set(mimeType: string, value: DataTransferItem): void;
public forEach(callbackfn: (item: DataTransferItem, mimeType: string) => void): void;
}

export interface TreeDragAndDropController<T> {
readonly dragMimeTypes: readonly string[];
readonly dropMimeTypes: readonly string[];
handleDrag?(source: T[], dataTransfer: DataTransfer, token: CancellationToken): Thenable<void> | void;
handleDrop?(target: T | undefined, dataTransfer: DataTransfer, token: CancellationToken): Thenable<void> | void;
}

export interface TreeViewOptions<T> {
dragAndDropController?: TreeDragAndDropController<T>;
}

}
1 change: 1 addition & 0 deletions src/@types/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export type WorkspacesStates = {
tags: Tag[],
workspaces: Project[],
workspaceGroups: WorkspaceGroup[],
workspaceGroupsState: WorkspaceGroupsState,
};

export type RefreshWorkspacesStates = {
Expand Down
2 changes: 2 additions & 0 deletions src/commands/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export function activate (context: vscode.ExtensionContext) {
tags: tagsState.get(),
workspaces: workspacesState.cache,
workspaceGroups: workspaceGroupsState.get(),
workspaceGroupsState,
});

const tagSorter = new TagSorter(workspacesProvider, workspaceGroupsState);
Expand All @@ -102,6 +103,7 @@ export function activate (context: vscode.ExtensionContext) {
const treeView = vscode.window.createTreeView('l13ProjectsWorkspaces', {
showCollapseAll: true,
treeDataProvider: workspacesProvider,
dragAndDropController: workspacesProvider,
});

// Tree View
Expand Down
65 changes: 61 additions & 4 deletions src/sidebar/WorkspacesProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import * as workspaces from '../common/workspaces';

import { ProjectsState } from '../states/ProjectsState';
import { HotkeySlotsState } from '../states/HotkeySlotsState';
import type { WorkspaceGroupsState } from '../states/WorkspaceGroupsState';

import { WorkspaceGroupTreeItem } from './trees/groups/WorkspaceGroupTreeItem';
import { ColorPickerTreeItem } from './trees/items/ColorPickerTreeItem';
import { CurrentWorkspaceTreeItem } from './trees/items/CurrentWorkspaceTreeItem';
import { WorkspaceTreeItem } from './trees/items/WorkspaceTreeItem';
import { UnknownProjectTreeItem } from './trees/items/UnknownProjectTreeItem';

Expand All @@ -40,7 +43,12 @@ import { UnknownProjectTreeItem } from './trees/items/UnknownProjectTreeItem';

// Exports ____________________________________________________________________

export class WorkspacesProvider implements vscode.TreeDataProvider<WorkspacesTreeItems> {
const DRAG_MIME_TYPE = 'application/vnd.code.tree.l13ProjectsWorkspaces';

export class WorkspacesProvider implements vscode.TreeDataProvider<WorkspacesTreeItems>, vscode.TreeDragAndDropController<WorkspacesTreeItems> {

public dragMimeTypes = [DRAG_MIME_TYPE];
public dropMimeTypes = [DRAG_MIME_TYPE];

public static current: WorkspacesProvider;

Expand Down Expand Up @@ -84,15 +92,18 @@ export class WorkspacesProvider implements vscode.TreeDataProvider<WorkspacesTre
public tags: Tag[] = [];
public slots: HotkeySlotsState = null;

public readonly workspaceGroupsState: WorkspaceGroupsState;

public colorPickerProject: Project = null;
public readonly colorPickerTreeItem = new ColorPickerTreeItem();

private constructor ({ hotkeySlots, workspaces: cache, workspaceGroups, tags }: WorkspacesStates) {
private constructor ({ hotkeySlots, workspaces: cache, workspaceGroups, workspaceGroupsState, tags }: WorkspacesStates) {

this.tags = tags;
this.slots = hotkeySlots;
this.workspaces = cache;
this.workspaceGroups = workspaceGroups;
this.workspaceGroupsState = workspaceGroupsState;

}

Expand Down Expand Up @@ -202,7 +213,53 @@ export class WorkspacesProvider implements vscode.TreeDataProvider<WorkspacesTre

}

public handleDrag (source: WorkspacesTreeItems[], dataTransfer: vscode.DataTransfer) {

const paths: string[] = [];

for (const item of source) {
if (item instanceof WorkspaceTreeItem || item instanceof CurrentWorkspaceTreeItem) {
paths.push(item.project.path);
}
}

if (paths.length) {
dataTransfer.set(DRAG_MIME_TYPE, new vscode.DataTransferItem(JSON.stringify(paths)));
}

}

public async handleDrop (target: WorkspacesTreeItems | undefined, dataTransfer: vscode.DataTransfer) {

const transferItem = dataTransfer.get(DRAG_MIME_TYPE);
if (!transferItem) return;

const paths: string[] = JSON.parse(await transferItem.asString());
if (!paths.length) return;

let group: WorkspaceGroup = null;

if (target instanceof WorkspaceGroupTreeItem) {
group = target.group;
} else if (target instanceof WorkspaceTreeItem || target instanceof CurrentWorkspaceTreeItem) {
// Only treat projects as a drop target for the group they belong to.
group = this.workspaceGroups.find((workspaceGroup) => workspaceGroup.paths.includes(target.project.path));
// A project outside of any group means "remove from all groups".
if (!group) this.workspaceGroupsState.removeWorkspaces(paths);
return;
} else if (target) {
// Any other item (tags, root or type groups) is not a valid drop target.
return;
}

const projects = paths
.map((path) => this.workspaces.find((project) => project.path === path))
.filter((project) => project);

if (group) this.workspaceGroupsState.addWorkspaces(projects, group);
else this.workspaceGroupsState.removeWorkspaces(paths);

}

}

// Functions __________________________________________________________________

51 changes: 47 additions & 4 deletions src/states/WorkspaceGroupsState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,57 @@ export class WorkspaceGroupsState {
public addWorkspace (workspace: Project, workspaceGroup: WorkspaceGroup) {

const workspaceGroups = this.get();
const targetGroup = workspaceGroups.find((group) => group.id === workspaceGroup.id);

if (!workspaceGroup.paths.includes(workspace.path)) {
if (targetGroup && !targetGroup.paths.includes(workspace.path)) {
const previousWorkspaceGroup = workspaceGroups.find((group) => remove(group.paths, workspace.path));
workspaceGroup.paths.push(workspace.path);
workspaceGroup.paths.sort();
targetGroup.paths.push(workspace.path);
targetGroup.paths.sort();
this.save(workspaceGroups);
if (previousWorkspaceGroup) this._onDidUpdateWorkspaceGroup.fire(previousWorkspaceGroup);
this._onDidUpdateWorkspaceGroup.fire(workspaceGroup);
this._onDidUpdateWorkspaceGroup.fire(targetGroup);
this._onDidChangeWorkspaceGroups.fire(workspaceGroups);
}

}

public addWorkspaces (workspaces: Project[], workspaceGroup: WorkspaceGroup) {

const workspaceGroups = this.get();
const targetGroup = workspaceGroups.find((group) => group.id === workspaceGroup.id);
if (!targetGroup) return;

let hasChanged = false;

for (const workspace of workspaces) {
if (targetGroup.paths.includes(workspace.path)) continue;
workspaceGroups.find((group) => remove(group.paths, workspace.path));
targetGroup.paths.push(workspace.path);
hasChanged = true;
}

if (hasChanged) {
targetGroup.paths.sort();
this.save(workspaceGroups);
this._onDidUpdateWorkspaceGroup.fire(targetGroup);
this._onDidChangeWorkspaceGroups.fire(workspaceGroups);
}

}

public removeWorkspaces (paths: string[]) {

const workspaceGroups = this.get();
let hasChanged = false;

for (const workspaceGroup of workspaceGroups) {
for (const path of paths) {
if (remove(workspaceGroup.paths, path)) hasChanged = true;
}
}

if (hasChanged) {
this.save(workspaceGroups);
this._onDidChangeWorkspaceGroups.fire(workspaceGroups);
}

Expand Down