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

More Bundles #290

Merged
merged 9 commits into from
Jan 3, 2023
Merged
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
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@atom/source-map-support": "^0.3.4",
"@babel/core": "7.18.6",
"about": "file:packages/about",
"archive-view": "https://codeload.github.com/atom/archive-view/legacy.tar.gz/refs/tags/v0.66.0",
"archive-view": "file:packages/archive-view",
"async": "3.2.4",
"atom-dark-syntax": "file:packages/atom-dark-syntax",
"atom-dark-ui": "file:packages/atom-dark-ui",
Expand All @@ -41,10 +41,10 @@
"autoflow": "file:packages/autoflow",
"autosave": "https://codeload.github.com/atom/autosave/legacy.tar.gz/refs/tags/v0.24.6",
"babel-preset-atomic": "^5.0.0",
"background-tips": "https://codeload.github.com/pulsar-edit/background-tips/legacy.tar.gz/refs/tags/v0.28.1",
"background-tips": "file:packages/background-tips",
"base16-tomorrow-dark-theme": "file:packages/base16-tomorrow-dark-theme",
"base16-tomorrow-light-theme": "file:packages/base16-tomorrow-light-theme",
"bookmarks": "https://codeload.github.com/atom/bookmarks/legacy.tar.gz/refs/tags/v0.46.0",
"bookmarks": "file:packages/bookmarks",
"bracket-matcher": "https://codeload.github.com/atom/bracket-matcher/legacy.tar.gz/refs/tags/v0.92.0",
"chai": "4.3.4",
"clear-cut": "^2.0.2",
Expand Down Expand Up @@ -75,7 +75,7 @@
"go-to-line": "file:packages/go-to-line",
"grammar-selector": "file:packages/grammar-selector",
"grim": "2.0.3",
"image-view": "https://codeload.github.com/atom/image-view/legacy.tar.gz/refs/tags/v0.64.0",
"image-view": "file:packages/image-view",
"incompatible-packages": "file:packages/incompatible-packages",
"jasmine-json": "~0.0",
"jasmine-reporters": "1.1.0",
Expand Down Expand Up @@ -187,16 +187,16 @@
"solarized-dark-syntax": "file:./packages/solarized-dark-syntax",
"solarized-light-syntax": "file:./packages/solarized-light-syntax",
"about": "file:./packages/about",
"archive-view": "0.66.0",
"archive-view": "file:./packages/archive-view",
"autocomplete-atom-api": "0.10.7",
"autocomplete-css": "file:./packages/autocomplete-css",
"autocomplete-html": "file:./packages/autocomplete-html",
"autocomplete-plus": "2.42.4",
"autocomplete-snippets": "1.12.1",
"autoflow": "file:./packages/autoflow",
"autosave": "0.24.6",
"background-tips": "0.28.0",
"bookmarks": "0.46.0",
"background-tips": "file:./packages/background-tips",
"bookmarks": "file:./packages/bookmarks",
"bracket-matcher": "0.92.0",
"command-palette": "0.43.5",
"dalek": "file:./packages/dalek",
Expand All @@ -210,7 +210,7 @@
"git-diff": "file:./packages/git-diff",
"go-to-line": "file:./packages/go-to-line",
"grammar-selector": "file:./packages/grammar-selector",
"image-view": "0.64.0",
"image-view": "file:./packages/image-view",
"incompatible-packages": "file:./packages/incompatible-packages",
"keybinding-resolver": "0.39.1",
"line-ending-selector": "file:./packages/line-ending-selector",
Expand Down
20 changes: 20 additions & 0 deletions packages/archive-view/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Archive view package

Adds support for browsing archive files in Pulsar with the following extensions:

* `.egg`
* `.epub`
* `.jar`
* `.love`
* `.nupkg`
* `.tar`
* `.tar.gz`
* `.tgz`
* `.war`
* `.whl`
* `.xpi`
* `.zip`

Select a file to extract it to a temp file and open it in a new editor.

![](./resources/preview.png)
3 changes: 3 additions & 0 deletions packages/archive-view/keymaps/archive-view.cson
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'.archive-editor':
'k': 'core:move-up'
'j': 'core:move-down'
208 changes: 208 additions & 0 deletions packages/archive-view/lib/archive-editor-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/** @babel */
/** @jsx etch.dom */

import fs from 'fs'
import humanize from 'humanize-plus'
import archive from 'ls-archive'
import {CompositeDisposable, Disposable, Emitter, File} from 'atom'
import etch from 'etch'

import FileView from './file-view'
import DirectoryView from './directory-view'

export default class ArchiveEditorView {
constructor (archivePath) {
this.disposables = new CompositeDisposable()
this.emitter = new Emitter()
this.path = archivePath
this.file = new File(this.path)
this.entries = []
etch.initialize(this)

this.refresh()

this.disposables.add(this.file.onDidChange(() => this.refresh()))
this.disposables.add(this.file.onDidRename(() => this.refresh()))
this.disposables.add(this.file.onDidDelete(() => this.destroy()))

const focusHandler = () => this.focusSelectedFile()

this.element.addEventListener('focus', focusHandler)
this.disposables.add(new Disposable(() => this.element.removeEventListener('focus', focusHandler)))
}

update () {}

render () {
return (
<div className='archive-editor' tabIndex='-1'>
<div className='archive-container'>
<div ref='loadingMessage' className='padded icon icon-hourglass text-info'>{`Loading archive\u2026`}</div>
<div ref='errorMessage' className='padded icon icon-alert text-error' />
<div className='inset-panel'>
<div ref='summary' className='panel-heading' />
<ol ref='tree' className='archive-tree padded list-tree has-collapsable-children' />
</div>
</div>
</div>
)
}

copy () {
return new ArchiveEditorView(this.path)
}

destroy () {
while (this.entries.length > 0) {
this.entries.pop().destroy()
}
this.disposables.dispose()
this.emitter.emit('did-destroy')
etch.destroy(this)
}

onDidDestroy (callback) {
return this.emitter.on('did-destroy', callback)
}

onDidChangeTitle (callback) {
return this.emitter.on('did-change-title', callback)
}

serialize () {
return {
deserializer: this.constructor.name,
path: this.path
}
}

getPath () {
return this.file.getPath()
}

getTitle () {
return this.path ? this.file.getBaseName() : 'untitled'
}

getURI () {
return this.path
}

refresh () {
this.refs.summary.style.display = 'none'
this.refs.tree.style.display = 'none'
this.refs.loadingMessage.style.display = ''
this.refs.errorMessage.style.display = 'none'

if (this.path !== this.getPath()) {
this.path = this.getPath()
this.emitter.emit('did-change-title')
}

const originalPath = this.path
archive.list(this.path, {tree: true}, (error, entries) => {
if (originalPath !== this.path) {
return
}

if (error != null) {
let message = 'Reading the archive file failed'
if (error.message) {
message += `: ${error.message}`
}
this.refs.errorMessage.style.display = ''
this.refs.errorMessage.textContent = message
} else {
this.createTreeEntries(entries)
this.updateSummary()
}

// We hide the loading message _after_ creating the archive tree
// to avoid forced reflows.
this.refs.loadingMessage.style.display = 'none'
})
}

createTreeEntries (entries) {
while (this.entries.length > 0) {
this.entries.pop().destroy()
}

let index = 0
for (const entry of entries) {
if (entry.isDirectory()) {
const entryView = new DirectoryView(this, index, this.path, entry)
this.entries.push(entryView)
} else {
const entryView = new FileView(this, index, this.path, entry)
this.entries.push(entryView)
}
index++
}

this.selectFileAfterIndex(-1)

// Wait until selecting (focusing) the first file before appending the entries
// to avoid a double-forced reflow when focusing.
for (const entry of this.entries) {
this.refs.tree.appendChild(entry.element)
}

this.refs.tree.style.display = ''
}

updateSummary () {
const fileCount = this.entries.filter((entry) => entry instanceof FileView).length
const fileLabel = fileCount === 1 ? '1 file' : `${humanize.intComma(fileCount)} files`

const directoryCount = this.entries.filter((entry) => entry instanceof DirectoryView).length
const directoryLabel = directoryCount === 1 ? '1 folder' : `${humanize.intComma(directoryCount)} folders`

this.refs.summary.style.display = ''
let fileSize
try {
fileSize = fs.statSync(this.path)?.size;
} catch (e) {}
if (fileSize == null) fileSize = -1
this.refs.summary.textContent = `${humanize.fileSize(fileSize)} with ${fileLabel} and ${directoryLabel}`
}

focusSelectedFile () {
const selectedFile = this.refs.tree.querySelector('.selected')
if (selectedFile) {
selectedFile.focus()
}
}

selectFileBeforeIndex (index) {
for (let i = index - 1; i >= 0; i--) {
const previousEntry = this.entries[i]
if (previousEntry instanceof FileView) {
previousEntry.select()
break
} else {
if (previousEntry.selectLastFile()) {
break
}
}
}
}

selectFileAfterIndex (index) {
for (let i = index + 1; i < this.entries.length; i++) {
const nextEntry = this.entries[i]
if (nextEntry instanceof FileView) {
nextEntry.select()
break
} else {
if (nextEntry.selectFirstFile()) {
break
}
}
}
}

focus () {
this.focusSelectedFile()
}
}
74 changes: 74 additions & 0 deletions packages/archive-view/lib/archive-editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const fs = require('fs')
const path = require('path')
const {Disposable} = require('atom')

const getIconServices = require('./get-icon-services')
const ArchiveEditorView = require('./archive-editor-view')

module.exports = {
activate () {
this.disposable = atom.workspace.addOpener((filePath = '') => {
// Check that filePath exists before opening, in case a remote URI was given
if (!isPathSupported(filePath)) return;
let isFile = false
try {
isFile = fs.statSync(filePath)?.isFile()
} catch (e) {}
if (isFile) {
return new ArchiveEditorView(filePath)
}
})
},

deactivate () {
this.disposable.dispose()
for (const item of atom.workspace.getPaneItems()) {
if (item instanceof ArchiveEditorView) {
item.destroy()
}
}
},

consumeElementIcons (service) {
getIconServices().setElementIcons(service)
return new Disposable(() => getIconServices().resetElementIcons())
},

consumeFileIcons (service) {
getIconServices().setFileIcons(service)
return new Disposable(() => getIconServices().resetFileIcons())
},

deserialize (params = {}) {
let isFile = false
try {
isFile = fs.statSync(params.path)?.isFile()
} catch (e) {}
if (isFile) {
return new ArchiveEditorView(params.path)
} else {
console.warn(`Can't build ArchiveEditorView for path "${params.path}"; file no longer exists`)
}
}
}

function isPathSupported (filePath) {
switch (path.extname(filePath)) {
case '.egg':
case '.epub':
case '.jar':
case '.love':
case '.nupkg':
case '.tar':
case '.tgz':
case '.war':
case '.whl':
case '.xpi':
case '.zip':
return true
case '.gz':
return path.extname(path.basename(filePath, '.gz')) === '.tar'
default:
return false
}
}
Loading