mirror of
https://github.com/atom/atom.git
synced 2026-01-24 06:18:03 -05:00
Add project outline view with all tags
Opened via meta-J and limited to a maximum of 10 tags similar to fuzzy-finder.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "include/cef_base.h"
|
||||
#include "include/cef_v8.h"
|
||||
#include "readtags.h"
|
||||
|
||||
namespace v8_extensions {
|
||||
|
||||
@@ -15,6 +16,9 @@ public:
|
||||
|
||||
// Provide the reference counting implementation for this class.
|
||||
IMPLEMENT_REFCOUNTING(Tags);
|
||||
|
||||
private:
|
||||
CefRefPtr<CefV8Value> ParseEntry(tagEntry entry);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -4,4 +4,7 @@ var $tags = {};
|
||||
native function find(path, tag);
|
||||
$tags.find = find;
|
||||
|
||||
native function getAllTagsAsync(path, callback);
|
||||
$tags.getAllTagsAsync = getAllTagsAsync;
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#import "tags.h"
|
||||
#import "readtags.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
namespace v8_extensions {
|
||||
@@ -10,6 +9,16 @@ Tags::Tags() : CefV8Handler() {
|
||||
CefRegisterExtension("v8/tags", [extensionCode UTF8String], this);
|
||||
}
|
||||
|
||||
CefRefPtr<CefV8Value> Tags::ParseEntry(tagEntry entry) {
|
||||
CefRefPtr<CefV8Value> tagEntry = CefV8Value::CreateObject(NULL);
|
||||
tagEntry->SetValue("name", CefV8Value::CreateString(entry.name), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
tagEntry->SetValue("file", CefV8Value::CreateString(entry.file), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
if (entry.address.pattern) {
|
||||
tagEntry->SetValue("pattern", CefV8Value::CreateString(entry.address.pattern), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
}
|
||||
return tagEntry;
|
||||
}
|
||||
|
||||
bool Tags::Execute(const CefString& name,
|
||||
CefRefPtr<CefV8Value> object,
|
||||
const CefV8ValueList& arguments,
|
||||
@@ -26,28 +35,9 @@ bool Tags::Execute(const CefString& name,
|
||||
tagEntry entry;
|
||||
std::vector<CefRefPtr<CefV8Value>> entries;
|
||||
if (tagsFind(tagFile, &entry, tag.c_str(), TAG_FULLMATCH | TAG_OBSERVECASE) == TagSuccess) {
|
||||
CefRefPtr<CefV8Value> tagEntry = CefV8Value::CreateObject(NULL);
|
||||
tagEntry->SetValue("name", CefV8Value::CreateString(entry.name), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
tagEntry->SetValue("file", CefV8Value::CreateString(entry.file), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
if (entry.kind) {
|
||||
tagEntry->SetValue("kind", CefV8Value::CreateString(entry.kind), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
}
|
||||
if (entry.address.pattern) {
|
||||
tagEntry->SetValue("pattern", CefV8Value::CreateString(entry.address.pattern), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
}
|
||||
entries.push_back(tagEntry);
|
||||
|
||||
entries.push_back(ParseEntry(entry));
|
||||
while (tagsFindNext(tagFile, &entry) == TagSuccess) {
|
||||
tagEntry = CefV8Value::CreateObject(NULL);
|
||||
tagEntry->SetValue("name", CefV8Value::CreateString(entry.name), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
tagEntry->SetValue("file", CefV8Value::CreateString(entry.file), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
if (entry.kind) {
|
||||
tagEntry->SetValue("kind", CefV8Value::CreateString(entry.kind), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
}
|
||||
if (entry.address.pattern) {
|
||||
tagEntry->SetValue("pattern", CefV8Value::CreateString(entry.address.pattern), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
}
|
||||
entries.push_back(tagEntry);
|
||||
entries.push_back(ParseEntry(entry));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +50,52 @@ bool Tags::Execute(const CefString& name,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "getAllTagsAsync") {
|
||||
std::string path = arguments[0]->GetStringValue().ToString();
|
||||
CefRefPtr<CefV8Value> callback = arguments[1];
|
||||
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
|
||||
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
dispatch_async(queue, ^{
|
||||
tagFileInfo info;
|
||||
tagFile* tagFile;
|
||||
tagFile = tagsOpen(path.c_str(), &info);
|
||||
std::vector<tagEntry> entries;
|
||||
|
||||
if (info.status.opened) {
|
||||
tagEntry entry;
|
||||
while (tagsNext(tagFile, &entry) == TagSuccess) {
|
||||
entry.name = strdup(entry.name);
|
||||
entry.file = strdup(entry.file);
|
||||
if (entry.address.pattern) {
|
||||
entry.address.pattern = strdup(entry.address.pattern);
|
||||
}
|
||||
entries.push_back(entry);
|
||||
}
|
||||
tagsClose(tagFile);
|
||||
}
|
||||
|
||||
dispatch_queue_t mainQueue = dispatch_get_main_queue();
|
||||
dispatch_async(mainQueue, ^{
|
||||
context->Enter();
|
||||
CefRefPtr<CefV8Value> v8Tags = CefV8Value::CreateArray(entries.size());
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
v8Tags->SetValue(i, ParseEntry(entries[i]));
|
||||
free((void*)entries[i].name);
|
||||
free((void*)entries[i].file);
|
||||
if (entries[i].address.pattern) {
|
||||
free((void*)entries[i].address.pattern);
|
||||
}
|
||||
}
|
||||
CefV8ValueList callbackArgs;
|
||||
callbackArgs.push_back(v8Tags);
|
||||
callback->ExecuteFunction(callback, callbackArgs);
|
||||
context->Exit();
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ describe "OutlineView", ->
|
||||
it "initially displays all JavaScript functions with line numbers", ->
|
||||
rootView.open('sample.js')
|
||||
expect(rootView.find('.outline-view')).not.toExist()
|
||||
rootView.getActiveEditor().trigger "outline-view:toggle"
|
||||
rootView.getActiveEditor().trigger "outline-view:toggle-file-outline"
|
||||
expect(outlineView.find('.loading')).toHaveText 'Generating symbols...'
|
||||
|
||||
waitsFor ->
|
||||
@@ -31,16 +31,16 @@ describe "OutlineView", ->
|
||||
expect(rootView.find('.outline-view')).toExist()
|
||||
expect(outlineView.list.children('li').length).toBe 2
|
||||
expect(outlineView.list.children('li:first').find('.function-name')).toHaveText 'quicksort'
|
||||
expect(outlineView.list.children('li:first').find('.function-line')).toHaveText 'Line 1'
|
||||
expect(outlineView.list.children('li:first').find('.function-details')).toHaveText 'Line 1'
|
||||
expect(outlineView.list.children('li:last').find('.function-name')).toHaveText 'quicksort.sort'
|
||||
expect(outlineView.list.children('li:last').find('.function-line')).toHaveText 'Line 2'
|
||||
expect(outlineView.list.children('li:last').find('.function-details')).toHaveText 'Line 2'
|
||||
expect(outlineView).not.toHaveClass "error"
|
||||
expect(outlineView.error).not.toBeVisible()
|
||||
|
||||
it "displays error when no tags match text in mini-editor", ->
|
||||
rootView.open('sample.js')
|
||||
expect(rootView.find('.outline-view')).not.toExist()
|
||||
rootView.getActiveEditor().trigger "outline-view:toggle"
|
||||
rootView.getActiveEditor().trigger "outline-view:toggle-file-outline"
|
||||
|
||||
waitsFor ->
|
||||
setArraySpy.callCount > 0
|
||||
@@ -67,7 +67,7 @@ describe "OutlineView", ->
|
||||
it "shows an error message when no matching tags are found", ->
|
||||
rootView.open('sample.txt')
|
||||
expect(rootView.find('.outline-view')).not.toExist()
|
||||
rootView.getActiveEditor().trigger "outline-view:toggle"
|
||||
rootView.getActiveEditor().trigger "outline-view:toggle-file-outline"
|
||||
setErrorSpy = spyOn(outlineView, "setError").andCallThrough()
|
||||
|
||||
waitsFor ->
|
||||
@@ -151,3 +151,24 @@ describe "OutlineView", ->
|
||||
outlineView.confirmed(outlineView.array[0])
|
||||
expect(rootView.getActiveEditor().getPath()).toBe rootView.project.resolve("tagged-duplicate.js")
|
||||
expect(rootView.getActiveEditor().getCursorBufferPosition()).toEqual [0,4]
|
||||
|
||||
describe "project outline", ->
|
||||
it "displays all tags", ->
|
||||
rootView.open("tagged.js")
|
||||
expect(rootView.find('.outline-view')).not.toExist()
|
||||
rootView.trigger "outline-view:toggle-project-outline"
|
||||
expect(outlineView.find('.loading')).toHaveText 'Loading symbols...'
|
||||
|
||||
waitsFor ->
|
||||
setArraySpy.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(outlineView.find('.loading')).toBeEmpty()
|
||||
expect(rootView.find('.outline-view')).toExist()
|
||||
expect(outlineView.list.children('li').length).toBe 4
|
||||
expect(outlineView.list.children('li:first').find('.function-name')).toHaveText 'callMeMaybe'
|
||||
expect(outlineView.list.children('li:first').find('.function-details')).toHaveText 'tagged.js'
|
||||
expect(outlineView.list.children('li:last').find('.function-name')).toHaveText 'thisIsCrazy'
|
||||
expect(outlineView.list.children('li:last').find('.function-details')).toHaveText 'tagged.js'
|
||||
expect(outlineView).not.toHaveClass "error"
|
||||
expect(outlineView.error).not.toBeVisible()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
window.keymap.bindKeys '.editor'
|
||||
'meta-j': 'outline-view:toggle'
|
||||
'meta-j': 'outline-view:toggle-file'
|
||||
'meta-J': 'outline-view:toggle-project'
|
||||
'meta-.': 'outline-view:jump-to-declaration'
|
||||
|
||||
@@ -14,7 +14,8 @@ class OutlineView extends SelectList
|
||||
requireStylesheet 'select-list.css'
|
||||
requireStylesheet 'outline-view/src/outline-view.css'
|
||||
@instance = new OutlineView(rootView)
|
||||
rootView.command 'outline-view:toggle', => @instance.toggle()
|
||||
rootView.command 'outline-view:toggle-file-outline', => @instance.toggleFileOutline()
|
||||
rootView.command 'outline-view:toggle-project-outline', => @instance.toggleProjectOutline()
|
||||
rootView.command 'outline-view:jump-to-declaration', => @instance.jumpToDeclaration()
|
||||
|
||||
@viewClass: -> "#{super} outline-view"
|
||||
@@ -24,22 +25,26 @@ class OutlineView extends SelectList
|
||||
initialize: (@rootView) ->
|
||||
super
|
||||
|
||||
itemForElement: ({position, name}) ->
|
||||
itemForElement: ({position, name, file}) ->
|
||||
$$ ->
|
||||
@li =>
|
||||
@div name, class: 'function-name'
|
||||
@div class: 'right', =>
|
||||
@div "Line #{position.row + 1}", class: 'function-line'
|
||||
if position
|
||||
text = "Line #{position.row + 1}"
|
||||
else
|
||||
text = fs.base(file)
|
||||
@div text, class: 'function-details'
|
||||
@div class: 'clear-float'
|
||||
|
||||
toggle: ->
|
||||
toggleFileOutline: ->
|
||||
if @hasParent()
|
||||
@cancel()
|
||||
else
|
||||
@populate()
|
||||
@populateFileOutline()
|
||||
@attach()
|
||||
|
||||
populate: ->
|
||||
populateFileOutline: ->
|
||||
tags = []
|
||||
callback = (tag) -> tags.push tag
|
||||
path = @rootView.getActiveEditor().getPath()
|
||||
@@ -47,6 +52,26 @@ class OutlineView extends SelectList
|
||||
new TagGenerator(path, callback).generate().done =>
|
||||
if tags.length > 0
|
||||
@miniEditor.show()
|
||||
@maxItem = Infinity
|
||||
@setArray(tags)
|
||||
else
|
||||
@miniEditor.hide()
|
||||
@setError("No symbols found")
|
||||
setTimeout (=> @detach()), 2000
|
||||
|
||||
toggleProjectOutline: ->
|
||||
if @hasParent()
|
||||
@cancel()
|
||||
else
|
||||
@populateProjectOutline()
|
||||
@attach()
|
||||
|
||||
populateProjectOutline: ->
|
||||
@setLoading("Loading symbols...")
|
||||
TagReader.getAllTags(@rootView.getActiveEditor()).done (tags) =>
|
||||
if tags.length > 0
|
||||
@miniEditor.show()
|
||||
@maxItems = 10
|
||||
@setArray(tags)
|
||||
else
|
||||
@miniEditor.hide()
|
||||
@@ -57,9 +82,11 @@ class OutlineView extends SelectList
|
||||
@cancel()
|
||||
@openTag(tag)
|
||||
|
||||
openTag: ({position, file}) ->
|
||||
@rootView.openInExistingEditor(file, true, true) if file
|
||||
@moveToPosition(position)
|
||||
openTag: (tag) ->
|
||||
position = tag.position
|
||||
position = @getTagLine(tag) unless position
|
||||
@rootView.openInExistingEditor(tag.file, true, true) if tag.file
|
||||
@moveToPosition(position) if position
|
||||
|
||||
moveToPosition: (position) ->
|
||||
editor = @rootView.getActiveEditor()
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
float: right;
|
||||
}
|
||||
|
||||
.outline-view ol .function-line {
|
||||
.outline-view ol .function-details {
|
||||
display: inline-block;
|
||||
margin: 4px 0;
|
||||
margin-right: .5em;
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
fs = require 'fs'
|
||||
$ = require 'jquery'
|
||||
|
||||
module.exports =
|
||||
|
||||
getTagsFile: (editor) ->
|
||||
project = editor.rootView().project
|
||||
tagsFile = project.resolve("tags") or project.resolve("TAGS")
|
||||
return tagsFile if fs.isFile(tagsFile)
|
||||
|
||||
find: (editor) ->
|
||||
word = editor.getTextInRange(editor.getCursor().getCurrentWordBufferRange())
|
||||
return [] unless word.length > 0
|
||||
|
||||
project = editor.rootView().project
|
||||
tagsFile = project.resolve("tags") or project.resolve("TAGS")
|
||||
return [] unless fs.isFile(tagsFile)
|
||||
tagsFile = @getTagsFile(editor)
|
||||
return [] unless tagsFile
|
||||
|
||||
$tags.find(tagsFile, word) or []
|
||||
|
||||
getAllTags: (editor, callback) ->
|
||||
deferred = $.Deferred()
|
||||
tagsFile = @getTagsFile(editor)
|
||||
if tagsFile
|
||||
$tags.getAllTagsAsync tagsFile, (tags) =>
|
||||
deferred.resolve(tags)
|
||||
else
|
||||
deferred.resolve([])
|
||||
deferred.promise()
|
||||
|
||||
Reference in New Issue
Block a user