lib.rs
#![feature(map_try_insert)]
#![feature(mapped_lock_guards)]
// TODO: fix credits in quick diff panel
// TODO: find a way to validate all subscriptions are disposed? or just standardize how they are registered
// TODO: replace `get_repository_folder` with something that can handle nested repositories
// TODO: consistent naming
// TODO: handle adding/removing workspace folders (and adding/removing .pijul folders)
// TODO: handle changing active text editors
// TODO: color workspace root with `gitDecoration.submoduleResourceForeground`?
// TODO: something breaks when opening quick diff
// TODO: static handling of package.json contributions
// TODO: move l10n into pijul-extension
// TODO: warn instead of returning an error for non-fatal errors
use napi::bindgen_prelude;
use napi_derive::napi;
use crate::event_loop::Event;
mod event_loop;
mod inline_credit;
mod repository;
mod uri;
mod vscode_sys;
const BUILD_INFO: extension_build_info::Build = extension_build_info::include_build_info!();
pub const PIJUL_SCHEME: &str = "pijul";
#[tracing::instrument(skip_all)]
fn provide_file_decoration<'env>(
env: &'env napi::Env,
vscode_uri: vscode_sys::Uri,
_cancellation_token: bindgen_prelude::Object,
) -> Result<bindgen_prelude::Object<'env>, napi::Error> {
let uri = uri::from_vscode(&vscode_uri)?;
let (deferred_promise, promise_object) = env.create_deferred()?;
event_loop::send(Event::RequestFileDecoration {
uri,
deferred_promise,
});
Ok(promise_object)
}
#[tracing::instrument(skip_all)]
fn provide_text_document_content<'env>(
env: &'env napi::Env,
encoded_uri: vscode_sys::Uri,
_cancellation_token: bindgen_prelude::Object,
) -> Result<bindgen_prelude::Object<'env>, napi::Error> {
// Example: pijul:tracked/dnNjb2RlOmV4YW1wbGUvdXJp -> vscode:example/uri
let decoded_uri = uri::decode(&encoded_uri)?;
let (deferred_promise, promise_object) = env.create_deferred()?;
event_loop::send(Event::RequestTrackedContents {
uri: decoded_uri,
deferred_promise,
});
Ok(promise_object)
}
fn provide_original_resource<'env>(
env: &'env napi::Env,
uri_to_encode: vscode_sys::Uri<'env>,
_cancellation_token: bindgen_prelude::Object,
) -> Result<Option<vscode_sys::Uri<'env>>, napi::Error> {
// Encode the original URI as part of a Pijul URI
// Example: vscode:example/uri -> pijul:tracked/dnNjb2RlOmV4YW1wbGUvdXJp
let encoded_uri = uri::encode(&uri_to_encode)?;
let vscode_uri = uri::to_vscode(env, &encoded_uri)?;
Ok(Some(vscode_uri))
}
#[tracing::instrument(skip_all)]
fn on_did_change_text_document(
_env: &napi::Env,
event: vscode_sys::TextDocumentChangeEvent,
) -> Result<(), napi::Error> {
let text_document = event.get_document()?;
let document_uri = text_document.get_uri()?;
// Ignore any messages printed to the `Output` channel (used by `tracing`).
// Since the output view is a text document, any logging calls will trigger this function,
// so make sure to return before it sends itself into an infinite loop.
if document_uri.get_scheme()? == "output" {
return Ok(());
}
let uri = uri::from_vscode(&document_uri)?;
let change_events = event.get_content_changes()?;
let mut changes = Vec::with_capacity(change_events.len());
for change_event in &change_events {
changes.push(event_loop::EditorContentsChange {
character_offset: change_event.get_range_offset()?,
characters_replaced: change_event.get_range_length()?,
replacement_text: change_event.get_text()?,
})
}
event_loop::send(Event::ChangeEditorContents { uri, changes });
Ok(())
}
#[tracing::instrument(skip_all)]
fn on_did_change_text_editor_selections(
_env: &napi::Env,
event: vscode_sys::TextEditorSelectionChangeEvent,
) -> Result<(), napi::Error> {
let editor = event.get_text_editor()?;
let document = editor.get_document()?;
let vscode_uri = document.get_uri()?;
// Ignore any messages printed to the `Output` channel (used by `tracing`)
if vscode_uri.get_scheme()? == "output" {
return Ok(());
}
let uri = uri::from_vscode(&vscode_uri)?;
event_loop::send(Event::RequestInlineCredit { uri });
Ok(())
}
#[tracing::instrument(skip_all)]
fn on_did_change_workspace_folders(
_env: &napi::Env,
event: vscode_sys::WorkspaceFoldersChangeEvent,
) -> Result<(), napi::Error> {
let added_workspaces = event.get_added()?;
let removed_workspaces = event.get_removed()?;
for added_workspace in added_workspaces {
let added_workspace_uri = added_workspace.get_uri()?;
let added_uri = uri::from_vscode(&added_workspace_uri)?;
event_loop::send(Event::OpenWorkspaceFolder {
workspace_uri: added_uri,
});
}
for removed_workspace in removed_workspaces {
// TODO
}
Ok(())
}
// TODO: handle closing text editors
#[tracing::instrument(skip_all)]
fn on_did_change_visible_text_editors(
_env: &napi::Env,
visible_text_editors: Vec<vscode_sys::TextEditor>,
) -> Result<(), napi::Error> {
for text_editor in &visible_text_editors {
let document_uri = text_editor.get_document()?.get_uri()?;
let uri = uri::from_vscode(&document_uri)?;
event_loop::send(Event::OpenTextEditor {
uri,
text_editor: text_editor.create_ref()?,
});
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub fn handle_fs_watcher_event(
_env: &napi::Env,
vscode_uri: vscode_sys::Uri,
) -> Result<(), napi::Error> {
let uri = uri::from_vscode(&vscode_uri)?;
event_loop::send(Event::ChangedFilesystemContents { uri });
Ok(())
}
#[tracing::instrument(skip_all)]
pub fn on_did_move_files(
_env: &napi::Env,
event: vscode_sys::FileRenameEvent,
) -> Result<(), napi::Error> {
for renamed_file in event.get_files()? {
let old_vscode_uri = renamed_file.get_old_uri()?;
let new_vscode_uri = renamed_file.get_new_uri()?;
let old_uri = uri::from_vscode(&old_vscode_uri)?;
let new_uri = uri::from_vscode(&new_vscode_uri)?;
event_loop::send(Event::MovePath { old_uri, new_uri });
}
Ok(())
}
// TODO: make sure things are registered in order:
// 1. Create extension state
// 2. First-time init e.g. initial inline credit and open files
// 3. Register extension state
// 4. Providers
// 5. Event handlers
// TODO: remove catch_unwind (make activate_internal)
#[napi(catch_unwind)]
pub fn activate(
env: &napi::Env,
vscode_object: bindgen_prelude::Object,
extension_context: bindgen_prelude::Object,
) -> Result<(), napi::Error> {
vscode_sys::activate(&vscode_object, &extension_context)?;
vscode_sys::log::init(
env,
"Pijul",
tracing_subscriber::fmt::format::DefaultFields::new(),
)?;
let decoration_change_event_emitter = vscode_sys::EventEmitter::new(env)?;
let decoration_change_event = decoration_change_event_emitter.get_event()?;
let mut file_decoration_provider =
vscode_sys::FileDecorationProvider::new(env, provide_file_decoration)?;
file_decoration_provider.set_on_did_change_file_decorations(decoration_change_event)?;
vscode_sys::window::register_file_decoration_provider(
env,
&extension_context,
file_decoration_provider,
)?;
let mut quick_diff_provider = vscode_sys::QuickDiffProvider::new(env)?;
quick_diff_provider.set_provide_original_resource(env, provide_original_resource)?;
event_loop::start(
env,
&vscode_object,
decoration_change_event_emitter,
quick_diff_provider,
)?;
for workspace_folder in vscode_sys::workspace::get_workspace_folders(env)? {
let vscode_uri = workspace_folder.get_uri()?;
let workspace_uri = uri::from_vscode(&vscode_uri)?;
event_loop::send(Event::OpenWorkspaceFolder { workspace_uri });
}
let visible_text_editors = vscode_sys::window::get_visible_text_editors(env)?;
for text_editor in visible_text_editors {
let document_uri = text_editor.get_document()?.get_uri()?;
let uri = uri::from_vscode(&document_uri)?;
event_loop::send(Event::OpenTextEditor {
uri,
text_editor: text_editor.create_ref()?,
});
}
let text_document_provider =
vscode_sys::TextDocumentContentProvider::new(env, provide_text_document_content)?;
vscode_sys::workspace::register_text_document_content_provider(
env,
&extension_context,
PIJUL_SCHEME,
text_document_provider,
)?;
vscode_sys::window::on_did_change_text_editor_selections(
env,
on_did_change_text_editor_selections,
)?;
vscode_sys::window::on_did_change_visible_text_editors(
env,
on_did_change_visible_text_editors,
)?;
vscode_sys::workspace::on_did_change_text_document(env, on_did_change_text_document)?;
vscode_sys::workspace::on_did_change_workspace_folders(env, on_did_change_workspace_folders)?;
vscode_sys::workspace::on_did_rename_files(env, on_did_move_files)?;
let file_system_watcher = vscode_sys::workspace::create_file_system_watcher(env, "**")?;
file_system_watcher.on_did_change(env, handle_fs_watcher_event)?;
file_system_watcher.on_did_create(env, handle_fs_watcher_event)?;
file_system_watcher.on_did_delete(env, handle_fs_watcher_event)?;
tracing::info!(message = "Extension activated", ?BUILD_INFO);
Ok(())
}