Add `RequestInlineCredit` event
Dependencies
- [2]
WFWTKCJNCreate initial Visual Studio Code extension - [3]
72K45XKDRefactor inline credit to improve hover messages - [4]
M2NAH3DCStore modified time of `FileContents` as `jiff::Timestamp` - [5]
DUZBRB3URender correspoding hunk on inline credit hover - [6]
6AUGQLIKAdd basic panic handling - [7]
T4NQUSRPEmit events on file decoration change - [8]
NB2MF3MYAdd `OpenWorkspaceFolder` event - [9]
XDFSAPI7Create a tokio runtime directly instead of `#[tokio::main]` - [10]
OUADGWKRCreate fully-initialized `SourceControl` object in `event_loop` - [11]
2ZAM5V35Move event handling into modules - [12]
TWEUQ64DMove threadsafe function builder logic into function-specific modules - [13]
QY4Z5ZXZRefactor `event_loop/threadsafe_function` file into `event_loop::js_function` module - [14]
IBVCQSSGUse `External` to wrap `initialize_source_control` arguments - [15]
IDY5SNLOUpdate source control resource states in event loop - [16]
3RNQI5RXRefactor `provide_file_decoration` to return a `Promise` resolved by `event_loop` - [17]
M5RW5PN4Add `OpenTextEditor` event - [18]
QY4DF3NMAdd `ChangeEditorContents` event - [19]
QJ6EZNXEAdd `RequestTrackedContents` event - [20]
37JT3GCXLazily load channel state instead of storing in memory - [21]
GQ52YLWHRemove unnecessary debug logs - [22]
IAMJPC2QInclude extension build info at compile time - [23]
ISLSD6ARUse `UriAbsoluteString` instead of `String` for `Event::OpenWorkspaceFolder` - [24]
3YGYMEXVCreate `event_loop` module
Change contents
- edit in editors/vscode/src/lib.rs at line 38
decoration_type: vscode_sys::reference::TextEditorDecorationTypeRef, - replacement in editors/vscode/src/lib.rs at line 162
// Re-render the inline credit annotationlet text_editor = open_repository.get_text_editor(env, relative_document_path)?.ok_or_else(|| {napi::Error::from_reason(format!("no open text editor for {absolute_document_path}"))})?;// // Re-render the inline credit annotation// let text_editor = open_repository// .get_text_editor(env, relative_document_path)?// .ok_or_else(|| {// napi::Error::from_reason(format!(// "no open text editor for {absolute_document_path}"// ))// })?; - replacement in editors/vscode/src/lib.rs at line 171
inline_credit::render(env, &program_state, &text_editor)?;// inline_credit::render(env, &program_state, &text_editor)?; - replacement in editors/vscode/src/lib.rs at line 179
env: &napi::Env,_env: &napi::Env, - replacement in editors/vscode/src/lib.rs at line 184
let uri = document.get_uri()?;let vscode_uri = document.get_uri()?; - replacement in editors/vscode/src/lib.rs at line 187
if uri.get_scheme()? == "output" {if vscode_uri.get_scheme()? == "output" { - replacement in editors/vscode/src/lib.rs at line 191
let program_state = ExtensionState::get()?;inline_credit::render(env, &program_state, &editor)let uri = uri::from_vscode(&vscode_uri)?;event_loop::send(Event::RequestInlineCredit { uri });Ok(()) - edit in editors/vscode/src/lib.rs at line 382
let decoration_type = inline_credit::create_decoration_type(env)?; - edit in editors/vscode/src/lib.rs at line 386
decoration_type: decoration_type.create_ref()?, - edit in editors/vscode/src/lib.rs at line 390[2.119077]→[2.119077:119166](∅→∅),[2.119166]→[3.948:1024](∅→∅),[3.1024]→[2.119331:119338](∅→∅),[2.119331]→[2.119331:119338](∅→∅)
if let Some(active_text_editor) = vscode_sys::window::get_active_text_editor(env)? {inline_credit::render(env, &extension_state, &active_text_editor)?;} - replacement in editors/vscode/src/inline_credit/mod.rs at line 1
use camino::Utf8PathBuf;use icu_locale::locale;use crate::vscode_sys; - edit in editors/vscode/src/inline_credit/mod.rs at line 3
use crate::{ExtensionState, vscode_sys}; - edit in editors/vscode/src/inline_credit/mod.rs at line 6
pub struct LineDecoration {pub line_number: usize,pub line_annotation_text: String,pub hover_message_text: String,}#[tracing::instrument(skip_all)] - edit in editors/vscode/src/inline_credit/mod.rs at line 29[2.123292]→[3.1183:1757](∅→∅),[3.1757]→[3.1757:1801](∅→∅),[3.1801]→[3.1801:3292](∅→∅),[3.3292]→[4.1082:1120](∅→∅),[4.1120]→[3.3292:3552](∅→∅),[3.3292]→[3.3292:3552](∅→∅),[3.3552]→[5.9474:9544](∅→∅),[5.9544]→[3.3592:4381](∅→∅),[3.3592]→[3.3592:4381](∅→∅)
}// TODO: different rendering modes? e.g. render only at boundaries between lines, render only on cursors, etcpub fn render(env: &napi::Env,program_state: &ExtensionState,editor: &vscode_sys::TextEditor,) -> Result<(), napi::Error> {let editor_document = editor.get_document()?;let document_uri = editor_document.get_uri()?;let Some((repository_path, open_repository)) = program_state.repositories.get_open_repository(env, &document_uri)?else {tracing::warn!(message = "No repository for URI",uri = document_uri.to_string()?);return Ok(());};let absolute_file_path = Utf8PathBuf::from(document_uri.get_fs_path()?);let relative_file_path =absolute_file_path.strip_prefix(&repository_path).map_err(|error| {napi::Error::from_reason(format!("Failed to strip prefix {repository_path} from {absolute_file_path}: {error}"))})?;let open_file = open_repository.repository.get_open_file(relative_file_path).ok_or_else(|| {napi::Error::from_reason(format!("unable to get open file for {relative_file_path}"))})?;// TODO: don't initialize herelet localization_context = l10n_embed::Context::new(locale!("en-US"), false);let mut decoration_options_list = Vec::new();for editor_selection in editor.get_selections()? {let start_line = editor_selection.get_start()?.get_line()?;let end_line: u32 = editor_selection.get_end()?.get_line()?;let selected_range = (start_line as usize)..=(end_line as usize);let credits = open_file.credit(selected_range);for credit_span in credits {for line in credit_span.lines {let annotation_range = line_annotation::range(env, open_file, line)?;let line_annotation_text = line_annotation::render(&localization_context,&open_repository.repository,&editor_document,open_file,&credit_span.value,)?;let hover_message = hover::render(env,&localization_context,&open_repository.repository,open_file,credit_span.value,)?;let after = vscode_sys::ThemableDecorationAttachmentRenderOptions::new(env)?.content_text(&line_annotation_text)?;let render_options =vscode_sys::DecorationInstanceRenderOptions::new(env)?.after(after)?;let decoration_options =vscode_sys::DecorationOptions::new(env, &annotation_range)?.hover_message(&hover_message)?.render_options(render_options)?;decoration_options_list.push(decoration_options);}}}let decoration_type = program_state.decoration_type.get_inner(env)?;editor.set_decorations(decoration_type, decoration_options_list)?;Ok(()) - edit in editors/vscode/src/inline_credit/line_annotation.rs at line 9
use crate::vscode_sys; - edit in editors/vscode/src/inline_credit/line_annotation.rs at line 31[2.124412]→[2.124412:124415](∅→∅),[2.124415]→[3.4566:4989](∅→∅),[3.4989]→[2.124611:124612](∅→∅),[2.124611]→[2.124611:124612](∅→∅),[2.124612]→[3.4990:5160](∅→∅),[3.5160]→[2.124872:124873](∅→∅),[2.124872]→[2.124872:124873](∅→∅),[2.124873]→[3.5161:5207](∅→∅)
}pub fn range<'env>(env: &'env napi::Env,open_file: &OpenFile,line: usize,) -> Result<vscode_sys::Range<'env>, napi::Error> {let current_line_contents = open_file.contents.text.get_line(line, ropey::LineType::LF_CR).ok_or_else(|| napi::Error::from_reason(format!("no contents for line {line}")))?;let final_character_offset = current_line_contents.len() as u32;let start = vscode_sys::Position::new(env, line as u32, final_character_offset)?;let end = vscode_sys::Position::new(env, line as u32, final_character_offset)?;vscode_sys::Range::new(env, &start, &end) - replacement in editors/vscode/src/inline_credit/line_annotation.rs at line 34
localization_context: &l10n_embed::Context,credit_source: &CreditSource,open_file: &OpenFile,text_document_is_dirty: bool, - replacement in editors/vscode/src/inline_credit/line_annotation.rs at line 38[2.125029]→[4.1121:1167](∅→∅),[4.1167]→[2.125029:125055](∅→∅),[2.125029]→[2.125029:125055](∅→∅),[2.125055]→[3.5224:5258](∅→∅)
text_document: &vscode_sys::TextDocument,open_file: &OpenFile,credit_source: &CreditSource,localization_context: &l10n_embed::Context, - replacement in editors/vscode/src/inline_credit/line_annotation.rs at line 71
let timestamp = if text_document.get_is_dirty()? {let timestamp = if text_document_is_dirty { - edit in editors/vscode/src/inline_credit/hover.rs at line 14
use crate::vscode_sys; - replacement in editors/vscode/src/inline_credit/hover.rs at line 55
pub fn render<'env>(env: &'env napi::Env,localization_context: &l10n_embed::Context,repository: &FileSystemRepository,open_file: &OpenFile,pub fn render( - replacement in editors/vscode/src/inline_credit/hover.rs at line 57
) -> Result<vscode_sys::MarkdownString<'env>, napi::Error> {open_file: &OpenFile,repository: &FileSystemRepository,localization_context: &l10n_embed::Context,) -> Result<String, napi::Error> { - replacement in editors/vscode/src/inline_credit/hover.rs at line 164
let mut hover_message = vscode_sys::MarkdownString::new(env, &rendered_html, false)?;hover_message.set_support_html(true)?;Ok(hover_message)Ok(rendered_html) - edit in editors/vscode/src/event_loop/mod.rs at line 30
decoration_type: Rc<vscode_sys::reference::TextEditorDecorationTypeRef>,localization_context: Rc<l10n_embed::Context>, - edit in editors/vscode/src/event_loop/mod.rs at line 88
decoration_type: vscode_sys::reference::TextEditorDecorationTypeRef, - edit in editors/vscode/src/event_loop/mod.rs at line 96
decoration_type: Rc::new(decoration_type),localization_context: Rc::new(l10n_embed::Context::new(icu_locale::locale!("en-US"),false,)), - edit in editors/vscode/src/event_loop/mod.rs at line 137
}Event::RequestInlineCredit { uri } => {event::request_inline_credit::handle(uri, &extension_state, &js_functions).await - edit in editors/vscode/src/event_loop/mod.rs at line 176
let decoration_type = crate::inline_credit::create_decoration_type(env)?.create_ref()?; - replacement in editors/vscode/src/event_loop/mod.rs at line 188
std::thread::spawn(move || runtime.block_on(event_loop(js_functions, sender, receiver)));std::thread::spawn(move || {runtime.block_on(event_loop(decoration_type, js_functions, sender, receiver))}); - file addition: set_text_editor_decorations.rs[13.435]
use std::rc::Rc;use napi::bindgen_prelude::{External, FunctionCallContext};use napi::threadsafe_function::ThreadsafeFunction;use crate::vscode_sys;pub struct Arguments {pub line_decorations: Vec<crate::inline_credit::LineDecoration>,pub text_editor_reference: Rc<vscode_sys::reference::TextEditorRef>,pub decoration_type_reference: Rc<vscode_sys::reference::TextEditorDecorationTypeRef>,}pub type Prototype =ThreadsafeFunction<External<Arguments>, (), External<Arguments>, napi::Status, false, false, 0>;pub fn build(env: &napi::Env) -> Result<Prototype, napi::Error> {env.create_function_from_closure("set_text_editor_decorations", callback)?.build_threadsafe_function().build()}fn callback(function_call_context: FunctionCallContext) -> Result<(), napi::Error> {let (external,): (&External<Arguments>,) = function_call_context.args()?;let Arguments {line_decorations,text_editor_reference,decoration_type_reference,} = &**external;let mut decoration_options_list = Vec::with_capacity(line_decorations.len());for crate::inline_credit::LineDecoration {line_number,line_annotation_text,hover_message_text,} in line_decorations{let annotation_line =vscode_sys::Position::new(function_call_context.env, *line_number as u32, u32::MAX)?;let annotation_range = vscode_sys::Range::new(function_call_context.env,&annotation_line,&annotation_line,)?;let mut hover_message =vscode_sys::MarkdownString::new(function_call_context.env, hover_message_text, false)?;hover_message.set_support_html(true)?;let after =vscode_sys::ThemableDecorationAttachmentRenderOptions::new(function_call_context.env)?.content_text(line_annotation_text)?;let render_options =vscode_sys::DecorationInstanceRenderOptions::new(function_call_context.env)?.after(after)?;let decoration_options =vscode_sys::DecorationOptions::new(function_call_context.env, &annotation_range)?.hover_message(&hover_message)?.render_options(render_options)?;decoration_options_list.push(decoration_options);}let text_editor = text_editor_reference.get_inner(function_call_context.env)?;let decoration_type = decoration_type_reference.get_inner(function_call_context.env)?;text_editor.set_decorations(decoration_type, decoration_options_list)?;Ok(())} - edit in editors/vscode/src/event_loop/js_function/mod.rs at line 10
pub mod get_text_editor_is_dirty;pub mod get_text_editor_selections; - edit in editors/vscode/src/event_loop/js_function/mod.rs at line 13
pub mod set_text_editor_decorations; - edit in editors/vscode/src/event_loop/js_function/mod.rs at line 18
get_text_editor_is_dirty: get_text_editor_is_dirty::Prototype,get_text_editor_selections: get_text_editor_selections::Prototype, - edit in editors/vscode/src/event_loop/js_function/mod.rs at line 21
set_text_editor_decorations: set_text_editor_decorations::Prototype, - edit in editors/vscode/src/event_loop/js_function/mod.rs at line 30
get_text_editor_is_dirty: get_text_editor_is_dirty::build(env)?,get_text_editor_selections: get_text_editor_selections::build(env)?, - edit in editors/vscode/src/event_loop/js_function/mod.rs at line 33
set_text_editor_decorations: set_text_editor_decorations::build(env)?, - edit in editors/vscode/src/event_loop/js_function/mod.rs at line 48
.call_async(External::new(arguments)).await}pub async fn get_text_editor_is_dirty(&self,text_editor_reference: &Rc<vscode_sys::reference::TextEditorRef>,) -> Result<bool, napi::Error> {let arguments = get_text_editor_is_dirty::Arguments {text_editor_reference: Rc::clone(text_editor_reference),};self.get_text_editor_is_dirty.call_async(External::new(arguments)).await}pub async fn get_text_editor_selections(&self,text_editor_reference: &Rc<vscode_sys::reference::TextEditorRef>,) -> Result<Vec<(u32, u32)>, napi::Error> {let arguments = get_text_editor_selections::Arguments {text_editor_reference: Rc::clone(text_editor_reference),};self.get_text_editor_selections - edit in editors/vscode/src/event_loop/js_function/mod.rs at line 93
.call_async(External::new(arguments)).await}pub async fn set_text_editor_decorations(&self,line_decorations: Vec<crate::inline_credit::LineDecoration>,text_editor_reference: &Rc<vscode_sys::reference::TextEditorRef>,decoration_type_reference: &Rc<vscode_sys::reference::TextEditorDecorationTypeRef>,) -> Result<(), napi::Error> {let arguments = set_text_editor_decorations::Arguments {line_decorations,text_editor_reference: Rc::clone(text_editor_reference),decoration_type_reference: Rc::clone(decoration_type_reference),};self.set_text_editor_decorations - file addition: get_text_editor_selections.rs[13.435]
use std::rc::Rc;use napi::bindgen_prelude::{External, FunctionCallContext};use napi::threadsafe_function::ThreadsafeFunction;use crate::vscode_sys;pub struct Arguments {pub text_editor_reference: Rc<vscode_sys::reference::TextEditorRef>,}pub type Prototype = ThreadsafeFunction<External<Arguments>,Vec<(u32, u32)>,External<Arguments>,napi::Status,false,false,0,>;pub fn build(env: &napi::Env) -> Result<Prototype, napi::Error> {env.create_function_from_closure("get_text_editor_selections", callback)?.build_threadsafe_function().build()}fn callback(function_call_context: FunctionCallContext) -> Result<Vec<(u32, u32)>, napi::Error> {let (external,): (&External<Arguments>,) = function_call_context.args()?;let Arguments {text_editor_reference,} = &**external;let text_editor = text_editor_reference.get_inner(function_call_context.env)?;let editor_selections = text_editor.get_selections()?;let mut selections = Vec::with_capacity(editor_selections.len());for editor_selection in text_editor.get_selections()? {let start_line = editor_selection.get_start()?.get_line()?;let end_line = editor_selection.get_end()?.get_line()?;selections.push((start_line, end_line));}Ok(selections)} - file addition: get_text_editor_is_dirty.rs[13.435]
use std::rc::Rc;use napi::bindgen_prelude::{External, FunctionCallContext};use napi::threadsafe_function::ThreadsafeFunction;use crate::vscode_sys;pub struct Arguments {pub text_editor_reference: Rc<vscode_sys::reference::TextEditorRef>,}pub type Prototype = ThreadsafeFunction<External<Arguments>,bool,External<Arguments>,napi::Status,false,false,0,>;pub fn build(env: &napi::Env) -> Result<Prototype, napi::Error> {env.create_function_from_closure("get_text_editor_is_dirty", callback)?.build_threadsafe_function().build()}fn callback(function_call_context: FunctionCallContext) -> Result<bool, napi::Error> {let (external,): (&External<Arguments>,) = function_call_context.args()?;let Arguments {text_editor_reference,} = &**external;let text_editor = text_editor_reference.get_inner(function_call_context.env)?;let document = text_editor.get_document()?;document.get_is_dirty()} - file addition: request_inline_credit.rs[11.332]
use iri_string::types::UriAbsoluteString;use crate::event_loop::ExtensionState;use crate::event_loop::js_function::Functions;#[tracing::instrument(skip(extension_state, js_functions))]pub async fn handle(uri: UriAbsoluteString,extension_state: &ExtensionState,js_functions: &Functions,) {let Some((repository_path, relative_path, repository)) = extension_state.get_repository(&uri)else {tracing::info!(message = "No matching repository for path", ?uri);return;};let Some(open_file) = repository.repository.get_open_file(relative_path) else {tracing::info!(message = "No open file found in repository",?repository_path,?relative_path);return;};let Some(text_editor_reference) = repository.open_editors.get(relative_path) else {tracing::error!(message = "No matching TextEditor found for path",?repository_path,?relative_path);return;};let editor_selections = match js_functions.get_text_editor_selections(text_editor_reference).await{Ok(editor_selections) => editor_selections,Err(error) => {tracing::error!(message = "Failed to get text editor selections", ?error);return;}};let text_document_is_dirty = match js_functions.get_text_editor_is_dirty(text_editor_reference).await{Ok(is_dirty) => is_dirty,Err(error) => {tracing::warn!(message = "Failed to get isDirty for text editor", ?error);false}};let mut line_decorations = Vec::new();for (start_line, end_line) in editor_selections {let selected_range = (start_line as usize)..=(end_line as usize);let credit_spans = open_file.credit(selected_range);for credit_span in credit_spans {for line in credit_span.lines {let line_annotation_text = match crate::inline_credit::line_annotation::render(&credit_span.value,open_file,text_document_is_dirty,&repository.repository,&extension_state.localization_context,) {Ok(line_annotation_text) => line_annotation_text,Err(error) => {tracing::error!(message = "Failed to render line annotation", ?error);return;}};let hover_message_text = match crate::inline_credit::hover::render(credit_span.value,open_file,&repository.repository,&extension_state.localization_context,) {Ok(hover_message_text) => hover_message_text,Err(error) => {tracing::error!(message = "Failed to render hover message", ?error);return;}};line_decorations.push(crate::inline_credit::LineDecoration {line_number: line,line_annotation_text,hover_message_text,})}}}if let Err(error) = js_functions.set_text_editor_decorations(line_decorations,text_editor_reference,&extension_state.decoration_type,).await{tracing::error!(message = "Failed to set text editor decorations", ?error);return;}} - edit in editors/vscode/src/event_loop/event/mod.rs at line 10
pub mod request_inline_credit; - edit in editors/vscode/src/event_loop/event/mod.rs at line 32
},RequestInlineCredit {uri: UriAbsoluteString,