Machine decaf tree-view source

This commit is contained in:
confused-Techie 2023-09-04 02:32:01 -07:00
parent f37b1dd1a8
commit 0c10f0a775
9 changed files with 2292 additions and 0 deletions

View File

@ -0,0 +1,81 @@
/*
* decaffeinate suggestions:
* DS002: Fix invalid constructor
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
let AddDialog;
const path = require('path');
const fs = require('fs-plus');
const Dialog = require('./dialog');
const {repoForPath} = require('./helpers');
module.exports =
(AddDialog = class AddDialog extends Dialog {
constructor(initialPath, isCreatingFile) {
let directoryPath;
this.isCreatingFile = isCreatingFile;
if (fs.isFileSync(initialPath)) {
directoryPath = path.dirname(initialPath);
} else {
directoryPath = initialPath;
}
let relativeDirectoryPath = directoryPath;
[this.rootProjectPath, relativeDirectoryPath] = atom.project.relativizePath(directoryPath);
if (relativeDirectoryPath.length > 0) { relativeDirectoryPath += path.sep; }
super({
prompt: "Enter the path for the new " + (isCreatingFile ? "file." : "folder."),
initialPath: relativeDirectoryPath,
select: false,
iconClass: isCreatingFile ? 'icon-file-add' : 'icon-file-directory-create'
});
}
onDidCreateFile(callback) {
return this.emitter.on('did-create-file', callback);
}
onDidCreateDirectory(callback) {
return this.emitter.on('did-create-directory', callback);
}
onConfirm(newPath) {
newPath = newPath.replace(/\s+$/, ''); // Remove trailing whitespace
const endsWithDirectorySeparator = newPath[newPath.length - 1] === path.sep;
if (!path.isAbsolute(newPath)) {
if (this.rootProjectPath == null) {
this.showError("You must open a directory to create a file with a relative path");
return;
}
newPath = path.join(this.rootProjectPath, newPath);
}
if (!newPath) { return; }
try {
if (fs.existsSync(newPath)) {
return this.showError(`'${newPath}' already exists.`);
} else if (this.isCreatingFile) {
if (endsWithDirectorySeparator) {
return this.showError(`File names must not end with a '${path.sep}' character.`);
} else {
fs.writeFileSync(newPath, '');
repoForPath(newPath)?.getPathStatus(newPath);
this.emitter.emit('did-create-file', newPath);
return this.close();
}
} else {
fs.makeTreeSync(newPath);
this.emitter.emit('did-create-directory', newPath);
return this.cancel();
}
} catch (error) {
return this.showError(`${error.message}.`);
}
}
});

View File

@ -0,0 +1,72 @@
/*
* decaffeinate suggestions:
* DS002: Fix invalid constructor
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
let CopyDialog;
const path = require('path');
const fs = require('fs-plus');
const Dialog = require('./dialog');
const {repoForPath} = require("./helpers");
module.exports =
(CopyDialog = class CopyDialog extends Dialog {
constructor(initialPath, {onCopy}) {
this.initialPath = initialPath;
this.onCopy = onCopy;
super({
prompt: 'Enter the new path for the duplicate.',
initialPath: atom.project.relativize(this.initialPath),
select: true,
iconClass: 'icon-arrow-right'
});
}
onConfirm(newPath) {
newPath = newPath.replace(/\s+$/, ''); // Remove trailing whitespace
if (!path.isAbsolute(newPath)) {
const [rootPath] = Array.from(atom.project.relativizePath(this.initialPath));
newPath = path.join(rootPath, newPath);
if (!newPath) { return; }
}
if (this.initialPath === newPath) {
this.close();
return;
}
if (fs.existsSync(newPath)) {
this.showError(`'${newPath}' already exists.`);
return;
}
let activeEditor = atom.workspace.getActiveTextEditor();
if (activeEditor?.getPath() !== this.initialPath) { activeEditor = null; }
try {
let repo;
if (fs.isDirectorySync(this.initialPath)) {
fs.copySync(this.initialPath, newPath);
this.onCopy?.({initialPath: this.initialPath, newPath});
} else {
fs.copy(this.initialPath, newPath, () => {
this.onCopy?.({initialPath: this.initialPath, newPath});
return atom.workspace.open(newPath, {
activatePane: true,
initialLine: activeEditor?.getLastCursor().getBufferRow(),
initialColumn: activeEditor?.getLastCursor().getBufferColumn()
}
);
});
}
if (repo = repoForPath(newPath)) {
repo.getPathStatus(this.initialPath);
repo.getPathStatus(newPath);
}
return this.close();
} catch (error) {
return this.showError(`${error.message}.`);
}
}
});

View File

@ -0,0 +1,31 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const fs = require('fs-plus');
const path = require('path');
class DefaultFileIcons {
iconClassForPath(filePath) {
const extension = path.extname(filePath);
if (fs.isSymbolicLinkSync(filePath)) {
return 'icon-file-symlink-file';
} else if (fs.isReadmePath(filePath)) {
return 'icon-book';
} else if (fs.isCompressedExtension(extension)) {
return 'icon-file-zip';
} else if (fs.isImageExtension(extension)) {
return 'icon-file-media';
} else if (fs.isPdfExtension(extension)) {
return 'icon-file-pdf';
} else if (fs.isBinaryExtension(extension)) {
return 'icon-file-binary';
} else {
return 'icon-file-text';
}
}
}
module.exports = new DefaultFileIcons;

View File

@ -0,0 +1,96 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
let Dialog;
const {TextEditor, CompositeDisposable, Disposable, Emitter, Range, Point} = require('atom');
const path = require('path');
const {getFullExtension} = require("./helpers");
module.exports =
(Dialog = class Dialog {
constructor(param) {
if (param == null) { param = {}; }
const {initialPath, select, iconClass, prompt} = param;
this.emitter = new Emitter();
this.disposables = new CompositeDisposable();
this.element = document.createElement('div');
this.element.classList.add('tree-view-dialog');
this.promptText = document.createElement('label');
this.promptText.classList.add('icon');
if (iconClass) { this.promptText.classList.add(iconClass); }
this.promptText.textContent = prompt;
this.element.appendChild(this.promptText);
this.miniEditor = new TextEditor({mini: true});
const blurHandler = () => {
if (document.hasFocus()) { return this.close(); }
};
this.miniEditor.element.addEventListener('blur', blurHandler);
this.disposables.add(new Disposable(() => this.miniEditor.element.removeEventListener('blur', blurHandler)));
this.disposables.add(this.miniEditor.onDidChange(() => this.showError()));
this.element.appendChild(this.miniEditor.element);
this.errorMessage = document.createElement('div');
this.errorMessage.classList.add('error-message');
this.element.appendChild(this.errorMessage);
atom.commands.add(this.element, {
'core:confirm': () => this.onConfirm(this.miniEditor.getText()),
'core:cancel': () => this.cancel()
}
);
this.miniEditor.setText(initialPath);
if (select) {
let selectionEnd;
const extension = getFullExtension(initialPath);
const baseName = path.basename(initialPath);
const selectionStart = initialPath.length - baseName.length;
if (baseName === extension) {
selectionEnd = initialPath.length;
} else {
selectionEnd = initialPath.length - extension.length;
}
this.miniEditor.setSelectedBufferRange(Range(Point(0, selectionStart), Point(0, selectionEnd)));
}
}
attach() {
this.panel = atom.workspace.addModalPanel({item: this});
this.miniEditor.element.focus();
return this.miniEditor.scrollToCursorPosition();
}
close() {
const {
panel
} = this;
this.panel = null;
panel?.destroy();
this.emitter.dispose();
this.disposables.dispose();
this.miniEditor.destroy();
const activePane = atom.workspace.getCenter().getActivePane();
if (!activePane.isDestroyed()) { return activePane.activate(); }
}
cancel() {
this.close();
return document.querySelector('.tree-view')?.focus();
}
showError(message) {
if (message == null) { message = ''; }
this.errorMessage.textContent = message;
if (message) {
this.element.classList.add('error');
return window.setTimeout((() => this.element.classList.remove('error')), 300);
}
}
});

View File

@ -0,0 +1,36 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const path = require("path");
module.exports = {
repoForPath(goalPath) {
const iterable = atom.project.getPaths();
for (let i = 0; i < iterable.length; i++) {
const projectPath = iterable[i];
if ((goalPath === projectPath) || (goalPath.indexOf(projectPath + path.sep) === 0)) {
return atom.project.getRepositories()[i];
}
}
return null;
},
getStyleObject(el) {
const styleProperties = window.getComputedStyle(el);
const styleObject = {};
for (let property in styleProperties) {
const value = styleProperties.getPropertyValue(property);
const camelizedAttr = property.replace(/\-([a-z])/g, (a, b) => b.toUpperCase());
styleObject[camelizedAttr] = value;
}
return styleObject;
},
getFullExtension(filePath) {
const basename = path.basename(filePath);
const position = basename.indexOf('.');
if (position > 0) { return basename.slice(position); } else { return ''; }
}
};

View File

@ -0,0 +1,41 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS104: Avoid inline assignments
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
let IgnoredNames;
let Minimatch = null; // Defer requiring until actually needed
module.exports =
(IgnoredNames = class IgnoredNames {
constructor() {
let left;
this.ignoredPatterns = [];
if (Minimatch == null) { ({
Minimatch
} = require('minimatch')); }
let ignoredNames = (left = atom.config.get('core.ignoredNames')) != null ? left : [];
if (typeof ignoredNames === 'string') { ignoredNames = [ignoredNames]; }
for (let ignoredName of Array.from(ignoredNames)) {
if (ignoredName) {
try {
this.ignoredPatterns.push(new Minimatch(ignoredName, {matchBase: true, dot: true}));
} catch (error) {
atom.notifications.addWarning(`Error parsing ignore pattern (${ignoredName})`, {detail: error.message});
}
}
}
}
matches(filePath) {
for (let ignoredPattern of Array.from(this.ignoredPatterns)) {
if (ignoredPattern.match(filePath)) { return true; }
}
return false;
}
});

View File

@ -0,0 +1,87 @@
/*
* decaffeinate suggestions:
* DS002: Fix invalid constructor
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
let MoveDialog;
const path = require('path');
const fs = require('fs-plus');
const Dialog = require('./dialog');
const {repoForPath} = require("./helpers");
module.exports =
(MoveDialog = class MoveDialog extends Dialog {
constructor(initialPath, {willMove, onMove, onMoveFailed}) {
let prompt;
this.initialPath = initialPath;
this.willMove = willMove;
this.onMove = onMove;
this.onMoveFailed = onMoveFailed;
if (fs.isDirectorySync(this.initialPath)) {
prompt = 'Enter the new path for the directory.';
} else {
prompt = 'Enter the new path for the file.';
}
super({
prompt,
initialPath: atom.project.relativize(this.initialPath),
select: true,
iconClass: 'icon-arrow-right'
});
}
onConfirm(newPath) {
newPath = newPath.replace(/\s+$/, ''); // Remove trailing whitespace
if (!path.isAbsolute(newPath)) {
const [rootPath] = Array.from(atom.project.relativizePath(this.initialPath));
newPath = path.join(rootPath, newPath);
if (!newPath) { return; }
}
if (this.initialPath === newPath) {
this.close();
return;
}
if (!this.isNewPathValid(newPath)) {
this.showError(`'${newPath}' already exists.`);
return;
}
const directoryPath = path.dirname(newPath);
try {
let repo;
this.willMove?.({initialPath: this.initialPath, newPath});
if (!fs.existsSync(directoryPath)) { fs.makeTreeSync(directoryPath); }
fs.moveSync(this.initialPath, newPath);
this.onMove?.({initialPath: this.initialPath, newPath});
if (repo = repoForPath(newPath)) {
repo.getPathStatus(this.initialPath);
repo.getPathStatus(newPath);
}
return this.close();
} catch (error) {
this.showError(`${error.message}.`);
return this.onMoveFailed?.({initialPath: this.initialPath, newPath});
}
}
isNewPathValid(newPath) {
try {
const oldStat = fs.statSync(this.initialPath);
const newStat = fs.statSync(newPath);
// New path exists so check if it points to the same file as the initial
// path to see if the case of the file name is being changed on a on a
// case insensitive filesystem.
return (this.initialPath.toLowerCase() === newPath.toLowerCase()) &&
(oldStat.dev === newStat.dev) &&
(oldStat.ino === newStat.ino);
} catch (error) {
return true; // new path does not exist so it is valid
}
}
});

View File

@ -0,0 +1,255 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
let RootDragAndDropHandler;
const url = require('url');
const {ipcRenderer, remote} = require('electron');
// TODO: Support dragging external folders and using the drag-and-drop indicators for them
// Currently they're handled in TreeView's drag listeners
module.exports =
(RootDragAndDropHandler = class RootDragAndDropHandler {
constructor(treeView) {
this.onDragStart = this.onDragStart.bind(this);
this.onDragLeave = this.onDragLeave.bind(this);
this.onDragEnd = this.onDragEnd.bind(this);
this.onDragOver = this.onDragOver.bind(this);
this.onDropOnOtherWindow = this.onDropOnOtherWindow.bind(this);
this.onDrop = this.onDrop.bind(this);
this.treeView = treeView;
ipcRenderer.on('tree-view:project-folder-dropped', this.onDropOnOtherWindow);
this.handleEvents();
}
dispose() {
return ipcRenderer.removeListener('tree-view:project-folder-dropped', this.onDropOnOtherWindow);
}
handleEvents() {
// onDragStart is called directly by TreeView's onDragStart
// will be cleaned up by tree view, since they are tree-view's handlers
this.treeView.element.addEventListener('dragenter', this.onDragEnter.bind(this));
this.treeView.element.addEventListener('dragend', this.onDragEnd.bind(this));
this.treeView.element.addEventListener('dragleave', this.onDragLeave.bind(this));
this.treeView.element.addEventListener('dragover', this.onDragOver.bind(this));
return this.treeView.element.addEventListener('drop', this.onDrop.bind(this));
}
onDragStart(e) {
if (!this.treeView.list.contains(e.target)) { return; }
this.prevDropTargetIndex = null;
e.dataTransfer.setData('atom-tree-view-root-event', 'true');
const projectRoot = e.target.closest('.project-root');
const {
directory
} = projectRoot;
e.dataTransfer.setData('project-root-index', Array.from(projectRoot.parentElement.children).indexOf(projectRoot));
let rootIndex = -1;
for (let index = 0; index < this.treeView.roots.length; index++) { const root = this.treeView.roots[index]; if (root.directory === directory) { rootIndex = index; break; } }
e.dataTransfer.setData('from-root-index', rootIndex);
e.dataTransfer.setData('from-root-path', directory.path);
e.dataTransfer.setData('from-window-id', this.getWindowId());
e.dataTransfer.setData('text/plain', directory.path);
if (['darwin', 'linux'].includes(process.platform)) {
let pathUri;
if (!this.uriHasProtocol(directory.path)) { pathUri = `file://${directory.path}`; }
return e.dataTransfer.setData('text/uri-list', pathUri);
}
}
uriHasProtocol(uri) {
try {
return (url.parse(uri).protocol != null);
} catch (error) {
return false;
}
}
onDragEnter(e) {
if (!this.treeView.list.contains(e.target)) { return; }
if (!this.isAtomTreeViewEvent(e)) { return; }
return e.stopPropagation();
}
onDragLeave(e) {
if (!this.treeView.list.contains(e.target)) { return; }
if (!this.isAtomTreeViewEvent(e)) { return; }
e.stopPropagation();
if (e.target === e.currentTarget) { return this.removePlaceholder(); }
}
onDragEnd(e) {
if (!e.target.matches('.project-root-header')) { return; }
if (!this.isAtomTreeViewEvent(e)) { return; }
e.stopPropagation();
return this.clearDropTarget();
}
onDragOver(e) {
let element;
if (!this.treeView.list.contains(e.target)) { return; }
if (!this.isAtomTreeViewEvent(e)) { return; }
e.preventDefault();
e.stopPropagation();
const entry = e.currentTarget;
if (this.treeView.roots.length === 0) {
this.treeView.list.appendChild(this.getPlaceholder());
return;
}
const newDropTargetIndex = this.getDropTargetIndex(e);
if (newDropTargetIndex == null) { return; }
if (this.prevDropTargetIndex === newDropTargetIndex) { return; }
this.prevDropTargetIndex = newDropTargetIndex;
const projectRoots = this.treeView.roots;
if (newDropTargetIndex < projectRoots.length) {
element = projectRoots[newDropTargetIndex];
element.classList.add('is-drop-target');
return element.parentElement.insertBefore(this.getPlaceholder(), element);
} else {
element = projectRoots[newDropTargetIndex - 1];
element.classList.add('drop-target-is-after');
return element.parentElement.insertBefore(this.getPlaceholder(), element.nextSibling);
}
}
onDropOnOtherWindow(e, fromItemIndex) {
const paths = atom.project.getPaths();
paths.splice(fromItemIndex, 1);
atom.project.setPaths(paths);
return this.clearDropTarget();
}
clearDropTarget() {
const element = this.treeView.element.querySelector(".is-dragging");
element?.classList.remove('is-dragging');
element?.updateTooltip();
return this.removePlaceholder();
}
onDrop(e) {
let projectPaths;
if (!this.treeView.list.contains(e.target)) { return; }
if (!this.isAtomTreeViewEvent(e)) { return; }
e.preventDefault();
e.stopPropagation();
const {dataTransfer} = e;
const fromWindowId = parseInt(dataTransfer.getData('from-window-id'));
const fromRootPath = dataTransfer.getData('from-root-path');
const fromIndex = parseInt(dataTransfer.getData('project-root-index'));
const fromRootIndex = parseInt(dataTransfer.getData('from-root-index'));
let toIndex = this.getDropTargetIndex(e);
this.clearDropTarget();
if (fromWindowId === this.getWindowId()) {
if (fromIndex !== toIndex) {
projectPaths = atom.project.getPaths();
projectPaths.splice(fromIndex, 1);
if (toIndex > fromIndex) { toIndex -= 1; }
projectPaths.splice(toIndex, 0, fromRootPath);
return atom.project.setPaths(projectPaths);
}
} else {
projectPaths = atom.project.getPaths();
projectPaths.splice(toIndex, 0, fromRootPath);
atom.project.setPaths(projectPaths);
if (!isNaN(fromWindowId)) {
// Let the window where the drag started know that the tab was dropped
const browserWindow = remote.BrowserWindow.fromId(fromWindowId);
return browserWindow?.webContents.send('tree-view:project-folder-dropped', fromIndex);
}
}
}
getDropTargetIndex(e) {
if (this.isPlaceholder(e.target)) { return; }
const projectRoots = this.treeView.roots;
let projectRoot = e.target.closest('.project-root');
if (!projectRoot) { projectRoot = projectRoots[projectRoots.length - 1]; }
if (!projectRoot) { return 0; }
const projectRootIndex = this.treeView.roots.indexOf(projectRoot);
const center = projectRoot.getBoundingClientRect().top + (projectRoot.offsetHeight / 2);
if (e.pageY < center) {
return projectRootIndex;
} else {
return projectRootIndex + 1;
}
}
canDragStart(e) {
return e.target.closest('.project-root-header');
}
isDragging(e) {
for (let item of Array.from(e.dataTransfer.items)) {
if (item.type === 'from-root-path') {
return true;
}
}
return false;
}
isAtomTreeViewEvent(e) {
for (let item of Array.from(e.dataTransfer.items)) {
if (item.type === 'atom-tree-view-root-event') {
return true;
}
}
return false;
}
getPlaceholder() {
if (!this.placeholderEl) {
this.placeholderEl = document.createElement('li');
this.placeholderEl.classList.add('placeholder');
}
return this.placeholderEl;
}
removePlaceholder() {
this.placeholderEl?.remove();
return this.placeholderEl = null;
}
isPlaceholder(element) {
return element.classList.contains('.placeholder');
}
getWindowId() {
return this.processId != null ? this.processId : (this.processId = atom.getCurrentWindow().id);
}
});

File diff suppressed because it is too large Load Diff