Skip to content

Commit

Permalink
added project-symbols (adapted from symbols-view package) via tsserve…
Browse files Browse the repository at this point in the history
…r command "navto"
  • Loading branch information
russaa committed Feb 7, 2018
1 parent 2cdc72e commit 328c311
Show file tree
Hide file tree
Showing 14 changed files with 295 additions and 29 deletions.
7 changes: 3 additions & 4 deletions keymaps/atom-typescript.cson
Expand Up @@ -12,9 +12,8 @@
# Bindings for file symbols
'.platform-darwin atom-text-editor:not([mini])[data-grammar^="source ts"]':
'cmd-r': 'typescript:toggle-file-symbols'
'cmd-shift-r': 'symbols-view:toggle-project-symbols'

'.platform-win32 atom-text-editor:not([mini])[data-grammar^="source ts"]':
'ctrl-r': 'typescript:toggle-file-symbols'

'.platform-linux atom-text-editor:not([mini])[data-grammar^="source ts"]':
'.platform-win32 atom-text-editor:not([mini])[data-grammar^="source ts"], .platform-linux atom-text-editor:not([mini])[data-grammar^="source ts"]':
'ctrl-r': 'typescript:toggle-file-symbols'
'ctrl-shift-r': 'symbols-view:toggle-project-symbols'
8 changes: 8 additions & 0 deletions lib/client/client.ts
Expand Up @@ -27,6 +27,7 @@ export const commandWithResponse = new Set([
"reload",
"rename",
"navtree",
"navto",
])

export class TypescriptServiceClient {
Expand Down Expand Up @@ -115,6 +116,9 @@ export class TypescriptServiceClient {
executeNavTree(args: protocol.FileRequestArgs): Promise<protocol.NavTreeResponse> {
return this.execute("navtree", args)
}
executeNavto(args: protocol.NavtoRequestArgs): Promise<protocol.NavtoResponse> {
return this.execute("navto", args)
}

private async execute(command: "change", args: protocol.ChangeRequestArgs): Promise<undefined>
private async execute(command: "close", args: protocol.FileRequestArgs): Promise<undefined>
Expand Down Expand Up @@ -189,6 +193,10 @@ export class TypescriptServiceClient {
command: "navtree",
args: protocol.FileRequestArgs,
): Promise<protocol.NavTreeResponse>
private async execute(
command: "navto",
args: protocol.NavtoRequestArgs,
): Promise<protocol.NavtoResponse>
private async execute(command: string, args: any) {
if (!this.serverPromise) {
throw new Error("Server is not running")
Expand Down
5 changes: 2 additions & 3 deletions lib/main/atom/commands/fileSymbolsView.ts
@@ -1,13 +1,12 @@
import {commands} from "./registry"
import {commandForTypeScript} from "../utils"
import {toggle} from "../views/symbols/symbolsViewMain"
import {toggleFileSymbols} from "../views/symbols/symbolsViewMain"

commands.set("typescript:toggle-file-symbols", () => {
return async e => {
if (!commandForTypeScript(e)) {
return
}
console.log("typescript:toggle-file-symbols")
toggle()
toggleFileSymbols()
}
})
2 changes: 2 additions & 0 deletions lib/main/atom/commands/index.ts
Expand Up @@ -11,6 +11,8 @@ import "./renameRefactor"
import "./showTooltip"
import "./initializeConfig"
import "./semanticView"
import "./fileSymbolsView"
import "./projectSymbolsView"

export function registerCommands(deps: Dependencies) {
for (const [name, command] of commands) {
Expand Down
12 changes: 12 additions & 0 deletions lib/main/atom/commands/projectSymbolsView.ts
@@ -0,0 +1,12 @@
import {commands} from "./registry"
import {commandForTypeScript} from "../utils"
import {toggleProjectSymbols} from "../views/symbols/symbolsViewMain"

commands.set("typescript:toggle-project-symbols", () => {
return async e => {
if (!commandForTypeScript(e)) {
return
}
toggleProjectSymbols()
}
})
2 changes: 2 additions & 0 deletions lib/main/atom/utils/fs.ts
Expand Up @@ -33,3 +33,5 @@ export function isFileSync(filePath: string): boolean {
}

export const readFileSync = fs.readFileSync

export const parsePath = path.parse
45 changes: 37 additions & 8 deletions lib/main/atom/views/symbols/fileSymbolsTag.ts
@@ -1,4 +1,5 @@
import {NavigationTree} from "typescript/lib/protocol"
import {NavigationTree, NavtoItem} from "typescript/lib/protocol"
import {parsePath} from "../../utils/fs"

/**
* this is a modified extraction (of Tag class) from symbols-view/lib/file-view.js
Expand All @@ -10,14 +11,18 @@ export class Tag {
position: {row: number; column: number}
name: string
type: string
parent: any
constructor(navTree: NavigationTree, parent?: Tag | null) {
this.name = navTree.text
this.type = this.getType(navTree.kind)
parent: Tag | null
directory?: string
file?: string

const start = navTree.spans[0].start
this.position = {row: start.line - 1, column: start.offset}
this.parent = parent ? parent : null
constructor(navItem: NavigationTree | NavtoItem, parent?: Tag | null) {
if ((navItem as NavigationTree).text) {
this.fromNavTree(navItem as NavigationTree, parent)
} else if ((navItem as NavtoItem).name) {
this.fromNavto(navItem as NavtoItem, parent)
} else {
console.error("Cannot convert to Tag: unkown data ", navItem)
}
}

getType(kind: string): string {
Expand Down Expand Up @@ -53,4 +58,28 @@ export class Tag {
// }
return kind
}

private fromNavTree(navTree: NavigationTree, parent?: Tag | null) {
this.name = navTree.text
this.type = this.getType(navTree.kind)

const start = navTree.spans[0].start
this.position = {row: start.line - 1, column: start.offset}
this.parent = parent ? parent : null
}

private fromNavto(navTo: NavtoItem, parent?: Tag | null) {
this.name = navTo.name
this.type = this.getType(navTo.kind)

const start = navTo.start
this.position = {row: start.line - 1, column: start.offset}
this.parent = parent ? parent : null

const path = parsePath(navTo.file)
if (path && path.base) {
this.file = path.base
this.directory = path.dir
}
}
}
2 changes: 2 additions & 0 deletions lib/main/atom/views/symbols/fileSymbolsView.ts
Expand Up @@ -156,6 +156,8 @@ export class FileView extends SymbolsView {
return this.cachedTags[filePath]
}

/////////////// custom tag generation: use tsserver /////////////////////

async generate(filePath: string) {
const navtree = await this.getNavTree(filePath)
const tags: Tag[] = []
Expand Down
172 changes: 172 additions & 0 deletions lib/main/atom/views/symbols/projectSymbolsView.ts
@@ -0,0 +1,172 @@
/** @babel */

import {Emitter} from "atom"
import * as humanize from "humanize-plus"
import SymbolsView from "./symbolsView"
import {clientResolver} from "../../../atomts"
import {NavtoItem} from "typescript/lib/protocol"
import {Tag} from "./fileSymbolsTag"

/**
* this is a modified copy of symbols-view/lib/project-view.js
* for support of searching project-symbols in typescript files,
* utilizing the typescript service instead of ctag.
*/

export default class ProjectView extends SymbolsView {
tags: Tag[]
updatedTags: Emitter<{tags: Tag[]}> = new Emitter<{tags: Tag[]}>()
loadTagsTask: Promise<Tag[]>
search: string | undefined

constructor(stack: any) {
super(stack, "Project has no tags file or it is empty", 10)
}

destroy() {
this.stopTask()
this.updatedTags.dispose()
return super.destroy()
}

toggle() {
if (this.panel.isVisible()) {
this.cancel()
} else {
this.populate()
this.attach()
}
}

async populate() {
if (this.tags) {
await this.selectListView.update({items: this.tags})
}

await this.selectListView.update({
loadingMessage: "Loading project symbols\u2026",
loadingBadge: 0,
})

let tagsRead = 0
this.updatedTags.clear()
this.updatedTags.on("tags", tags => {
if (tags && tags.length > 0) {
tagsRead += tags.length
this.selectListView.update({loadingBadge: humanize.intComma(tagsRead)})
} else {
this.tags = []
const message = this.getEmptyResultMessage()
this.selectListView.update({
loadingMessage: message,
loadingBadge: null,
items: this.tags,
})
}
})

this.updatedTags.emit("tags", this.tags)
}

stopTask() {
if (this.loadTagsTask) {
// TODO cancel request -- would need Oberservable or similar instead of Promise
// this.loadTagsTask.terminate();
}
}

startTask(searchValue: string) {
this.stopTask()

// NOTE need file path when querying tsserver's "navto"
const filePath = this.getPath()
if (filePath) {
this.loadTagsTask = this.generate(filePath, searchValue).then(tags => {
this.tags = tags
const message: string | null = tags.length > 1 ? null : this.getEmptyResultMessage()
this.selectListView.update({
loadingMessage: message,
loadingBadge: null,
items: this.tags,
})
return tags
})
}
}

didChangeQuery(query: string) {
this.search = query
if (query) {
this.startTask(query)
} else {
this.updatedTags.emit("tags", [])
}
}

private getEmptyResultMessage() {
return this.search ? "No symbols found" : "Please enter search value"
}

//////////////// copied from fileSymbolsView /////////////////////////////

getEditor() {
return atom.workspace.getActiveTextEditor()
}

getPath() {
const editor = this.getEditor()
if (editor) {
return editor.getPath()
}
return undefined
}

/////////////// custom tag generation: use tsserver /////////////////////

async generate(filePath: string, searchValue: string) {
const navto = await this.getNavTo(filePath, searchValue)
const tags: Tag[] = []
if (navto && navto.length > 0) {
this.parseNavTo(navto, tags)
}
return tags
}

private parseNavTo(navTree: NavtoItem | NavtoItem[], list: Tag[], parent?: Tag | null) {
let tag: Tag | null
let children: NavtoItem[] | null
if (!Array.isArray(navTree)) {
tag = new Tag(navTree, parent)
list.push(tag)
children = null
} else {
tag = null
children = navTree
}

if (children) {
for (let i = 0, size = children.length; i < size; ++i) {
this.parseNavTo(children[i], list, tag)
}
}
}

private async getNavTo(filePath: string, query: string): Promise<NavtoItem[] | null> {
try {
const client = await clientResolver.get(filePath)
await client.executeOpen({file: filePath})
const navtoResult = await client.executeNavto({
file: filePath,
currentFileOnly: false,
searchValue: query,
})
const navTo = navtoResult ? (navtoResult.body as NavtoItem[]) : void 0
if (navTo) {
return navTo
}
} catch (err) {
console.error(err, filePath)
}
return null
}
}
7 changes: 5 additions & 2 deletions lib/main/atom/views/symbols/symbolsView.ts
Expand Up @@ -71,6 +71,7 @@ export default class SymbolsView {
didConfirmSelection: this.didConfirmSelection.bind(this),
didConfirmEmptySelection: this.didConfirmEmptySelection.bind(this),
didCancelSelection: this.didCancelSelection.bind(this),
didChangeQuery: this.didChangeQuery.bind(this),
})
this.element = this.selectListView.element
this.element.classList.add("symbols-view")
Expand Down Expand Up @@ -148,9 +149,11 @@ export default class SymbolsView {
}
}

/* tslint:disable */
didChangeSelection(_tag: any) {
/* tslint:enable */
// no-op
}

didChangeQuery(_query: string) {
// no-op
}

Expand Down

0 comments on commit 328c311

Please sign in to comment.