call-hierarchy-sample

This commit is contained in:
Jan Dolejsi
2019-11-11 00:47:36 +01:00
parent a8af0838d6
commit a1d64677ea
18 changed files with 824 additions and 28 deletions

View File

@ -184,6 +184,13 @@ const samples = [
guide: null,
apis: [`languages.registerCodeLensProvider`, `CodeLensProvider`, `CodeLens`],
contributions: []
},
{
description: 'Call Hierarchy Sample',
path: 'call-hierarchy-sample',
guide: null,
apis: [`languages.registerCallHierarchyProvider`, `CallHierarchyProvider`, `CallHierarchyItem`, `CallHierarchyOutgoingCall`, `CallHierarchyIncomingCall`],
contributions: []
}
]

4
call-hierarchy-sample/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
out
node_modules
.vscode-test/
*.vsix

View File

@ -0,0 +1,22 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm: watch"
}
]
}

View File

@ -0,0 +1,3 @@
{
"editor.insertSpaces": false
}

View File

@ -0,0 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,9 @@
.vscode/**
.vscode-test/**
out/test/**
out/**/*.map
src/**
.gitignore
tsconfig.json
vsc-extension-quickstart.md
tslint.json

View File

@ -0,0 +1,34 @@
# Call Hierarchy Provider Sample
This sample shows the **Call Hierarchy** in action based on a simple food pyramid model defined using simple subject~verb~object syntax.
![Sample](demo.gif)
## VS Code API
### `vscode` module
- [`languages.registerCallHierarchyProvider`](https://code.visualstudio.com/api/references/vscode-api#languages.registerCallHierarchyProvider)
- [`CallHierarchyProvider`](https://code.visualstudio.com/api/references/vscode-api#CallHierarchyProvider)
## Running the Sample
Start the extension in the debugger and it automatically opens a file that is ready for the right-click > Peek Call Hierarchy. Otherwise, it can be testing by creating a file with extension `.txt` and pasting following text:
```plaintext
Coyote eats deer.
Deer eats plants.
Coyote eats lizard.
Lizard eats bird.
Lizard eats frog.
Lizard eats butterfly.
Bird eats seeds.
Frog eats insects.
Butterfly eats fruit.
```
Right click on a noun or a verb and select _Peek Call Hierarchy_.
## Contributing to the Sample and Testing the Sample
Run the _Run Extension Tests_ configuration and verify in the Debug Console that all tests are passing.

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

323
call-hierarchy-sample/package-lock.json generated Normal file
View File

@ -0,0 +1,323 @@
{
"name": "call-hierarchy-sample",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/highlight": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
"integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^4.0.0"
}
},
"@types/node": {
"version": "10.14.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.17.tgz",
"integrity": "sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ==",
"dev": true
},
"@types/vscode": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.40.0.tgz",
"integrity": "sha512-5kEIxL3qVRkwhlMerxO7XuMffa+0LBl+iG2TcRa0NsdoeSFLkt/9hJ02jsi/Kvc6y8OVF2N2P2IHP5S4lWf/5w==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"resolve": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
"integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
"dev": true
},
"tslint": {
"version": "5.19.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.19.0.tgz",
"integrity": "sha512-1LwwtBxfRJZnUvoS9c0uj8XQtAnyhWr9KlNvDIdB+oXyT+VpsOAaEhEgKi1HrZ8rq0ki/AAnbGSv4KM6/AfVZw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"builtin-modules": "^1.1.1",
"chalk": "^2.3.0",
"commander": "^2.12.1",
"diff": "^3.2.0",
"glob": "^7.1.1",
"js-yaml": "^3.13.1",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.1",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.29.0"
}
},
"tsutils": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"typescript": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.2.tgz",
"integrity": "sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
}

View File

@ -0,0 +1,30 @@
{
"name": "call-hierarchy-sample",
"displayName": "call-hierarchy-sample",
"description": "Call hierarchy provider sample",
"version": "0.0.1",
"publisher": "vscode-samples",
"repository": "https://github.com/Microsoft/vscode-extension-samples/call-hierarchy-sample",
"engines": {
"vscode": "^1.40.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onLanguage:plaintext"
],
"main": "./out/extension.js",
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"lint": "tslint -p ./",
"watch": "tsc -watch -p ./"
},
"devDependencies": {
"@types/node": "^10.14.17",
"@types/vscode": "^1.40.0",
"tslint": "^5.16.0",
"typescript": "^3.5.1"
}
}

View File

@ -0,0 +1,11 @@
# Right click on a term below and select `Peek Call Hierarchy`.
Coyote eats deer.
Deer eats plants.
Coyote eats lizard.
Lizard eats bird.
Lizard eats frog.
Lizard eats butterfly.
Bird eats seeds.
Frog eats insects.
Butterfly eats fruit.

View File

@ -0,0 +1,155 @@
import {
Range, Position, CallHierarchyProvider, TextDocument, CancellationToken, CallHierarchyItem,
SymbolKind, ProviderResult, CallHierarchyIncomingCall, CallHierarchyOutgoingCall, workspace, Uri
} from 'vscode';
import { FoodPyramid, FoodRelation } from './model';
export class FoodPyramidHierarchyProvider implements CallHierarchyProvider {
prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): CallHierarchyItem | undefined {
let range = document.getWordRangeAtPosition(position);
if (range) {
let word = document.getText(range);
return this.createCallHierarchyItem(word, '', document, range);
} else {
return undefined;
}
}
async provideCallHierarchyOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyOutgoingCall[] | undefined> {
let document = await workspace.openTextDocument(item.uri);
let parser = new FoodPyramidParser();
parser.parse(document);
let model = parser.getModel();
let originRelation = model.getRelationAt(item.range);
let outgoingCallItems: CallHierarchyOutgoingCall[] = [];
if (model.isVerb(item.name)) {
let outgoingCalls = model.getVerbRelations(item.name)
.filter(relation => relation.subject === originRelation!.subject);
outgoingCalls.forEach(relation => {
let outgoingCallRange = relation.getRangeOf(relation.object);
let verbItem = this.createCallHierarchyItem(relation.object, 'noun', document, outgoingCallRange);
let outgoingCallItem = new CallHierarchyOutgoingCall(verbItem, [outgoingCallRange]);
outgoingCallItems.push(outgoingCallItem);
});
}
else if (model.isNoun(item.name)) {
let outgoingCallMap = groupBy(model.getSubjectRelations(item.name), relation => relation.verb);
outgoingCallMap.forEach((relations, verb) => {
let outgoingCallRanges = relations.map(relation => relation.getRangeOf(verb));
let verbItem = this.createCallHierarchyItem(verb, 'verb', document, outgoingCallRanges[0]);
let outgoingCallItem = new CallHierarchyOutgoingCall(verbItem, outgoingCallRanges);
outgoingCallItems.push(outgoingCallItem);
});
}
return outgoingCallItems;
}
async provideCallHierarchyIncomingCalls(item: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyIncomingCall[]> {
let document = await workspace.openTextDocument(item.uri);
let parser = new FoodPyramidParser();
parser.parse(document);
let model = parser.getModel();
let originRelation = model.getRelationAt(item.range);
let outgoingCallItems: CallHierarchyIncomingCall[] = [];
if (model.isVerb(item.name)) {
let outgoingCalls = model.getVerbRelations(item.name)
.filter(relation => relation.object === originRelation!.object);
outgoingCalls.forEach(relation => {
let outgoingCallRange = relation.getRangeOf(relation.subject);
let verbItem = this.createCallHierarchyItem(relation.subject, 'noun', document, outgoingCallRange);
let outgoingCallItem = new CallHierarchyIncomingCall(verbItem, [outgoingCallRange]);
outgoingCallItems.push(outgoingCallItem);
});
}
else if (model.isNoun(item.name)) {
let outgoingCallMap = groupBy(model.getObjectRelations(item.name), relation => relation.verb);
outgoingCallMap.forEach((relations, verb) => {
let outgoingCallRanges = relations.map(relation => relation.getRangeOf(verb));
let verbItem = this.createCallHierarchyItem(verb, 'verb-inverted', document, outgoingCallRanges[0]);
let outgoingCallItem = new CallHierarchyIncomingCall(verbItem, outgoingCallRanges);
outgoingCallItems.push(outgoingCallItem);
});
}
return outgoingCallItems;
}
private createCallHierarchyItem(word: string, type: string, document: TextDocument, range: Range): CallHierarchyItem {
return new CallHierarchyItem(SymbolKind.Object, word, `(${type})`, document.uri, range, range);
}
deriveCalledItem(item: CallHierarchyItem, called: string, document: TextDocument): CallHierarchyOutgoingCall {
const range = this.rangeOf(called, document);
let calledItem = new CallHierarchyItem(item.kind, called, called, item.uri, range, range);
return new CallHierarchyOutgoingCall(calledItem, this.allRangesOf(called, document));
}
rangeOf(word: string, document: TextDocument): Range {
let match = new RegExp("\\b" + word + "\\b").exec(document.getText());
let offset = match!.index;
return this.toRange(document, offset, word);
}
allRangesOf(word: string, document: TextDocument): Range[] {
let pattern = new RegExp("\b" + word + "\b");
let ranges: Range[] = [];
var match: RegExpExecArray | null;
while (match = pattern.exec(document.getText())) {
ranges.push(this.toRange(document, match.index, word));
}
return ranges;
}
private toRange(document: TextDocument, offset: number, word: string) {
let position = document.positionAt(offset);
return new Range(position, position.translate({ characterDelta: word.length }));
}
}
/**
* Sample parser of the document text into the [FoodPyramid](#FoodPyramid) model.
*/
class FoodPyramidParser {
private model = new FoodPyramid();
getModel(): FoodPyramid {
return this.model;
}
parse(textDocument: TextDocument): void {
let pattern = /^(\w+)\s+(\w+)\s+(\w+).$/gm;
let match: RegExpExecArray | null;
while (match = pattern.exec(textDocument.getText())) {
let startPosition = textDocument.positionAt(match.index);
let range = new Range(startPosition, startPosition.translate({ characterDelta: match[0].length }));
this.model.addRelation(new FoodRelation(match[1], match[2], match[3], match[0], range));
}
}
}
/**
* Groups array items by a field defined using a key selector.
* @param array array to be grouped
* @param keyGetter grouping key selector
*/
function groupBy<K, V>(array: Array<V>, keyGetter: (value: V) => K): Map<K, V[]> {
const map = new Map();
array.forEach((item) => {
const key = keyGetter(item);
const groupForKey = map.get(key) || [];
groupForKey.push(item);
map.set(key, groupForKey);
});
return map;
}

View File

@ -0,0 +1,29 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import * as fs from 'fs';
import { FoodPyramidHierarchyProvider } from './FoodPyramidHierarchyProvider';
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "call-hierarchy-sample" is now active!');
let disposable = vscode.languages.registerCallHierarchyProvider('plaintext', new FoodPyramidHierarchyProvider());
context.subscriptions.push(disposable);
showSampleText(context);
}
async function showSampleText(context: vscode.ExtensionContext): Promise<void> {
fs.readFile(context.asAbsolutePath('sample.txt'), async (err, sampleText) => {
let doc = await vscode.workspace.openTextDocument({ language: 'plaintext', content: sampleText.toString("utf-8") });
vscode.window.showTextDocument(doc);
});
}
// this method is called when your extension is deactivated
export function deactivate() { }

View File

@ -0,0 +1,91 @@
import {
Range, Position, CallHierarchyProvider, TextDocument, CancellationToken, CallHierarchyItem,
SymbolKind, ProviderResult, CallHierarchyIncomingCall, CallHierarchyOutgoingCall, workspace, Uri
} from 'vscode';
/**
* Sample model of what the text in the document contains.
*/
export class FoodPyramid {
private relations: FoodRelation[] = [];
private nouns = new Set<string>();
private verbs = new Set<string>();
getRelationAt(wordRange: Range): FoodRelation | undefined {
return this.relations.find(relation => relation.range.contains(wordRange));
}
addRelation(relation: FoodRelation): void {
this.relations.push(relation);
this.nouns.add(relation.object).add(relation.subject);
this.verbs.add(relation.verb);
}
isVerb(name: string): boolean {
return this.verbs.has(name.toLowerCase());
}
isNoun(name: string): boolean {
return this.nouns.has(name.toLowerCase());
}
getVerbRelations(verb: string): FoodRelation[] {
return this.relations
.filter(relation => relation.verb === verb.toLowerCase());
}
getNounRelations(noun: string): FoodRelation[] {
return this.relations
.filter(relation => relation.involves(noun));
}
getSubjectRelations(subject: string): FoodRelation[] {
return this.relations
.filter(relation => relation.subject === subject.toLowerCase());
}
getObjectRelations(object: string): FoodRelation[] {
return this.relations
.filter(relation => relation.object === object.toLowerCase());
}
}
/**
* Model element.
*/
export class FoodRelation {
private _subject: string;
private _verb: string;
private _object: string;
constructor(subject: string, verb: string, object: string,
private readonly originalText: string, public readonly range: Range) {
this._subject = subject.toLowerCase();
this._verb = verb.toLowerCase();
this._object = object.toLowerCase();
}
get subject(): string {
return this._subject;
}
get object(): string {
return this._object;
}
get verb(): string {
return this._verb;
}
involves(noun: string): boolean {
let needle = noun.toLowerCase();
return this._subject === needle || this._object === needle;
}
getRangeOf(word: string): Range {
let indexOfWord = new RegExp("\\b" + word + "\\b", "i").exec(this.originalText)!.index;
return new Range(this.range.start.translate({ characterDelta: indexOfWord }),
this.range.start.translate({ characterDelta: indexOfWord + word.length }));
}
}

View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"sourceMap": true,
"rootDir": "src",
"strict": true
},
"exclude": ["node_modules", ".vscode-test"]
}

View File

@ -0,0 +1,6 @@
{
"rules": {
"indent": [true, "tabs"],
"semicolon": [true, "always"]
}
}

View File

@ -308,9 +308,9 @@
}
},
"typescript": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz",
"integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==",
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz",
"integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==",
"dev": true
},
"wrappy": {

View File

@ -2,41 +2,82 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
'use strict';
import { ExtensionContext, StatusBarAlignment, window, StatusBarItem, Selection, workspace, TextEditor, commands, ProgressLocation } from 'vscode';
import { ExtensionContext, window, commands, ProgressLocation, CancellationToken, Progress } from 'vscode';
import { spawn, spawnSync } from 'child_process';
export function activate(context: ExtensionContext) {
context.subscriptions.push(commands.registerCommand('extension.startTask', () => {
context.subscriptions.push(commands.registerCommand('extension.askMode', () => {
return window.showQuickPick(['sync', 'async'], { placeHolder: 'Pick mode...' });
}));
context.subscriptions.push(commands.registerCommand('extension.startTask', async () => {
let mode = await commands.executeCommand('extension.askMode');
window.withProgress({
location: ProgressLocation.Notification,
title: "I am long running!",
title: "I am long running",
cancellable: true
}, (progress, token) => {
}, async (progress, token) => {
token.onCancellationRequested(() => {
console.log("User canceled the long running operation");
});
progress.report({ increment: 0 });
setTimeout(() => {
progress.report({ increment: 10, message: "I am long running! - still going..." });
}, 1000);
setTimeout(() => {
progress.report({ increment: 40, message: "I am long running! - still going even more..." });
}, 2000);
setTimeout(() => {
progress.report({ increment: 50, message: "I am long running! - almost there..." });
}, 3000);
var p = new Promise(resolve => {
setTimeout(() => {
resolve();
}, 5000);
});
return p;
switch (mode) {
case undefined:
return; // canceled by the user
case 'sync':
return spawnSomethingSync(token);
case 'async':
default:
return spawnSomethingAsync(progress, token);
}
});
}));
}
/**
* Synchronous approach
* @param _token cancellation token (unused in the sync approach)
*/
function spawnSomethingSync(_token: CancellationToken): Promise<void> {
return new Promise(resolve => {
console.log('Started...');
let child = spawnSync('cmd', ['/c', 'dir', '/S'], { cwd: 'c:\\', encoding: 'utf8' });
console.log(`stdout: ${child.stdout.slice(0, 1000)}`); // otherwise it is too big for the console
resolve();
});
}
/**
* Asynchronous approach
* @param token cancellation token (triggered by the cancel button on the UI)
*/
function spawnSomethingAsync(progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (token.isCancellationRequested) {
return;
}
var progressUpdate = 'Starting up...';
const interval = setInterval(() => progress.report({ message: progressUpdate }), 500);
let childProcess = spawn('cmd', ['/c', 'dir', '/S'], { cwd: 'C:\\Users\\jdolejsi\\AppData\\' })
.on("close", (code, signal) => {
console.log(`Closed: ${code} ${signal}`);
if (childProcess.killed) { console.log('KILLED'); }
resolve();
clearInterval(interval);
})
.on("error", err => {
reject(err);
});
childProcess.stdout
.on("data", (chunk: string | Buffer) => {
console.log(`stdout: ${chunk}`);
progressUpdate = chunk.toString('utf8', 0, 50).replace(/[\r\n]/g, '');
});
token.onCancellationRequested(_ => childProcess.kill());
});
}