Skip to content

Commit

Permalink
support nested repos + specs, addressing atom/atom#2203 and atom/atom…
Browse files Browse the repository at this point in the history
  • Loading branch information
jarig committed Jun 24, 2019
1 parent b0debd0 commit 88e5291
Show file tree
Hide file tree
Showing 10 changed files with 427 additions and 39 deletions.
12 changes: 12 additions & 0 deletions lib/directory-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class DirectoryView {
this.element.header = this.header
this.element.entries = this.entries
this.element.directoryName = this.directoryName
this.element.refreshRepoStatus = this.refreshRepoStatus.bind(this)
}

updateIcon () {
Expand Down Expand Up @@ -134,6 +135,17 @@ class DirectoryView {
}
}

refreshRepoStatus (isRecursive = true, includeCollapsed = false) {
this.directory.refreshRepoStatus()
if (isRecursive) {
for (let entry of this.entries.children) {
if (entry.classList.contains('directory') && (entry.isExpanded || includeCollapsed)) {
entry.refreshRepoStatus(true, includeCollapsed)
}
}
}
}

toggleExpansion (isRecursive) {
if (isRecursive == null) {
isRecursive = false
Expand Down
41 changes: 25 additions & 16 deletions lib/directory.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const {CompositeDisposable, Emitter} = require('atom')
const fs = require('fs-plus')
const PathWatcher = require('pathwatcher')
const File = require('./file')
const {repoForPath} = require('./helpers')
const {repoForPath, getRepoCacheSize} = require('./helpers')

module.exports =
class Directory {
Expand All @@ -30,6 +30,10 @@ class Directory {
this.lowerCasePath = this.path.toLowerCase()
this.lowerCaseRealPath = this.lowerCasePath
}
this.repo = repoForPath(this.path)
if (this.repo && atom.config.get('tree-view.refreshVcsStatusOnProjectOpen') >= getRepoCacheSize()) {
this.refreshRepoStatus()
}

if (this.isRoot == null) {
this.isRoot = false
Expand Down Expand Up @@ -69,8 +73,7 @@ class Directory {
this.status = null
this.entries = new Map()

const repo = repoForPath(this.path)
this.submodule = repo && repo.isSubmodule(this.path)
this.submodule = this.repo && this.repo.isSubmodule(this.path)

this.subscribeToRepo()
this.updateStatus()
Expand Down Expand Up @@ -129,24 +132,30 @@ class Directory {
}
}

refreshRepoStatus () {
if (this.repo == null) return

this.repo.refreshIndex()
this.repo.refreshStatus()
}

// Subscribe to project's repo for changes to the Git status of this directory.
subscribeToRepo () {
const repo = repoForPath(this.path)
if (repo == null) return
if (this.repo == null) return

this.subscriptions.add(repo.onDidChangeStatus(event => {
this.subscriptions.add(this.repo.onDidChangeStatus(event => {
if (this.contains(event.path)) {
this.updateStatus(repo)
this.updateStatus(this.repo)
}
}))
this.subscriptions.add(repo.onDidChangeStatuses(() => {
this.updateStatus(repo)
this.subscriptions.add(this.repo.onDidChangeStatuses(() => {
this.updateStatus(this.repo)
}))
}

// Update the status property of this directory using the repo.
updateStatus () {
const repo = repoForPath(this.path)
updateStatus (repo = null) {
if (repo == null) repo = this.repo
if (repo == null) return

let newStatus = null
Expand All @@ -156,7 +165,8 @@ class Directory {
newStatus = 'ignored-name'
} else {
let status
if (this.isRoot) {
if (!repo.relativize(this.path + '/')) {
// repo root directory
// repo.getDirectoryStatus will always fail for the
// root because the path is relativized + concatenated with '/'
// making the matching string be '/'. Then path.indexOf('/')
Expand Down Expand Up @@ -184,8 +194,7 @@ class Directory {
// Is the given path ignored?
isPathIgnored (filePath) {
if (atom.config.get('tree-view.hideVcsIgnoredFiles')) {
const repo = repoForPath(this.path)
if (repo && repo.isProjectAtRoot() && repo.isPathIgnored(filePath)) return true
if (this.repo && this.repo.isProjectAtRoot() && this.repo.isPathIgnored(filePath)) return true
}

if (atom.config.get('tree-view.hideIgnoredNames')) {
Expand Down Expand Up @@ -276,7 +285,7 @@ class Directory {
} catch (error) {
names = []
}
names.sort(new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}).compare)
names.sort(new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare)

const files = []
const directories = []
Expand Down Expand Up @@ -319,7 +328,7 @@ class Directory {
// track the insertion index for the created views
files.push(name)
} else {
files.push(new File({name, fullPath, symlink, ignoredNames: this.ignoredNames, useSyncFS: this.useSyncFS, stats: statFlat}))
files.push(new File({ name, fullPath, symlink, ignoredNames: this.ignoredNames, useSyncFS: this.useSyncFS, stats: statFlat }))
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions lib/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class File {

this.path = fullPath
this.realPath = this.path
this.repo = repoForPath(this.path)

this.subscribeToRepo()
this.updateStatus()
Expand Down Expand Up @@ -49,22 +50,21 @@ class File {

// Subscribe to the project's repo for changes to the Git status of this file.
subscribeToRepo () {
const repo = repoForPath(this.path)
if (repo == null) return
if (this.repo == null) return

this.subscriptions.add(repo.onDidChangeStatus(event => {
this.subscriptions.add(this.repo.onDidChangeStatus(event => {
if (this.isPathEqual(event.path)) {
this.updateStatus(repo)
this.updateStatus(this.repo)
}
}))
this.subscriptions.add(repo.onDidChangeStatuses(() => {
this.updateStatus(repo)
this.subscriptions.add(this.repo.onDidChangeStatuses(() => {
this.updateStatus(this.repo)
}))
}

// Update the status property of this directory using the repo.
updateStatus () {
const repo = repoForPath(this.path)
updateStatus (repo) {
if (repo == null) repo = this.repo
if (repo == null) return

let newStatus = null
Expand Down
16 changes: 12 additions & 4 deletions lib/get-icon-services.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,19 @@ class IconServices {
iconClass = 'icon-file-symlink-directory'
} else {
iconClass = 'icon-file-directory'
if (view.directory.isRoot) {
const repo = repoForPath(view.directory.path)
if (repo && repo.isProjectAtRoot()) iconClass = 'icon-repo'
let repo = repoForPath(view.directory.path)
if (repo) {
let relPath = repo.relativize(view.directory.path + '/')
if (relPath != null && relPath.length === 0) {
iconClass = 'icon-repo'
}
} else {
if (view.directory.submodule) iconClass = 'icon-file-submodule'
if (view.directory.isRoot) {
const repo = repoForPath(view.directory.path)
if (repo && repo.isProjectAtRoot()) iconClass = 'icon-repo'
} else {
if (view.directory.submodule) iconClass = 'icon-file-submodule'
}
}
}
classes.push(iconClass)
Expand Down
70 changes: 67 additions & 3 deletions lib/helpers.coffee
Original file line number Diff line number Diff line change
@@ -1,11 +1,75 @@
path = require "path"
fs = require 'fs-plus'
{GitRepository} = require 'atom'

module.exports =
repositoryCache: {}
fakeProjectRoots: []

getRepoCache: ->
module.exports.repositoryCache

isFakeProjectRoot: (checkPath) ->
path.normalize(checkPath) in module.exports.fakeProjectRoots

getRepoCacheSize: ->
Object.keys(module.exports.repositoryCache).length

resetRepoCache: ->
module.exports.repositoryCache = {}

repoForPath: (goalPath) ->
result = null
project = null
projectIndex = null
_this = module.exports
for projectPath, i in atom.project.getPaths()
if goalPath is projectPath or goalPath.indexOf(projectPath + path.sep) is 0
return atom.project.getRepositories()[i]
null
if goalPath.indexOf(projectPath) is 0
project = projectPath
projectIndex = i
# can't find related projects, so repo can't be assigned
return null unless project?
walkUpwards = (startDir, toDir, projectIndex) ->
if fs.existsSync(startDir + '/.git')
for provider in atom.project.repositoryProviders
if _this.repositoryCache[startDir]
return _this.repositoryCache[startDir]
for dProvider in atom.project.directoryProviders
break if directory = dProvider.directoryForURISync(startDir)
directory ?= atom.project.defaultDirectoryProvider.directoryForURISync(startDir)
repo = GitRepository.open(startDir, {project: provider.project, \
refreshOnWindowFocus: atom.config.get('tree-view.refreshVcsStatusOnFocusChange') > _this.getRepoCacheSize()})
return null unless repo
repo.onDidDestroy( ->
delete _this.repositoryCache[startDir]
indexToRemove = null
for dir, i in atom.project.getDirectories()
if startDir is dir.getPath()
indexToRemove = i
break
atom.project.rootDirectories.splice(indexToRemove, 1)
atom.project.repositories.splice(indexToRemove, 1)
)
existsInAtom = false
for dir in atom.project.rootDirectories
if dir.getRealPathSync() is directory.getRealPathSync()
existsInAtom = true
break
if not existsInAtom
atom.project.repositories.splice(0, 0, repo)
atom.project.rootDirectories.splice(0, 0, directory)
_this.fakeProjectRoots.push(startDir)
_this.repositoryCache[startDir] = repo
return repo
if startDir is toDir
# top of project
if atom.project.getRepositories()[projectIndex]
return atom.project.getRepositories()[projectIndex]
return null
dirName = path.dirname(startDir)
return null if dirName is startDir # reached top
return walkUpwards(dirName, project, projectIndex)
return walkUpwards(path.normalize(goalPath), project, projectIndex)

getStyleObject: (el) ->
styleProperties = window.getComputedStyle(el)
Expand Down
6 changes: 4 additions & 2 deletions lib/tree-view-package.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const {Disposable, CompositeDisposable} = require('atom')

const helpers = require('./helpers')
const getIconServices = require('./get-icon-services')
const TreeView = require('./tree-view')

Expand All @@ -17,7 +17,8 @@ class TreeViewPackage {
'tree-view:duplicate': () => this.getTreeViewInstance().copySelectedEntry(),
'tree-view:remove': () => this.getTreeViewInstance().removeSelectedEntries(),
'tree-view:rename': () => this.getTreeViewInstance().moveSelectedEntry(),
'tree-view:show-current-file-in-file-manager': () => this.getTreeViewInstance().showCurrentFileInFileManager()
'tree-view:show-current-file-in-file-manager': () => this.getTreeViewInstance().showCurrentFileInFileManager(),
'tree-view:refresh-vcs-status': () => this.getTreeViewInstance().refreshVcsStatus()
}))

const treeView = this.getTreeViewInstance()
Expand All @@ -32,6 +33,7 @@ class TreeViewPackage {
this.disposables.dispose()
await this.treeViewOpenPromise // Wait for Tree View to finish opening before destroying it
if (this.treeView) this.treeView.destroy()
helpers.resetRepoCache()
this.treeView = null
}

Expand Down
12 changes: 11 additions & 1 deletion lib/tree-view.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ path = require 'path'

_ = require 'underscore-plus'
{BufferedProcess, CompositeDisposable, Emitter} = require 'atom'
{repoForPath, getStyleObject, getFullExtension} = require "./helpers"
{repoForPath, getStyleObject, getFullExtension, isFakeProjectRoot} = require "./helpers"
fs = require 'fs-plus'

AddDialog = require './add-dialog'
Expand Down Expand Up @@ -237,6 +237,7 @@ class TreeView
'tree-view:toggle-vcs-ignored-files': -> toggleConfig 'tree-view.hideVcsIgnoredFiles'
'tree-view:toggle-ignored-names': -> toggleConfig 'tree-view.hideIgnoredNames'
'tree-view:remove-project-folder': (e) => @removeProjectFolder(e)
'tree-view:refresh-folder-vcs-status': (e) => @refreshVcsStatus(e)

[0..8].forEach (index) =>
atom.commands.add @element, "tree-view:open-selected-entry-in-pane-#{index + 1}", =>
Expand All @@ -258,6 +259,15 @@ class TreeView
@disposables.add atom.config.onDidChange 'tree-view.squashDirectoryNames', =>
@updateRoots()

refreshVcsStatus: (e) ->
unless e
refreshFrom = @list.querySelectorAll('.project-root')
else
refreshFrom = [@selectedEntry()]
for refreshPoint in refreshFrom
if refreshPoint? and refreshPoint.refreshRepoStatus
refreshPoint.refreshRepoStatus(true, includeCollapsed = true)

toggle: ->
atom.workspace.toggle(this)

Expand Down
3 changes: 3 additions & 0 deletions menus/tree-view.cson
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
{'label': 'Add Project Folder', 'command': 'application:add-project-folder'}
{'type': 'separator'}

{'label': 'Refresh VCS Status', 'command': 'tree-view:refresh-folder-vcs-status'}
{'type': 'separator'}

{'label': 'Copy Full Path', 'command': 'tree-view:copy-full-path'}
{'label': 'Copy Project Path', 'command': 'tree-view:copy-project-path'}
{'label': 'Open in New Window', 'command': 'tree-view:open-in-new-window'}
Expand Down
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@
"type": "boolean",
"default": false,
"description": "When opening a file, always focus an already-existing view of the file even if it's in a another pane."
},
"refreshVcsStatusOnFocusChange": {
"title": "Refresh VCS Status On Focus Change for first N repos it met",
"type": "integer",
"default": 1,
"description": "Refresh VCS Status when focus of Atom editor changes of first N repos. In case of many nested repos Atom can be freezing, so consider this value to be low."
},
"refreshVcsStatusOnProjectOpen": {
"title": "Refresh VCS Status On Project Open for first N repos it met",
"type": "integer",
"default": 10,
"description": "Refresh VCS Status once Atom project is opened of first N repos. Can decrease start-up time if amount of repositories and the option number are high."
}
}
}
Loading

0 comments on commit 88e5291

Please sign in to comment.