mirror of
https://github.com/enso-org/enso.git
synced 2024-12-28 18:31:39 +03:00
Expose IDE as a library (https://github.com/enso-org/ide/pull/1080)
Original commit: b16ac40a3d
This commit is contained in:
parent
6a7bee1776
commit
89d990c5b3
@ -72,17 +72,18 @@ function command(docs) {
|
|||||||
return {docs}
|
return {docs}
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_project_manager() {
|
// FIXME: this does not work if project manager was not downloaded yet.
|
||||||
const bin_path = paths.get_project_manager_path(paths.dist.bin)
|
//function run_project_manager() {
|
||||||
console.log(`Starting the language server from "${bin_path}".`)
|
// const bin_path = paths.get_project_manager_path(paths.dist.bin)
|
||||||
child_process.execFile(bin_path, [], (error, stdout, stderr) => {
|
// console.log(`Starting the language server from "${bin_path}".`)
|
||||||
console.error(stderr)
|
// child_process.execFile(bin_path, [], (error, stdout, stderr) => {
|
||||||
if (error) {
|
// console.error(stderr)
|
||||||
throw error
|
// if (error) {
|
||||||
}
|
// throw error
|
||||||
console.log(stdout)
|
// }
|
||||||
})
|
// console.log(stdout)
|
||||||
}
|
// })
|
||||||
|
//}
|
||||||
|
|
||||||
// ================
|
// ================
|
||||||
// === Commands ===
|
// === Commands ===
|
||||||
@ -241,7 +242,8 @@ commands.watch.rust = async function(argv) {
|
|||||||
build_args.push(`--crate=${argv.crate}`)
|
build_args.push(`--crate=${argv.crate}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
run_project_manager()
|
// FIXME: See fixme on fn definition.
|
||||||
|
// run_project_manager()
|
||||||
build_args = build_args.join(' ')
|
build_args = build_args.join(' ')
|
||||||
let target =
|
let target =
|
||||||
'"' +
|
'"' +
|
||||||
|
@ -147,9 +147,9 @@ are presented below:
|
|||||||
utility which will build the project on every change. Open
|
utility which will build the project on every change. Open
|
||||||
`http://localhost:8080` (the port may vary and will be reported in the
|
`http://localhost:8080` (the port may vary and will be reported in the
|
||||||
terminal if `8080` was already in use) to run the application, or
|
terminal if `8080` was already in use) to run the application, or
|
||||||
`http://localhost:8080/debug` to open example demo scenes. Please remember
|
`http://localhost:8080/?entry` to open example demo scenes list. Please
|
||||||
to disable the cache in your browser during the development! By default,
|
remember to disable the cache in your browser during the development! By
|
||||||
the script disables heavyweight optimizations to provide interactive
|
default, the script disables heavyweight optimizations to provide interactive
|
||||||
development experience. The scripts are thin wrappers for
|
development experience. The scripts are thin wrappers for
|
||||||
[wasm-pack](https://github.com/rustwasm/wasm-pack) and accept the same
|
[wasm-pack](https://github.com/rustwasm/wasm-pack) and accept the same
|
||||||
[command line arguments](https://rustwasm.github.io/wasm-pack/book/commands/build.html).
|
[command line arguments](https://rustwasm.github.io/wasm-pack/book/commands/build.html).
|
||||||
@ -158,8 +158,8 @@ are presented below:
|
|||||||
In order to compile in a production mode (enable all optimizations, strip
|
In order to compile in a production mode (enable all optimizations, strip
|
||||||
WASM debug symbols, minimize the output binaries, etc.), run
|
WASM debug symbols, minimize the output binaries, etc.), run
|
||||||
`node ./run build`. To create platform-specific packages and installers use
|
`node ./run build`. To create platform-specific packages and installers use
|
||||||
`node ./run dist` instead. The final packages will be located at
|
`node ./run dist` instead. The final executables will be located at
|
||||||
`app/dist/native`.
|
`dist/client/$PLATFORM`.
|
||||||
|
|
||||||
- **Selective mode**
|
- **Selective mode**
|
||||||
In order to compile only part of the project, and thus drastically shorten
|
In order to compile only part of the project, and thus drastically shorten
|
||||||
@ -173,6 +173,19 @@ are presented below:
|
|||||||
were defined or re-exported by that crate. In particular, the `ide` crate
|
were defined or re-exported by that crate. In particular, the `ide` crate
|
||||||
exposes the `entry_point_ide` function, so you have to compile it to test
|
exposes the `entry_point_ide` function, so you have to compile it to test
|
||||||
your code in the Enso IDE.
|
your code in the Enso IDE.
|
||||||
|
|
||||||
|
|
||||||
|
### Using IDE as a library.
|
||||||
|
In case you want to use the IDE as a library, for example to embed it into
|
||||||
|
another website, you need to first build it using `node ./run {built,dist}` and
|
||||||
|
find the necessary artifacts located at `dist/content`. Especially, the
|
||||||
|
`dist/content/index.js` defines a function `window.enso.main(cfg)` which you
|
||||||
|
can use to run the IDE. Currently, the configuration argument can contain the
|
||||||
|
following options:
|
||||||
|
- `entry` - the entry point, one of predefined scenes. Set it to empty string to
|
||||||
|
see the list of possible entry points.
|
||||||
|
- `project` - the project name to open after loading the IDE.
|
||||||
|
|
||||||
|
|
||||||
### Testing, Linting, and Validation
|
### Testing, Linting, and Validation
|
||||||
After changing the code it's always a good idea to lint and test the code. We
|
After changing the code it's always a good idea to lint and test the code. We
|
||||||
|
8
gui/src/config.yaml
Normal file
8
gui/src/config.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# The name of an object created in the global window scope. This object will contain functions
|
||||||
|
# to control the IDE, like the `main` function, and also the configuration object.
|
||||||
|
windowAppScopeName: "enso"
|
||||||
|
|
||||||
|
# The configuration object nested inside of the `windowAppScopeName` object, containing all
|
||||||
|
# startup configuration options. See usages of this variable to learn more about available
|
||||||
|
# options.
|
||||||
|
windowAppScopeConfigName: "config"
|
@ -10,7 +10,8 @@ let config = {
|
|||||||
},
|
},
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
"compression-webpack-plugin": "^3.1.0",
|
"compression-webpack-plugin": "^3.1.0",
|
||||||
"copy-webpack-plugin": "^5.1.1"
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
|
"yaml-loader": "^0.6.0",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,9 +69,9 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" src="/assets/index.js" defer></script>
|
<script type="module" src="/assets/index.js" defer></script>
|
||||||
|
<script type="module" src="/assets/run.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- <div class="titlebar"></div> -->
|
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<noscript>
|
<noscript>
|
||||||
This page requires JavaScript to run. Please enable it in your browser.
|
This page requires JavaScript to run. Please enable it in your browser.
|
||||||
|
@ -5,6 +5,16 @@
|
|||||||
import * as loader_module from 'enso-studio-common/src/loader'
|
import * as loader_module from 'enso-studio-common/src/loader'
|
||||||
import * as html_utils from 'enso-studio-common/src/html_utils'
|
import * as html_utils from 'enso-studio-common/src/html_utils'
|
||||||
import * as animation from 'enso-studio-common/src/animation'
|
import * as animation from 'enso-studio-common/src/animation'
|
||||||
|
import * as globalConfig from '../../../../config.yaml'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==================
|
||||||
|
// === Global API ===
|
||||||
|
// ==================
|
||||||
|
|
||||||
|
let API = {}
|
||||||
|
window[globalConfig.windowAppScopeName] = API
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -109,18 +119,13 @@ function show_debug_screen(wasm,msg) {
|
|||||||
ul.appendChild(li)
|
ul.appendChild(li)
|
||||||
a.appendChild(linkText)
|
a.appendChild(linkText)
|
||||||
a.title = name
|
a.title = name
|
||||||
a.href = "javascript:{}"
|
a.href = "?entry="+name
|
||||||
a.onclick = () => {
|
|
||||||
html_utils.remove_node(debug_screen_div)
|
|
||||||
let fn_name = wasm_entry_point_pfx + name
|
|
||||||
let fn = wasm[fn_name]
|
|
||||||
fn()
|
|
||||||
}
|
|
||||||
li.appendChild(a)
|
li.appendChild(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
// === Scam Warning ===
|
// === Scam Warning ===
|
||||||
// ====================
|
// ====================
|
||||||
@ -191,7 +196,7 @@ window.logsBuffer = logsBuffer
|
|||||||
|
|
||||||
let root = document.getElementById('root')
|
let root = document.getElementById('root')
|
||||||
|
|
||||||
function prepare_root(cfg) {
|
function style_root() {
|
||||||
root.style.backgroundColor = '#f6f3f199'
|
root.style.backgroundColor = '#f6f3f199'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,43 +223,36 @@ function disableContextMenu() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ok(value) {
|
||||||
|
return value !== null && value !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
/// Main entry point. Loads WASM, initializes it, chooses the scene to run.
|
/// Main entry point. Loads WASM, initializes it, chooses the scene to run.
|
||||||
async function main() {
|
API.main = async function (inputConfig) {
|
||||||
|
let urlParams = new URLSearchParams(window.location.search);
|
||||||
|
let urlConfig = Object.fromEntries(urlParams.entries())
|
||||||
|
let config = Object.assign({},inputConfig,urlConfig)
|
||||||
|
API[globalConfig.windowAppScopeConfigName] = config
|
||||||
|
|
||||||
|
style_root()
|
||||||
printScamWarning()
|
printScamWarning()
|
||||||
hideLogs()
|
hideLogs()
|
||||||
disableContextMenu()
|
disableContextMenu()
|
||||||
let location = window.location.pathname.split('/')
|
|
||||||
location.splice(0,1)
|
|
||||||
let cfg = getUrlParams()
|
|
||||||
prepare_root(cfg)
|
|
||||||
|
|
||||||
let debug_mode = location[0] == "debug"
|
let entryTarget = ok(config.entry) ? config.entry : main_entry_point
|
||||||
let debug_target = location[1]
|
let useLoader = entryTarget === main_entry_point
|
||||||
let no_loader = debug_mode && debug_target
|
|
||||||
|
|
||||||
await windowShowAnimation()
|
await windowShowAnimation()
|
||||||
let {wasm,loader} = await download_content({no_loader})
|
let {wasm,loader} = await download_content({no_loader:!useLoader})
|
||||||
|
|
||||||
let target = null;
|
if (entryTarget) {
|
||||||
if (debug_mode) {
|
let fn_name = wasm_entry_point_pfx + entryTarget
|
||||||
loader.destroy()
|
|
||||||
if (debug_target) {
|
|
||||||
target = debug_target
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
target = main_entry_point
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target) {
|
|
||||||
let fn_name = wasm_entry_point_pfx + target
|
|
||||||
let fn = wasm[fn_name]
|
let fn = wasm[fn_name]
|
||||||
if (fn) { fn() } else {
|
if (fn) { fn() } else {
|
||||||
loader.destroy()
|
loader.destroy()
|
||||||
show_debug_screen(wasm,"Unknown entry point '" + target + "'. ")
|
show_debug_screen(wasm,"Unknown entry point '" + entryTarget + "'. ")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
show_debug_screen(wasm)
|
show_debug_screen(wasm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
|
||||||
|
4
gui/src/js/lib/content/src/run.js
Normal file
4
gui/src/js/lib/content/src/run.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/// This file is used to simply run the IDE. It can be not invoked if the IDE needs to be used as a
|
||||||
|
/// library.
|
||||||
|
|
||||||
|
window.enso.main()
|
@ -24,6 +24,7 @@ module.exports = {
|
|||||||
new CompressionPlugin(),
|
new CompressionPlugin(),
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin([
|
||||||
path.resolve(thisPath,'src','index.html'),
|
path.resolve(thisPath,'src','index.html'),
|
||||||
|
path.resolve(thisPath,'src','run.js'),
|
||||||
path.resolve(wasmPath,'ide.wasm'),
|
path.resolve(wasmPath,'ide.wasm'),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
@ -42,5 +43,14 @@ module.exports = {
|
|||||||
hints: false,
|
hints: false,
|
||||||
},
|
},
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
stats: 'minimal'
|
stats: 'minimal',
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ya?ml$/,
|
||||||
|
type: 'json',
|
||||||
|
use: 'yaml-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
28
gui/src/js/package-lock.json
generated
28
gui/src/js/package-lock.json
generated
@ -1212,9 +1212,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@octokit/openapi-types": {
|
"@octokit/openapi-types": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.1.tgz",
|
||||||
"integrity": "sha512-J4bfM7lf8oZvEAdpS71oTvC1ofKxfEZgU5vKVwzZKi4QPiL82udjpseJwxPid9Pu2FNmyRQOX4iEj6W1iOSnPw==",
|
"integrity": "sha512-9AuC04PUnZrjoLiw3uPtwGh9FE4Q3rTqs51oNlQ0rkwgE8ftYsOC+lsrQyvCvWm85smBbSc0FNRKKumvGyb44Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@octokit/plugin-enterprise-rest": {
|
"@octokit/plugin-enterprise-rest": {
|
||||||
@ -1358,12 +1358,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@octokit/types": {
|
"@octokit/types": {
|
||||||
"version": "6.1.1",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.2.tgz",
|
||||||
"integrity": "sha512-btm3D6S7VkRrgyYF31etUtVY/eQ1KzrNRqhFt25KSe2mKlXuLXJilglRC6eDA2P6ou94BUnk/Kz5MPEolXgoiw==",
|
"integrity": "sha512-LPCpcLbcky7fWfHCTuc7tMiSHFpFlrThJqVdaHgowBTMS0ijlZFfonQC/C1PrZOjD4xRCYgBqH9yttEATGE/nw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@octokit/openapi-types": "^2.0.0",
|
"@octokit/openapi-types": "^2.0.1",
|
||||||
"@types/node": ">= 8"
|
"@types/node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -13038,6 +13038,20 @@
|
|||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||||
},
|
},
|
||||||
|
"yaml": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg=="
|
||||||
|
},
|
||||||
|
"yaml-loader": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml-loader/-/yaml-loader-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-1bNiLelumURyj+zvVHOv8Y3dpCri0F2S+DCcmps0pA1zWRLjS+FhZQg4o3aUUDYESh73+pKZNI18bj7stpReow==",
|
||||||
|
"requires": {
|
||||||
|
"loader-utils": "^1.4.0",
|
||||||
|
"yaml": "^1.8.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
"version": "15.4.1",
|
"version": "15.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||||
|
@ -100,7 +100,7 @@ impl Display for IpWithSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Project name.
|
/// Project name.
|
||||||
#[derive(Debug,Display,Clone,Serialize,Deserialize,PartialEq,Shrinkwrap)]
|
#[derive(Debug,Display,Clone,Serialize,Deserialize,From,PartialEq,Shrinkwrap)]
|
||||||
#[shrinkwrap(mutable)]
|
#[shrinkwrap(mutable)]
|
||||||
pub struct ProjectName(pub String);
|
pub struct ProjectName(pub String);
|
||||||
|
|
||||||
|
@ -8,26 +8,14 @@ use ensogl::system::web;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
mod connection_arguments {
|
|
||||||
pub const PROJECT_MANAGER : &str = "project_manager";
|
|
||||||
pub const LANGUAGE_SERVER_JSON : &str = "language_server_rpc";
|
|
||||||
pub const LANGUAGE_SERVER_BINARY : &str = "language_server_data";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==============
|
// ==============
|
||||||
// === Errors ===
|
// === Errors ===
|
||||||
// ==============
|
// ==============
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone,Copy,Debug,Fail)]
|
#[derive(Clone,Copy,Debug,Fail)]
|
||||||
#[fail(display="Missing program option: {}.",name)]
|
#[fail(display="Missing program option: {}.",0)]
|
||||||
pub struct MissingOption {name:&'static str}
|
pub struct MissingOption (&'static str);
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone,Copy,Debug,Fail)]
|
#[derive(Clone,Copy,Debug,Fail)]
|
||||||
@ -36,9 +24,9 @@ pub struct MutuallyExclusiveOptions;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==================
|
// ======================
|
||||||
// === Connection ===
|
// === BackendService ===
|
||||||
// ==================
|
// ======================
|
||||||
|
|
||||||
/// A Configuration defining to what backend service should IDE connect.
|
/// A Configuration defining to what backend service should IDE connect.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
@ -64,36 +52,127 @@ impl Default for BackendService {
|
|||||||
impl BackendService {
|
impl BackendService {
|
||||||
/// Read backend configuration from the web arguments. See also [`web::Arguments`]
|
/// Read backend configuration from the web arguments. See also [`web::Arguments`]
|
||||||
/// documentation.
|
/// documentation.
|
||||||
pub fn from_web_arguments(arguments:&web::Arguments) -> FallibleResult<Self> {
|
pub fn from_web_arguments(config:&ConfigReader) -> FallibleResult<Self> {
|
||||||
let pm_endpoint = arguments.get(connection_arguments::PROJECT_MANAGER).cloned();
|
if let Some(endpoint) = &config.project_manager {
|
||||||
let ls_json_endpoint = arguments.get(connection_arguments::LANGUAGE_SERVER_JSON).cloned();
|
if config.language_server_rpc.is_some() || config.language_server_data.is_some() {
|
||||||
let ls_bin_endpoint = arguments.get(connection_arguments::LANGUAGE_SERVER_BINARY).cloned();
|
|
||||||
if let Some(endpoint) = pm_endpoint {
|
|
||||||
if ls_json_endpoint.is_some() || ls_bin_endpoint.is_some() {
|
|
||||||
Err(MutuallyExclusiveOptions.into())
|
Err(MutuallyExclusiveOptions.into())
|
||||||
} else {
|
} else {
|
||||||
|
let endpoint = endpoint.clone();
|
||||||
Ok(Self::ProjectManager {endpoint})
|
Ok(Self::ProjectManager {endpoint})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match (ls_json_endpoint,ls_bin_endpoint) {
|
match (&config.language_server_rpc,&config.language_server_data) {
|
||||||
(Some(json_endpoint),Some(binary_endpoint)) =>
|
(Some(json_endpoint),Some(binary_endpoint)) => {
|
||||||
Ok(Self::LanguageServer {json_endpoint,binary_endpoint}),
|
let json_endpoint = json_endpoint.clone();
|
||||||
(None,None) =>
|
let binary_endpoint = binary_endpoint.clone();
|
||||||
Ok(default()),
|
Ok(Self::LanguageServer {json_endpoint,binary_endpoint})
|
||||||
(Some(_),None) =>
|
}
|
||||||
Err(MissingOption{name:connection_arguments::LANGUAGE_SERVER_BINARY}.into()),
|
(None,None) => Ok(default()),
|
||||||
(None,Some(_)) =>
|
(Some(_),None) => Err(MissingOption(config.names().language_server_data).into()),
|
||||||
Err(MissingOption{name:connection_arguments::LANGUAGE_SERVER_JSON }.into())
|
(None,Some(_)) => Err(MissingOption(config.names().language_server_rpc).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// === Config ===
|
||||||
|
// ==============
|
||||||
|
|
||||||
|
/// The path at which the config is accessible. This needs to be synchronised with the
|
||||||
|
/// `src/config.yaml` configuration file. In the future, we could write a procedural macro, which
|
||||||
|
/// loads the configuration and splits Rust variables from it during compilation time. This is not
|
||||||
|
/// possible by using macro rules, as there is no way to plug in the output of `include_str!` macro
|
||||||
|
/// to another macro input.
|
||||||
|
const WINDOW_CFG_PATH : &[&str] = &["enso","config"];
|
||||||
|
|
||||||
|
/// Defines a new config structure. The provided fields are converted to optional fields. The config
|
||||||
|
/// constructor queries JavaScript configuration for the keys defined in this structure. For each
|
||||||
|
/// resulting string value, it converts it to the defined type. It also reports warnings for all
|
||||||
|
/// config options that were provided, but were not matched this definition.
|
||||||
|
macro_rules! define_config {
|
||||||
|
($name:ident { $($field:ident : $field_type:ty),* $(,)? }) => {
|
||||||
|
|
||||||
|
/// Reflection mechanism containing string representation of option names.
|
||||||
|
#[derive(Clone,Copy,Debug)]
|
||||||
|
pub struct Names {
|
||||||
|
$($field : &'static str),*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Names {
|
||||||
|
fn default() -> Self {
|
||||||
|
$(let $field = stringify!{$field};)*
|
||||||
|
Self {$($field),*}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The structure containing application configs.
|
||||||
|
#[derive(Clone,Debug,Default)]
|
||||||
|
pub struct $name {
|
||||||
|
__names__ : Names,
|
||||||
|
$($field : Option<$field_type>),*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
/// Constructor.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let logger = Logger::new(stringify!{$name});
|
||||||
|
let window = web::window();
|
||||||
|
match web::reflect_get_nested_object(&window,WINDOW_CFG_PATH).ok() {
|
||||||
|
None => {
|
||||||
|
let path = WINDOW_CFG_PATH.join(".");
|
||||||
|
error!(&logger,"The config path '{path}' is invalid.");
|
||||||
|
default()
|
||||||
|
}
|
||||||
|
Some(cfg) => {
|
||||||
|
let __names__ = default();
|
||||||
|
let keys = web::object_keys(&cfg);
|
||||||
|
let mut keys = keys.into_iter().collect::<HashSet<String>>();
|
||||||
|
$(
|
||||||
|
let name = stringify!{$field};
|
||||||
|
let $field = web::reflect_get_nested_string(&cfg,&[name]).ok();
|
||||||
|
let $field = $field.map(|t|t.into());
|
||||||
|
keys.remove(name);
|
||||||
|
)*
|
||||||
|
for key in keys {
|
||||||
|
warning!(&logger,"Unknown config option provided '{key}'.");
|
||||||
|
}
|
||||||
|
Self {__names__,$($field),*}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reflection mechanism to get string representation of option names.
|
||||||
|
pub fn names(&self) -> &Names {
|
||||||
|
&self.__names__
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_config! {
|
||||||
|
ConfigReader {
|
||||||
|
entry : String,
|
||||||
|
project : ProjectName,
|
||||||
|
project_manager : String,
|
||||||
|
language_server_rpc : String,
|
||||||
|
language_server_data : String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ===============
|
||||||
|
// === Startup ===
|
||||||
|
// ===============
|
||||||
|
|
||||||
/// Configuration data necessary to initialize IDE.
|
/// Configuration data necessary to initialize IDE.
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct Startup {
|
pub struct Startup {
|
||||||
/// The configuration of connection to the backend service.
|
/// The configuration of connection to the backend service.
|
||||||
pub backend:BackendService,
|
pub backend : BackendService,
|
||||||
/// The project name we want to open on startup.
|
/// The project name we want to open on startup.
|
||||||
pub project_name : ProjectName
|
pub project_name : ProjectName
|
||||||
}
|
}
|
||||||
@ -110,12 +189,11 @@ impl Default for Startup {
|
|||||||
impl Startup {
|
impl Startup {
|
||||||
/// Read configuration from the web arguments. See also [`web::Arguments`] documentation.
|
/// Read configuration from the web arguments. See also [`web::Arguments`] documentation.
|
||||||
pub fn from_web_arguments() -> FallibleResult<Startup> {
|
pub fn from_web_arguments() -> FallibleResult<Startup> {
|
||||||
let arguments = ensogl::system::web::Arguments::new();
|
let config = ConfigReader::new();
|
||||||
let backend = BackendService::from_web_arguments(&arguments)?;
|
let backend = BackendService::from_web_arguments(&config)?;
|
||||||
let project_name = arguments.get("project").map(ProjectName::new);
|
let project_name = config.project.unwrap_or_else(||
|
||||||
let project_name = project_name.unwrap_or_else(|| {
|
|
||||||
ProjectName::new(constants::DEFAULT_PROJECT_NAME)
|
ProjectName::new(constants::DEFAULT_PROJECT_NAME)
|
||||||
});
|
);
|
||||||
Ok(Startup{backend,project_name})
|
Ok(Startup{backend,project_name})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ pub use web_sys::WebGl2RenderingContext;
|
|||||||
pub use web_sys::Window;
|
pub use web_sys::Window;
|
||||||
pub use std::time::Duration;
|
pub use std::time::Duration;
|
||||||
pub use std::time::Instant;
|
pub use std::time::Instant;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
// =============
|
||||||
@ -62,6 +62,13 @@ pub fn Error<S:Into<String>>(message:S) -> Error {
|
|||||||
|
|
||||||
pub type Result<T> = std::result::Result<T,Error>;
|
pub type Result<T> = std::result::Result<T,Error>;
|
||||||
|
|
||||||
|
impl From<JsValue> for Error {
|
||||||
|
fn from(t:JsValue) -> Self {
|
||||||
|
let message = format!("{:?}",t);
|
||||||
|
Self {message}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==============
|
// ==============
|
||||||
@ -595,50 +602,46 @@ pub async fn sleep(duration:Duration) {
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use async_std::task::sleep;
|
pub use async_std::task::sleep;
|
||||||
|
|
||||||
/// Stores arguments extracted from `Location`'s search
|
/// Get the nested value of the provided object. This is similar to writing `foo.bar.baz` in
|
||||||
/// (https://developer.mozilla.org/en-US/docs/Web/API/Location/search).
|
/// JavaScript, but in a safe manner, while checking if the value exists on each level.
|
||||||
/// e.g. extracts arg0 = value0, and arg1 = value1 from http://localhost/?arg0=value0&arg1=value1.
|
pub fn reflect_get_nested(target:&JsValue, keys:&[&str]) -> Result<JsValue> {
|
||||||
#[derive(Debug)]
|
let mut tgt = target.clone();
|
||||||
pub struct Arguments {
|
for key in keys {
|
||||||
pub hash_map : HashMap<String,String>
|
let obj = tgt.dyn_into::<js_sys::Object>()?;
|
||||||
}
|
let key = (*key).into();
|
||||||
|
tgt = js_sys::Reflect::get(&obj,&key)?;
|
||||||
impl Deref for Arguments {
|
|
||||||
type Target = HashMap<String,String>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.hash_map }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Arguments {
|
|
||||||
fn args_from_search(search:&str) -> HashMap<String,String> {
|
|
||||||
if search.chars().nth(0) == Some('?') {
|
|
||||||
let search_without_question_mark = &search[1..];
|
|
||||||
search_without_question_mark.split('&').filter_map(|arg| {
|
|
||||||
match arg.split('=').collect_vec().as_slice() {
|
|
||||||
[key,value] => Some(((*key).to_string(),(*value).to_string())),
|
|
||||||
[key] => Some(((*key).to_string(),"".to_string())),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}).collect()
|
|
||||||
} else {
|
|
||||||
default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new arguments map from location search.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
default()
|
|
||||||
}
|
}
|
||||||
|
Ok(tgt)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Arguments {
|
/// Get the nested value of the provided object and cast it to [`Object`]. See docs of
|
||||||
fn default() -> Self {
|
/// [`reflect_get_nested`] to learn more.
|
||||||
let search = window().location().search().unwrap_or_default();
|
pub fn reflect_get_nested_object(target:&JsValue, keys:&[&str]) -> Result<js_sys::Object> {
|
||||||
let hash_map = Self::args_from_search(&search);
|
let tgt = reflect_get_nested(target,keys)?;
|
||||||
Self{hash_map}
|
Ok(tgt.dyn_into()?)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the nested value of the provided object and cast it to [`String`]. See docs of
|
||||||
|
/// [`reflect_get_nested`] to learn more.
|
||||||
|
pub fn reflect_get_nested_string(target:&JsValue, keys:&[&str]) -> Result<String> {
|
||||||
|
let tgt = reflect_get_nested(target,keys)?;
|
||||||
|
let val = tgt.dyn_into::<js_sys::JsString>()?;
|
||||||
|
Ok(val.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all the keys of the provided [`Object`].
|
||||||
|
pub fn object_keys(target:&JsValue) -> Vec<String> {
|
||||||
|
target.clone().dyn_into::<js_sys::Object>().ok().map(|obj| {
|
||||||
|
js_sys::Object::keys(&obj).iter().map(|key| {
|
||||||
|
// The unwrap is safe, the `Object::keys` API guarantees it.
|
||||||
|
let js_str = key.dyn_into::<js_sys::JsString>().unwrap();
|
||||||
|
js_str.into()
|
||||||
|
}).collect()
|
||||||
|
}).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ============
|
// ============
|
||||||
// === Test ===
|
// === Test ===
|
||||||
// ============
|
// ============
|
||||||
@ -665,29 +668,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn args() {
|
|
||||||
let search = String::from("?project=HelloWorld&arg0=value0");
|
|
||||||
let args = Arguments::args_from_search(&search);
|
|
||||||
assert_eq!(args.len(), 2);
|
|
||||||
assert_eq!(args.get("project"), Some(&"HelloWorld".to_string()));
|
|
||||||
assert_eq!(args.get("arg0"), Some(&"value0".to_string()));
|
|
||||||
|
|
||||||
let search = String::from("project=HelloWorld&arg0=value0");
|
|
||||||
let args = Arguments::args_from_search(&search);
|
|
||||||
assert_eq!(args.len(), 0);
|
|
||||||
|
|
||||||
let search = String::from("");
|
|
||||||
let args = Arguments::args_from_search(&search);
|
|
||||||
assert_eq!(args.len(), 0);
|
|
||||||
|
|
||||||
let search = String::from("?project&arg0=value0");
|
|
||||||
let args = Arguments::args_from_search(&search);
|
|
||||||
assert_eq!(args.len(), 2);
|
|
||||||
assert_eq!(args.get("project"), Some(&"".to_string()));
|
|
||||||
assert_eq!(args.get("arg0"), Some(&"value0".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
mod helpers {
|
mod helpers {
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
Loading…
Reference in New Issue
Block a user