Visualization Registry integration (https://github.com/enso-org/ide/pull/433)

Original commit: 47468f311c
This commit is contained in:
Danilo Guanabara 2020-05-27 09:29:09 -03:00 committed by GitHub
parent 71bc491b28
commit 1ef0241bec
17 changed files with 590 additions and 138 deletions

View File

@ -150,6 +150,6 @@ scripts which maximally automate the process:
[official source](https://chromedriver.chromium.org/downloads) and ensure it is in your `PATH`.
- **Linting**
Please be sure to fix all errors reported by `node ./run line` before creating a pull request to
Please be sure to fix all errors reported by `node ./run lint` before creating a pull request to
this repository.

View File

@ -390,9 +390,9 @@
}
},
"@lerna/conventional-commits": {
"version": "3.18.5",
"resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.18.5.tgz",
"integrity": "sha512-qcvXIEJ3qSgalxXnQ7Yxp5H9Ta5TVyai6vEor6AAEHc20WiO7UIdbLDCxBtiiHMdGdpH85dTYlsoYUwsCJu3HQ==",
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.22.0.tgz",
"integrity": "sha512-z4ZZk1e8Mhz7+IS8NxHr64wyklHctCJyWpJKEZZPJiLFJ8yKto/x38O80R10pIzC0rr8Sy/OsjSH4bl0TbbgqA==",
"dev": true,
"requires": {
"@lerna/validation-error": "3.13.0",
@ -409,9 +409,9 @@
}
},
"@lerna/create": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.21.0.tgz",
"integrity": "sha512-cRIopzKzE2vXJPmsiwCDMWo4Ct+KTmX3nvvkQLDoQNrrRK7w+3KQT3iiorbj1koD95RsVQA7mS2haWok9SIv0g==",
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.22.0.tgz",
"integrity": "sha512-MdiQQzCcB4E9fBF1TyMOaAEz9lUjIHp1Ju9H7f3lXze5JK6Fl5NYkouAvsLgY6YSIhXMY8AHW2zzXeBDY4yWkw==",
"dev": true,
"requires": {
"@evocateur/pacote": "^9.6.3",
@ -527,13 +527,13 @@
}
},
"@lerna/github-client": {
"version": "3.16.5",
"resolved": "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.16.5.tgz",
"integrity": "sha512-rHQdn8Dv/CJrO3VouOP66zAcJzrHsm+wFuZ4uGAai2At2NkgKH+tpNhQy2H1PSC0Ezj9LxvdaHYrUzULqVK5Hw==",
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.22.0.tgz",
"integrity": "sha512-O/GwPW+Gzr3Eb5bk+nTzTJ3uv+jh5jGho9BOqKlajXaOkMYGBELEAqV5+uARNGWZFvYAiF4PgqHb6aCUu7XdXg==",
"dev": true,
"requires": {
"@lerna/child-process": "3.16.5",
"@octokit/plugin-enterprise-rest": "^3.6.1",
"@octokit/plugin-enterprise-rest": "^6.0.1",
"@octokit/rest": "^16.28.4",
"git-url-parse": "^11.1.2",
"npmlog": "^4.1.2"
@ -567,9 +567,9 @@
}
},
"@lerna/import": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.21.0.tgz",
"integrity": "sha512-aISkL4XD0Dqf5asDaOZWu65jgj8fWUhuQseZWuQe3UfHxav69fTS2YLIngUfencaOSZVOcVCom28YCzp61YDxw==",
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.22.0.tgz",
"integrity": "sha512-uWOlexasM5XR6tXi4YehODtH9Y3OZrFht3mGUFFT3OIl2s+V85xIGFfqFGMTipMPAGb2oF1UBLL48kR43hRsOg==",
"dev": true,
"requires": {
"@lerna/child-process": "3.16.5",
@ -831,9 +831,9 @@
}
},
"@lerna/publish": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.21.0.tgz",
"integrity": "sha512-JZ+ehZB9UCQ9nqH8Ld/Yqc/If++aK/7XIubkrB9sQ5hf2GeIbmI/BrJpMgLW/e9T5bKrUBZPUvoUN3daVipA5A==",
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.22.0.tgz",
"integrity": "sha512-8LBeTLBN8NIrCrLGykRu+PKrfrCC16sGCVY0/bzq9TDioR7g6+cY0ZAw653Qt/0Kr7rg3J7XxVNdzj3fvevlwA==",
"dev": true,
"requires": {
"@evocateur/libnpmaccess": "^3.1.2",
@ -857,7 +857,7 @@
"@lerna/run-lifecycle": "3.16.2",
"@lerna/run-topologically": "3.18.5",
"@lerna/validation-error": "3.13.0",
"@lerna/version": "3.21.0",
"@lerna/version": "3.22.0",
"figgy-pudding": "^3.5.1",
"fs-extra": "^8.1.0",
"npm-package-arg": "^6.1.0",
@ -993,17 +993,17 @@
}
},
"@lerna/version": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.21.0.tgz",
"integrity": "sha512-nIT3u43fCNj6uSMN1dRxFnF4GhmIiOEqSTkGSjrMU+8kHKwzOqS/6X6TOzklBmCyEZOpF/fLlGqH3BZHnwLDzQ==",
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.22.0.tgz",
"integrity": "sha512-6uhL6RL7/FeW6u1INEgyKjd5dwO8+IsbLfkfC682QuoVLS7VG6OOB+JmTpCvnuyYWI6fqGh1bRk9ww8kPsj+EA==",
"dev": true,
"requires": {
"@lerna/check-working-tree": "3.16.5",
"@lerna/child-process": "3.16.5",
"@lerna/collect-updates": "3.20.0",
"@lerna/command": "3.21.0",
"@lerna/conventional-commits": "3.18.5",
"@lerna/github-client": "3.16.5",
"@lerna/conventional-commits": "3.22.0",
"@lerna/github-client": "3.22.0",
"@lerna/gitlab-client": "3.15.0",
"@lerna/output": "3.13.0",
"@lerna/prerelease-id-from-version": "3.16.0",
@ -1062,25 +1062,16 @@
}
},
"@octokit/endpoint": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.1.tgz",
"integrity": "sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.2.tgz",
"integrity": "sha512-xs1mmCEZ2y4shXCpFjNq3UbmNR+bLzxtZim2L0zfEtj9R6O6kc4qLDvYw66hvO6lUsYzPTM5hMkltbuNAbRAcQ==",
"dev": true,
"requires": {
"@octokit/types": "^2.11.1",
"@octokit/types": "^4.0.1",
"is-plain-object": "^3.0.0",
"universal-user-agent": "^5.0.0"
},
"dependencies": {
"@octokit/types": {
"version": "2.16.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz",
"integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==",
"dev": true,
"requires": {
"@types/node": ">= 8"
}
},
"is-plain-object": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz",
@ -1108,9 +1099,9 @@
}
},
"@octokit/plugin-enterprise-rest": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-3.6.2.tgz",
"integrity": "sha512-3wF5eueS5OHQYuAEudkpN+xVeUsg8vYEMMenEzLphUZ7PRZ8OJtDcsreL3ad9zxXmBbaFWzLmFcdob5CLyZftA==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz",
"integrity": "sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==",
"dev": true
},
"@octokit/plugin-paginate-rest": {
@ -1161,14 +1152,14 @@
}
},
"@octokit/request": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.2.tgz",
"integrity": "sha512-zKdnGuQ2TQ2vFk9VU8awFT4+EYf92Z/v3OlzRaSh4RIP0H6cvW1BFPXq4XYvNez+TPQjqN+0uSkCYnMFFhcFrw==",
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.4.tgz",
"integrity": "sha512-vqv1lz41c6VTxUvF9nM+a6U+vvP3vGk7drDpr0DVQg4zyqlOiKVrY17DLD6de5okj+YLHKcoqaUZTBtlNZ1BtQ==",
"dev": true,
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.0.0",
"@octokit/types": "^2.11.1",
"@octokit/types": "^4.0.1",
"deprecation": "^2.0.0",
"is-plain-object": "^3.0.0",
"node-fetch": "^2.3.0",
@ -1185,26 +1176,6 @@
"@octokit/types": "^4.0.1",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
"dependencies": {
"@octokit/types": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.0.1.tgz",
"integrity": "sha512-Ho6h7w2h9y8RRE8r656hIj1oiSbwbIHJGF5r9G5FOwS2VdDPq8QLGvsG4x6pKHpvyGK7j+43sAc2cJKMiFoIJw==",
"dev": true,
"requires": {
"@types/node": ">= 8"
}
}
}
},
"@octokit/types": {
"version": "2.16.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz",
"integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==",
"dev": true,
"requires": {
"@types/node": ">= 8"
}
},
"is-plain-object": {
@ -1280,9 +1251,9 @@
}
},
"@octokit/types": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.0.1.tgz",
"integrity": "sha512-Ho6h7w2h9y8RRE8r656hIj1oiSbwbIHJGF5r9G5FOwS2VdDPq8QLGvsG4x6pKHpvyGK7j+43sAc2cJKMiFoIJw==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.0.2.tgz",
"integrity": "sha512-+4X6qfhT/fk/5FD66395NrFLxCzD6FsGlpPwfwvnukdyfYbhiZB/FJltiT1XM5Q63rGGBSf9FPaNV3WpNHm54A==",
"dev": true,
"requires": {
"@types/node": ">= 8"
@ -2037,9 +2008,9 @@
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz",
"integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
},
"balanced-match": {
"version": "1.0.0",
@ -4371,9 +4342,9 @@
},
"dependencies": {
"@types/node": {
"version": "12.12.41",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.41.tgz",
"integrity": "sha512-Q+eSkdYQJ2XK1AJnr4Ji8Gvk3sRDybEwfTvtL9CA25FFUSD2EgZQewN6VCyWYZCXg5MWZdwogdTNBhlWRcWS1w=="
"version": "12.12.42",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.42.tgz",
"integrity": "sha512-R/9QdYFLL9dE9l5cWWzWIZByVGFd7lk7JVOJ7KD+E1SJ4gni7XJRLz9QTjyYQiHIqEAgku9VgxdLjMlhhUaAFg=="
}
}
},
@ -7529,9 +7500,9 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"jake": {
"version": "10.6.1",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.6.1.tgz",
"integrity": "sha512-pHUK3+V0BjOb1XSi95rbBksrMdIqLVC9bJqDnshVyleYsET3H0XAq+3VB2E3notcYvv4wRdRHn13p7vobG+wfQ==",
"version": "10.7.1",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.7.1.tgz",
"integrity": "sha512-FUkLZXms1LSTQop5EJBdXVzbM0q6yYWMM4vo/TiLQeHJ4UMJVO8DBTZFiAgMBJctin9q92xnr2vdH7Wrpn7tTQ==",
"requires": {
"async": "0.9.x",
"chalk": "^2.4.2",
@ -7611,9 +7582,9 @@
"dev": true
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@ -7739,9 +7710,9 @@
}
},
"lerna": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/lerna/-/lerna-3.21.0.tgz",
"integrity": "sha512-ux8yOwQEgIXOZVUfq+T8nVzPymL19vlIoPbysOP3YA4hcjKlqQIlsjI/1ugBe6b4MF7W4iV5vS3gH9cGqBBc1A==",
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/lerna/-/lerna-3.22.0.tgz",
"integrity": "sha512-xWlHdAStcqK/IjKvjsSMHPZjPkBV1lS60PmsIeObU8rLljTepc4Sg/hncw4HWfQxPIewHAUTqhrxPIsqf9L2Eg==",
"dev": true,
"requires": {
"@lerna/add": "3.21.0",
@ -7749,17 +7720,17 @@
"@lerna/changed": "3.21.0",
"@lerna/clean": "3.21.0",
"@lerna/cli": "3.18.5",
"@lerna/create": "3.21.0",
"@lerna/create": "3.22.0",
"@lerna/diff": "3.21.0",
"@lerna/exec": "3.21.0",
"@lerna/import": "3.21.0",
"@lerna/import": "3.22.0",
"@lerna/info": "3.21.0",
"@lerna/init": "3.21.0",
"@lerna/link": "3.21.0",
"@lerna/list": "3.21.0",
"@lerna/publish": "3.21.0",
"@lerna/publish": "3.22.0",
"@lerna/run": "3.21.0",
"@lerna/version": "3.21.0",
"@lerna/version": "3.22.0",
"import-local": "^2.0.0",
"npmlog": "^4.1.2"
}
@ -8627,9 +8598,9 @@
"dev": true
},
"node-gyp": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz",
"integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz",
"integrity": "sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw==",
"dev": true,
"requires": {
"env-paths": "^2.2.0",
@ -9485,14 +9456,14 @@
"dev": true
},
"prebuild-install": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
"integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==",
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.4.tgz",
"integrity": "sha512-AkKN+pf4fSEihjapLEEj8n85YIw/tN6BQqkhzbDc0RvEZGdkpJBGMUYx66AAMcPG2KzmPQS7Cm16an4HVBRRMA==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.0",
"minimist": "^1.2.3",
"mkdirp": "^0.5.1",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
@ -10916,9 +10887,9 @@
"dev": true
},
"spdx-correct": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
"integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
"integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"

View File

@ -29,6 +29,18 @@ pub struct Path {
}
impl From<&FileSystemObject> for Path {
fn from(file_system_object:&FileSystemObject) -> Path {
match file_system_object {
FileSystemObject::Directory{name,path} => path.append_im(name),
FileSystemObject::File{name,path} => path.append_im(name),
FileSystemObject::DirectoryTruncated{name,path} => path.append_im(name),
FileSystemObject::Other{name,path} => path.append_im(name),
FileSystemObject::SymlinkLoop{name,path,..} => path.append_im(name)
}
}
}
impl Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "//{}/", self.root_id)?;
@ -37,6 +49,25 @@ impl Display for Path {
}
impl Path {
/// Splits path into name and path to parent directory. e.g.:
/// Path{root_id,segments:["foo","bar","qux"]} => ("qux",Path{root_id,segments:["foo","bar"]})
pub fn split(mut self) -> Option<(Path,String)> {
self.segments.pop().map(|name| (self,name))
}
/// Creates a new clone appending a new `segment`.
pub fn append_im(&self, segment:impl Str) -> Self {
let mut clone = self.clone();
clone.segments.push(segment.into());
clone
}
/// Returns the parent `Path` if the current `Path` is not `root`.
pub fn parent(&self) -> Option<Self> {
let mut parent = self.clone();
parent.segments.pop().map(|_| parent)
}
/// Returns the file name, i.e. the last segment if exists.
pub fn file_name(&self) -> Option<&String> {
self.segments.last()
@ -173,6 +204,34 @@ pub enum FileSystemObject {
}
}
impl FileSystemObject {
/// Creates a new Directory variant.
pub fn new_directory(path:Path) -> Option<Self> {
path.split().map(|(path,name)| Self::Directory{name,path})
}
/// Creates a new DirectoryTruncated variant.
pub fn new_directory_truncated(path:Path) -> Option<Self> {
path.split().map(|(path,name)| Self::DirectoryTruncated{name,path})
}
/// Creates a new File variant.
pub fn new_file(path:Path) -> Option<Self> {
path.split().map(|(path,name)| Self::File{name,path})
}
/// Creates a new Other variant.
pub fn new_other(path:Path) -> Option<Self> {
path.split().map(|(path,name)| Self::Other{name,path})
}
/// Creates a new SymlinkLoop variant.
pub fn new_symlink_loop(path:Path,target:Path) -> Option<Self> {
path.split().map(|(path,name)| Self::SymlinkLoop{name,path,target})
}
}
// ================

View File

@ -0,0 +1,4 @@
//! This module provides constants used throughout the crate.
/// Visualization folder where IDE can look for user-defined visualizations per project.
pub const PROJECT_VISUALIZATION_FOLDER:&str = "visualization";

View File

@ -19,12 +19,14 @@ pub mod graph;
pub mod module;
pub mod project;
pub mod text;
pub mod visualization;
pub use graph::Handle as Graph;
pub use graph::executed::Handle as ExecutedGraph;
pub use module::Handle as Module;
pub use project::Handle as Project;
pub use text::Handle as Text;
pub use visualization::Handle as Visualization;

View File

@ -6,6 +6,7 @@
use crate::prelude::*;
use crate::controller::FilePath;
use crate::controller::Visualization;
use enso_protocol::language_server;
use enso_protocol::binary;
@ -24,6 +25,7 @@ type ModulePath = controller::module::Path;
#[derive(Debug)]
pub struct Handle {
pub language_server_rpc : Rc<language_server::Connection>,
pub visualization : Visualization,
pub language_server_bin : Rc<binary::Connection>,
pub module_registry : Rc<model::registry::Registry<ModulePath,model::synchronized::Module>>,
pub parser : Parser,
@ -37,13 +39,15 @@ impl Handle {
, language_server_client : language_server::Connection
, language_server_binary : binary::Connection
) -> Self {
Handle {
module_registry : default(),
parser : Parser::new_or_panic(),
language_server_rpc : Rc::new(language_server_client),
language_server_bin : Rc::new(language_server_binary),
logger : parent.sub("Project Controller"),
}
let module_registry = default();
let parser = Parser::new_or_panic();
let language_server_rpc = Rc::new(language_server_client);
let language_server_bin = Rc::new(language_server_binary);
let logger = parent.sub("Project Controller");
let embedded_visualizations = default();
let language_server = language_server_rpc.clone();
let visualization = Visualization::new(language_server,embedded_visualizations);
Handle {module_registry,parser,language_server_rpc,language_server_bin,logger,visualization}
}
/// Returns a text controller for a given file path.

View File

@ -0,0 +1,250 @@
//! Visualization controller.
//!
//! Ths Visualization Controller is Responsible identifying all the available visualizations
//! natively embedded in IDE and available within the project's `visualization` folder.
use crate::prelude::*;
use crate::config::PROJECT_VISUALIZATION_FOLDER;
use enso_protocol::language_server;
use graph_editor::component::visualization::class;
use graph_editor::component::visualization::JsSourceClass;
use std::rc::Rc;
// =============
// === Error ===
// =============
/// Enumeration of errors used in `Visualization Controller`.
#[derive(Debug,Fail)]
#[allow(missing_docs)]
pub enum VisualizationError {
#[fail(display = "Visualization \"{}\" not found.", identifier)]
NotFound {
identifier : VisualizationPath
},
#[fail(display = "JavaScript visualization \"{}\" failed to be instantiated.", identifier)]
InstantiationError {
identifier : VisualizationPath
}
}
// =========================
// === VisualizationPath ===
// =========================
/// This enum is used to provide a path to visualization either in the project folder or natively
/// embedded in IDE.
#[derive(Clone,Debug,Display,Eq,PartialEq)]
#[allow(missing_docs)]
pub enum VisualizationPath {
Embedded(String),
File(language_server::Path)
}
// ==============================
// === EmbeddedVisualizations ===
// ==============================
#[allow(missing_docs)]
pub type EmbeddedVisualizationName = String;
/// Embedded visualizations mapped from name to source code.
#[derive(Shrinkwrap,Debug,Clone,Default)]
#[shrinkwrap(mutable)]
pub struct EmbeddedVisualizations {
#[allow(missing_docs)]
pub map:HashMap<EmbeddedVisualizationName,Rc<class::Handle>>
}
// ==============
// === Handle ===
// ==============
/// Visualization Controller is responsible for listing and loading all the available
/// visualizations on the project and the native ones embedded on IDE.
#[derive(Debug,Clone,CloneRef)]
pub struct Handle {
language_server_rpc : Rc<language_server::Connection>,
embedded_visualizations : Rc<RefCell<EmbeddedVisualizations>>
}
impl Handle {
/// Creates a new visualization controller.
pub fn new
( language_server_rpc : Rc<language_server::Connection>
, embedded_visualizations : EmbeddedVisualizations) -> Self {
let embedded_visualizations = Rc::new(RefCell::new(embedded_visualizations));
Self {language_server_rpc,embedded_visualizations}
}
async fn list_project_specific_visualizations
(&self) -> FallibleResult<Vec<VisualizationPath>> {
let root_id = self.language_server_rpc.content_root();
let path = language_server::Path::new(root_id,&[PROJECT_VISUALIZATION_FOLDER]);
let folder = self.language_server_rpc.file_exists(&path).await?;
let file_list = if folder.exists {
self.language_server_rpc.file_list(&path).await?.paths
} else {
default()
};
let result = file_list.iter().filter_map(|object| {
if let language_server::FileSystemObject::File{..} = object {
Some(VisualizationPath::File(object.into()))
} else {
None
}
}).collect();
Ok(result)
}
fn list_embedded_visualizations(&self) -> Vec<VisualizationPath> {
let embedded_visualizations = self.embedded_visualizations.borrow();
let result = embedded_visualizations.keys().cloned();
let result = result.map(VisualizationPath::Embedded);
result.collect()
}
/// Get a list of all available visualizations.
pub async fn list_visualizations(&self) -> FallibleResult<Vec<VisualizationPath>> {
let mut visualizations = self.list_embedded_visualizations();
visualizations.extend_from_slice(&self.list_project_specific_visualizations().await?);
Ok(visualizations)
}
/// Load the source code of the specified visualization.
pub async fn load_visualization
(&self, visualization:&VisualizationPath) -> FallibleResult<Rc<class::Handle>> {
match visualization {
VisualizationPath::Embedded(identifier) => {
let embedded_visualizations = self.embedded_visualizations.borrow();
let result = embedded_visualizations.get(identifier);
let identifier = visualization.clone();
let error = || VisualizationError::NotFound{identifier}.into();
result.cloned().ok_or_else(error)
},
VisualizationPath::File(path) => {
let js_code = self.language_server_rpc.read_file(&path).await?.contents;
let identifier = visualization.clone();
let error = |_| VisualizationError::InstantiationError {identifier}.into();
let js_class = JsSourceClass::from_js_source_raw(&js_code).map_err(error);
js_class.map(|js_class| Rc::new(class::Handle::new(js_class)))
}
}
}
}
// =============
// === Tests ===
// =============
#[cfg(test)]
mod tests {
use super::*;
use ensogl::display::Scene;
use enso_protocol::language_server::FileSystemObject;
use enso_protocol::language_server::Path;
use graph_editor::component::visualization::{NativeConstructorClass, Signature, Visualization};
use graph_editor::component::visualization::renderer::example::native::BubbleChart;
use json_rpc::expect_call;
use wasm_bindgen_test::wasm_bindgen_test_configure;
use wasm_bindgen_test::wasm_bindgen_test;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test(async)]
async fn list_and_load() {
let mock_client = language_server::MockClient::default();
let root_id = uuid::Uuid::default();
let path = Path::new(root_id,&["visualization"]);
let path0 = Path::new(root_id,&["visualization","histogram.js"]);
let path1 = Path::new(root_id,&["visualization","graph.js"]);
let paths = vec![
FileSystemObject::new_file(path0.clone()).unwrap(),
FileSystemObject::new_file(path1.clone()).unwrap(),
];
let file_list_result = language_server::response::FileList{paths};
expect_call!(mock_client.file_list(path=path.clone()) => Ok(file_list_result));
let file_content0 = r#"
class Vis0 {
static inputTypes = ["Float"]
onDataReceived(root,data) {}
setSize(root,size) {}
}
return Vis0
"#.to_string();
let file_content1 = r#"
class Vis1 {
static inputTypes = ["Float"]
onDataReceived(root,data) {}
setSize(root,size) {}
}
return Vis1
"#.to_string();
let read_result0 = language_server::response::Read{contents:file_content0.clone()};
let read_result1 = language_server::response::Read{contents:file_content1.clone()};
let exists_result0 = language_server::response::FileExists{exists:true};
let exists_result1 = language_server::response::FileExists{exists:true};
expect_call!(mock_client.file_exists(path=path.clone()) => Ok(exists_result0));
expect_call!(mock_client.file_exists(path=path.clone()) => Ok(exists_result1));
expect_call!(mock_client.read_file(path=path0.clone()) => Ok(read_result0));
expect_call!(mock_client.read_file(path=path1.clone()) => Ok(read_result1));
let language_server = language_server::Connection::new_mock_rc(mock_client);
let mut embedded_visualizations = EmbeddedVisualizations::default();
let embedded_visualization = Rc::new(class::Handle::new(NativeConstructorClass::new(
Signature {
name : "Bubble Visualization (native)".to_string(),
input_types : vec!["[[Float,Float,Float]]".to_string().into()],
},
|scene:&Scene| Ok(Visualization::new(BubbleChart::new(scene)))
)));
embedded_visualizations.insert("PointCloud".to_string(),embedded_visualization.clone());
let vis_controller = Handle::new(language_server,embedded_visualizations);
let visualizations = vis_controller.list_visualizations().await;
let visualizations = visualizations.expect("Couldn't list visualizations.");
assert_eq!(visualizations[0], VisualizationPath::Embedded("PointCloud".to_string()));
assert_eq!(visualizations[1], VisualizationPath::File(path0));
assert_eq!(visualizations[2], VisualizationPath::File(path1));
assert_eq!(visualizations.len(),3);
let javascript_vis0 = JsSourceClass::from_js_source_raw(&file_content0);
let javascript_vis1 = JsSourceClass::from_js_source_raw(&file_content1);
let javascript_vis0 = javascript_vis0.expect("Couldn't create visualization class.");
let javascript_vis1 = javascript_vis1.expect("Couldn't create visualization class.");
let javascript_vis0 = Rc::new(class::Handle::new(javascript_vis0));
let javascript_vis1 = Rc::new(class::Handle::new(javascript_vis1));
let expected_visualizations = vec![embedded_visualization,javascript_vis0,javascript_vis1];
let zipped = visualizations.iter().zip(expected_visualizations.iter());
for (visualization,expected_visualization) in zipped {
let loaded_visualization = vis_controller.load_visualization(&visualization).await;
let loaded_visualization = loaded_visualization.expect("Couldn't load visualization's content.");
let loaded_class = loaded_visualization.class();
let loaded_class = loaded_class.as_ref();
let loaded_signature = loaded_class.expect("Couldn't get class.").signature();
let expected_class = expected_visualization.class();
let expected_class = expected_class.as_ref();
let expected_signature = expected_class.expect("Couldn't get class.").signature();
assert_eq!(loaded_signature,expected_signature);
}
}
}

View File

@ -18,6 +18,7 @@
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
pub mod config;
pub mod controller;
pub mod double_representation;
pub mod executor;

View File

@ -18,7 +18,7 @@ use nalgebra::Vector2;
use nalgebra::zero;
use std::cell::RefCell;
use std::rc::Rc;
use graph_editor::GraphEditor;
// ==================
@ -87,26 +87,30 @@ impl ViewLayoutData {
impl ViewLayout {
/// Creates a new ViewLayout with a single TextEditor.
pub fn new
( logger : &Logger
, kb_actions : &mut keyboard::Actions
, application : &Application
, text_controller : controller::Text
, graph_controller : controller::ExecutedGraph
, fonts : &mut font::Registry
) -> Self {
let logger = logger.sub("ViewLayout");
let world = &application.display;
let text_editor = TextEditor::new(&logger,world,text_controller,kb_actions,fonts);
let node_editor = NodeEditor::new(&logger,application,graph_controller.clone_ref());
let node_searcher = NodeSearcher::new(world,&logger,graph_controller.graph.clone_ref(),fonts);
pub async fn new
( logger : &Logger
, kb_actions : &mut keyboard::Actions
, application : &Application
, text_controller : controller::Text
, graph_controller : controller::ExecutedGraph
, visualization_controller : controller::Visualization
, fonts : &mut font::Registry
) -> FallibleResult<Self> {
let logger = logger.sub("ViewLayout");
let world = &application.display;
let text_editor = TextEditor::new(&logger,world,text_controller,kb_actions,fonts);
let graph = graph_controller.graph.clone_ref();
let node_searcher = NodeSearcher::new(world,&logger,graph,fonts);
let graph_controller = graph_controller.clone_ref();
let node_editor = NodeEditor::new
(&logger,application,graph_controller,visualization_controller).await?;
world.add_child(&text_editor.display_object());
world.add_child(&node_editor);
world.add_child(&node_searcher);
let size = zero();
let data = ViewLayoutData {text_editor,node_editor,node_searcher,size,logger};
let rc = Rc::new(RefCell::new(data));
Self {rc}.init(world,kb_actions)
let size = zero();
let data = ViewLayoutData {text_editor,node_editor,node_searcher,size,logger};
let rc = Rc::new(RefCell::new(data));
Ok(Self {rc}.init(world,kb_actions))
}
fn init_keyboard(self, _keyboard_actions:&mut keyboard::Actions) -> Self {
@ -120,4 +124,9 @@ impl ViewLayout {
self.set_size(size);
self.init_keyboard(keyboard_actions)
}
/// Get GraphEditor.
pub fn graph_editor(&self) -> GraphEditor {
self.rc.borrow_mut().node_editor.graph.graph_editor()
}
}

View File

@ -107,6 +107,13 @@ pub struct GraphEditorIntegratedWithController {
network : frp::Network,
}
impl GraphEditorIntegratedWithController {
/// Get GraphEditor.
pub fn graph_editor(&self) -> GraphEditor {
self.model.editor.clone_ref()
}
}
#[derive(Debug)]
struct GraphEditorIntegratedWithControllerModel {
logger : Logger,
@ -438,19 +445,40 @@ impl GraphEditorIntegratedWithControllerModel {
#[derive(Clone,CloneRef,Debug)]
pub struct NodeEditor {
display_object : display::object::Instance,
graph : Rc<GraphEditorIntegratedWithController>,
controller : controller::ExecutedGraph,
#[allow(missing_docs)]
pub graph : Rc<GraphEditorIntegratedWithController>,
controller : controller::ExecutedGraph,
visualization : controller::Visualization
}
impl NodeEditor {
/// Create Node Editor Panel.
pub fn new(logger:&Logger, app:&Application, controller:controller::ExecutedGraph) -> Self {
pub async fn new
( logger : &Logger
, app : &Application
, controller : controller::ExecutedGraph
, visualization : controller::Visualization) -> FallibleResult<Self> {
let logger = logger.sub("NodeEditor");
let display_object = display::object::Instance::new(&logger);
let graph = GraphEditorIntegratedWithController::new(logger,app,controller.clone_ref());
let graph = Rc::new(graph);
display_object.add_child(&graph.model.editor);
NodeEditor {display_object,graph,controller}
Ok(NodeEditor {display_object,graph,controller,visualization}.init().await?)
}
async fn init(self) -> FallibleResult<Self> {
let graph_editor = self.graph.graph_editor();
let identifiers = self.visualization.list_visualizations().await;
let identifiers = identifiers.unwrap_or_default();
for identifier in identifiers {
let visualization = self.visualization.load_visualization(&identifier).await;
let visualization = visualization.map(|visualization| {
let class_handle = &Some(visualization);
graph_editor.frp.register_visualization_class.emit_event(class_handle);
});
visualization?;
}
Ok(self)
}
}

View File

@ -87,14 +87,15 @@ impl ProjectView {
Self::setup_theme(&application);
let _world = &application.display;
// graph::register_shapes(&world);
let logger = logger.sub("ProjectView");
let keyboard = Keyboard::default();
let keyboard_bindings = KeyboardFrpBindings::new(&logger,&keyboard);
let mut keyboard_actions = keyboard::Actions::new(&keyboard);
let resize_callback = None;
let mut fonts = font::Registry::new();
let layout = ViewLayout::new(&logger,&mut keyboard_actions,&application,
text_controller,graph_controller,&mut fonts);
let logger = logger.sub("ProjectView");
let keyboard = Keyboard::default();
let keyboard_bindings = KeyboardFrpBindings::new(&logger,&keyboard);
let mut keyboard_actions = keyboard::Actions::new(&keyboard);
let resize_callback = None;
let mut fonts = font::Registry::new();
let visualization_controller = controller.visualization.clone();
let layout = ViewLayout::new(&logger,&mut keyboard_actions,&application, text_controller,
graph_controller,visualization_controller,&mut fonts).await?;
let data = ProjectViewData {application,layout,resize_callback,controller,keyboard,
keyboard_bindings,keyboard_actions};
Ok(Self::new_from_data(data).init())

View File

@ -57,8 +57,6 @@ wasm_bindgen_test_configure!(run_in_browser);
//#[wasm_bindgen_test::wasm_bindgen_test(async)]
#[allow(dead_code)]
async fn file_operations() {
ensogl::system::web::set_stdout();
let ws = WebSocket::new_opened(default(),SERVER_ENDPOINT).await;
let ws = ws.expect("Couldn't connect to WebSocket server.");
let client = Client::new(ws);
@ -201,7 +199,6 @@ async fn file_operations() {
//#[wasm_bindgen_test::wasm_bindgen_test(async)]
#[allow(dead_code)]
async fn file_events() {
ensogl::system::web::set_stdout();
let ws = WebSocket::new_opened(default(),SERVER_ENDPOINT).await;
let ws = ws.expect("Couldn't connect to WebSocket server.");
let client = Client::new(ws);
@ -256,7 +253,6 @@ async fn file_events() {
/// * establishing a binary protocol connection with Language Server
/// * writing and reading a file using the binary protocol
async fn binary_protocol_test() {
ensogl_system_web::set_stdout();
// Setup project
let _guard = ide::setup_global_executor();
let logger = Logger::new("Test");

View File

@ -31,5 +31,6 @@ pub mod shape_system;
pub mod sprite_system;
pub mod text_field;
pub mod text_typing;
pub mod visualization;
pub use enso_prelude as prelude;

View File

@ -181,6 +181,8 @@ use graph_editor::component::node::port::Expression;
pub fn expression_mock() -> Expression {
let pattern_crumb = vec![Seq{right:false },Or,Or,Build];
let _val = ast::crumbs::SegmentMatchCrumb::Body{val:pattern_crumb};
let code = "open \"data.csv\"".into();
let output_span_tree = default();
let input_span_tree = span_tree::builder::TreeBuilder::new(15)

View File

@ -0,0 +1,124 @@
//! This is a visualization example scene which creates a sinusoidal graph.
use ensogl::application::Application;
use ensogl::display::navigation::navigator::Navigator;
use ensogl::system::web;
use ensogl_core_msdf_sys::run_once_initialized;
use graph_editor::component::visualization::Data;
use graph_editor::component::visualization::JsSourceClass;
use graph_editor::component::visualization::Registry;
use js_sys::Math::sin;
use nalgebra::Vector2;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
fn generate_data(seconds:f64) -> Vec<Vector2<f32>> {
let mut data = Vec::new();
for x in 0..100 {
let x = x as f64 / 50.0 - 1.0;
let y = sin(x * std::f64::consts::PI + seconds);
data.push(Vector2::new(x as f32,y as f32));
}
data
}
fn constructor_graph() -> JsSourceClass {
let fn_constructor = r#"
class Graph {
static inputTypes = ["[[Float,Float,Float]]"]
onDataReceived(root, data) {
if (!root.canvas) {
root.canvas = document.createElement("canvas");
root.context = root.canvas.getContext("2d");
root.appendChild(root.canvas);
}
let first = data.shift();
if (first) {
root.context.clearRect(0,0,root.canvas.width,root.canvas.height);
root.context.save();
root.context.scale(root.canvas.width/2,root.canvas.height/2);
root.context.translate(1,1);
root.context.lineWidth = 1/Math.min(root.canvas.width,root.canvas.height);
root.context.beginPath();
root.context.moveTo(first[0],first[1]);
data.forEach(data => {
root.context.lineTo(data[0],data[1]);
});
root.context.stroke();
root.context.restore();
root.context.beginPath();
root.context.moveTo(first[0],first[1]);
root.context.stroke();
}
}
setSize(root, size) {
if (root.canvas) {
root.canvas.width = size[0];
root.canvas.height = size[1];
}
}
}
return Graph;
"#;
JsSourceClass::from_js_source_raw(fn_constructor).unwrap()
}
#[wasm_bindgen]
#[allow(dead_code,missing_docs)]
pub fn run_example_visualization() {
web::forward_panic_hook_to_console();
web::set_stdout();
web::set_stack_trace_limit();
run_once_initialized(|| {
let app = Application::new(&web::get_html_element_by_id("root").unwrap());
init(&app);
std::mem::forget(app);
});
}
fn init(app:&Application) {
let world = &app.display;
let scene = world.scene();
let camera = scene.camera();
let navigator = Navigator::new(&scene,&camera);
let registry = Registry::with_default_visualizations();
registry.register_class(constructor_graph());
let vis_factories = registry.valid_sources(&"[[Float,Float,Float]]".into());
let vis_class = vis_factories.iter().find(|class| {
class.signature().name == "Graph"
}).expect("Couldn't find Graph class.");
let visualization = vis_class.instantiate(&scene).expect("Couldn't create visualiser.");
let mut was_rendered = false;
let mut loader_hidden = false;
world.on_frame(move |time_info| {
let _keep_alive = &navigator;
let data = generate_data((time_info.local / 1000.0).into());
let data = Rc::new(data);
let content = Rc::new(serde_json::to_value(data).unwrap());
let data = Data::JSON{content};
visualization.frp.set_data.emit(Some(data));
// Temporary code removing the web-loader instance.
// To be changed in the future.
if was_rendered && !loader_hidden {
web::get_element_by_id("loader").map(|t| {
t.parent_node().map(|p| {
p.remove_child(&t).unwrap()
})
}).ok();
loader_hidden = true;
}
was_rendered = true;
}).forget();
}

View File

@ -39,7 +39,7 @@ impl From<&str> for EnsoType {
}
/// Contains general information about a visualization.
#[derive(Clone,Debug)]
#[derive(Clone,Debug,PartialEq)]
#[allow(missing_docs)]
pub struct Signature {
pub name : String,

View File

@ -244,7 +244,7 @@ ensogl::def_command_api! { Commands
remove_selected_nodes,
/// Remove all nodes from the graph.
remove_all_nodes,
/// Toggle the visibility of the selected visualizations
/// Toggle the visibility of the selected visualizations.
toggle_visualization_visibility,