Skip to content

Commit

Permalink
Merge branch '0.1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Weichart committed Sep 20, 2022
2 parents 467b0e7 + 2b05b77 commit a2dc073
Show file tree
Hide file tree
Showing 21 changed files with 967 additions and 505 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Cargo.lock
.vscode-test
.vscode-launch.json
.vscode-tasks.json
copy_to_vault.sh
8 changes: 8 additions & 0 deletions docs/dev-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ The TypeScript part of the project is used create the UI using React. Also, it s

Contributions are welcome, but please make sure they are understandable and no bloat

#### Building

- Build the project by running `npm run build` in the root directory
- To easily copy the build results into your vault, run `npm run build-copy` in the root directory
- NOTE: Don't forget to:
- change the vault path in `package.json` to your vault path!
- make the copy script executable by running `chmod +x ./copy_to_vault.sh`

### 🗺️ Roadmap

### Near future
Expand Down
27 changes: 26 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::rs::note::note_scanned_event::NoteScannedEvent;
mod rs;

#[wasm_bindgen]
pub fn find(context: JsValue, notes: Array, callback: Function) -> Array {
pub fn find_in_vault(context: JsValue, notes: Array, callback: Function) -> Array {
let notes: Vec<Note> = notes.iter()
.filter_map(|note: JsValue| Note::try_from(note).ok())
.collect();
Expand All @@ -36,6 +36,31 @@ pub fn find(context: JsValue, notes: Array, callback: Function) -> Array {
array
}


#[wasm_bindgen]
pub fn find_in_note(context: JsValue, active_note: Note, notes: Array, callback: Function) -> Array {
let notes: Vec<Note> = notes.iter()
.filter_map(|note: JsValue| Note::try_from(note).ok())
.collect();
let mut note = active_note.clone();

let mut res: Vec<LinkFinderResult> = vec![];

let _ = call_callback(&callback, &context, build_args(&note, 0));

let link_finder_result_option = link_finder::find_links(&mut note, &notes);
if let Some(r) = link_finder_result_option {
res.push(r);
}

let array: Array = Array::new();
for r in res {
let js: JsValue = r.into();
array.push(&js);
}
array
}

fn build_args(note: &Note, index: usize) -> Array {
let args = js_sys::Array::new();
let note_scanned_event = NoteScannedEvent::new(note, index).to_json_string();
Expand Down
68 changes: 46 additions & 22 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,58 @@
import {Plugin} from "obsidian";
import { Plugin } from "obsidian";
import rustPlugin from "../pkg/obisidian_note_linker_bg.wasm";
import * as wasm from "../pkg";
import MainModal from "./ts/MainModal";
import {init_panic_hook} from "../pkg/";
import { init_panic_hook } from "../pkg/";
import * as Comlink from "comlink";

// @ts-ignore
import Wcw from 'web-worker:./ts/webWorkers/WasmWorker.ts';
import Wcw from "web-worker:./ts/webWorkers/WasmWorker.ts";
import { MatchingMode } from "./ts/components/containers/MainComponent";

export default class RustPlugin extends Plugin {
async onload() {
// init wasm
const buffer = Uint8Array.from(atob(rustPlugin as unknown as string), c => c.charCodeAt(0))
await wasm.default(Promise.resolve(buffer));
init_panic_hook()
async onload() {
// init wasm
const buffer = Uint8Array.from(atob(rustPlugin as unknown as string), (c) =>
c.charCodeAt(0)
);
await wasm.default(Promise.resolve(buffer));
init_panic_hook();

this.addRibbonIcon('link', 'Note Linker', async () => {
// init the secondary wasm thread (for searching)
const wcw = new Wcw();
const WasmComlinkWorker = Comlink.wrap<typeof Wcw>(wcw)
let wasmWorkerInstance: Comlink.Remote<typeof WasmComlinkWorker>;
this.addRibbonIcon("link", "Note Linker", () => this.openModal());
this.addCommand({
id: "open-note-linker",
name: "Open",
callback: this.openModal,
});
this.addCommand({
id: "open-note-linker-vault",
name: "Scan Vault",
callback: () => this.openModal(MatchingMode.Vault),
});
this.addCommand({
id: "open-note-linker-note",
name: "Scan Note",
callback: () => this.openModal(MatchingMode.Note),
});
}

wasmWorkerInstance = await new WasmComlinkWorker();
await wasmWorkerInstance.init();
openModal = async (_matchingModal?: MatchingMode) => {
// init the secondary wasm thread (for searching)
const wcw = new Wcw();
const WasmComlinkWorker = Comlink.wrap<typeof Wcw>(wcw);
let wasmWorkerInstance: Comlink.Remote<typeof WasmComlinkWorker>;

const linkMatchSelectionModal = new MainModal(app, wasmWorkerInstance, () => {
wcw.terminate();
});
linkMatchSelectionModal.open();
});
}
wasmWorkerInstance = await new WasmComlinkWorker();
await wasmWorkerInstance.init();

}
const linkMatchSelectionModal = new MainModal(
app,
wasmWorkerInstance,
() => {
wcw.terminate();
},
_matchingModal
);
linkMatchSelectionModal.open();
};
}
15 changes: 11 additions & 4 deletions src/rs/matching/link_finder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::ops::Add;

use fancy_regex::{escape, Regex};

use crate::rs::util::wasm_util::log;
use crate::{LinkFinderResult};
use crate::rs::matching::link_match::LinkMatch;
use crate::rs::matching::regex_match::RegexMatch;
Expand All @@ -19,8 +20,11 @@ struct LinkFinderMatchingResult<'m> {

impl<'m> LinkFinderMatchingResult<'m> {
fn find_matches(note: &'m mut Note, target_note: &'m Note) -> Self {
// build the regex
let regex_matches: Vec<RegexMatch> = build_link_finder(target_note)
// find all matches
.captures_iter(note.get_sanitized_content())
// map the results to a vector of RegexMatch
.filter_map(|capture_result| {
match capture_result {
Ok(captures) => {
Expand All @@ -42,6 +46,7 @@ impl<'m> LinkFinderMatchingResult<'m> {
}
}

/// Creates a vec of LinkMatch from a LinkFinderMatchingResult.
impl<'m> Into<Vec<LinkMatch>> for LinkFinderMatchingResult<'m> {
fn into(self) -> Vec<LinkMatch> {
let note: &Note = self.note;
Expand All @@ -60,10 +65,10 @@ impl<'m> Into<Vec<LinkMatch>> for LinkFinderMatchingResult<'m> {
fn concat_as_regex_string(strings: &[String]) -> String {
strings.iter()
.enumerate()
.fold("(".to_string(), |prev, (index, current)| {
return if index == 0 { format!("{}{}", prev, current) } else { format!("{}|{}", prev, current) };
.fold("".to_string(), |prev, (index, current)| {
return if index == 0 { format!("(\\b{}\\b)", current) } else { format!("{}|(\\b{}\\b)", prev, current) };
})
.add(")")
.add("")
}

/// Constructs a LinkFinder for the provided target note.
Expand All @@ -73,7 +78,9 @@ fn build_link_finder(target_note: &Note) -> LinkFinder {
escaped_search_strings.push(escaped_title);

let regex_string = concat_as_regex_string(&escaped_search_strings);
Regex::new(&*format!(r"\b{}\b", regex_string)).unwrap()
//log(&format!("Regex string: {}", regex_string));

Regex::new(&*format!(r"{}", regex_string)).unwrap()
}

/// Finds all link candidates in the provided note.
Expand Down
8 changes: 7 additions & 1 deletion src/rs/matching/regex_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use fancy_regex::Captures;

use crate::rs::Errors::cast_error::CastError;
use crate::rs::util::range::Range;
use crate::rs::util::wasm_util::log;

/// Utility struct that represents a single match of a regular expression in a note.
pub struct RegexMatch {
Expand All @@ -18,11 +19,16 @@ impl<'c> TryFrom<Captures<'c>> for RegexMatch {

fn try_from(captures: Captures) -> Result<Self, CastError> {
let valid = captures.iter()
// get index of capture group
.enumerate()
.find_map(|(i, c)| c.map(|c_| (c_, i)));
// filter out all capture groups that didn't match
.filter_map(|(i, c)| c.map(|c_| (c_, i)))
// pick the last match
.last();

match valid {
Some((m, capture_index)) => {
//log(&format!("Found match {} at index {}", m.as_str().to_string(), capture_index ));
Ok(
RegexMatch {
position: Range::new(m.start(), m.end()),
Expand Down
99 changes: 78 additions & 21 deletions src/rs/note/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ use std::convert::TryFrom;

use js_sys::Array;
use serde::{Deserialize, Serialize};
use unicode_segmentation::{Graphemes, UnicodeSegmentation};
use wasm_bindgen::{JsValue};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;

use crate::rs::text::text_util::create_string_with_n_characters;
use crate::rs::util::range::{array_to_range_vec, Range};
use crate::rs::util::wasm_util::{generic_of_jsval};
use crate::rs::util::wasm_util::generic_of_jsval;
use crate::rs::util::wasm_util::log;

/// A single note.
#[wasm_bindgen]
#[derive(Clone, Serialize, Deserialize)]
pub struct Note {
title: String, // the title of the note
path: String, // the path of the note
title: String, // the title of the note
path: String, // the path of the note
content: String, // the content of the note
#[serde(skip)]
aliases: Array, // possible aliases for the note
Expand All @@ -36,10 +36,16 @@ pub struct Note {
#[wasm_bindgen]
impl Note {
#[wasm_bindgen(constructor)]
pub fn new(title: String, path: String, content: String, aliases: Array, ignore: Array) -> Note {
pub fn new(
title: String,
path: String,
content: String,
aliases: Array,
ignore: Array,
) -> Note {
let ignore_vec = array_to_range_vec(ignore.clone());
Note {
title,
title: title,
path,
content: content.clone(),
aliases: aliases.clone(),
Expand All @@ -52,9 +58,13 @@ impl Note {
}

#[wasm_bindgen(getter)]
pub fn title(&self) -> String { self.title.clone() }
pub fn title(&self) -> String {
self.title.clone()
}
#[wasm_bindgen(getter)]
pub fn path(&self) -> String { self.path.clone() }
pub fn path(&self) -> String {
self.path.clone()
}
#[wasm_bindgen(getter)]
pub fn content(&self) -> String {
self.content.clone()
Expand Down Expand Up @@ -90,26 +100,72 @@ impl Note {
/// Cleans up the note content by:
/// - removing all parts of the content that are in the ignore list
fn sanitize_content(mut content: String, ignore_vec: Vec<Range>) -> String {
let mut _offset: usize = 0;
let mut _last_start: usize = 9999999;
// the ignore ranges are sorted
for ignore in ignore_vec {
if _last_start == ignore.start() {
// skip this range
continue;
} else {
_last_start = ignore.start();
}

let offset = _offset;
let range: std::ops::Range<usize> = ignore.clone().into();
let split_content: Graphemes = UnicodeSegmentation::graphemes(content.as_str(), true);
let split_content: Vec<u16> = content.encode_utf16().collect();
let mut new_content: String = String::new();
// necessary to split graphemes, since characters such as emojis
// only increase the character offset by 1 (offset is provided by obsidian api),
// however in rust strings, they have a length of 2-4
split_content.enumerate().for_each(
|(index, grapheme)| {
if range.start <= index && range.end >= index { new_content.push_str(&*create_string_with_n_characters(&grapheme.len(), '_')) } else { new_content.push_str(grapheme) }
}
);

let before_vec_utf_16 = split_content
.iter()
.take(range.start + offset)
.map(|c| c.clone())
.collect::<Vec<u16>>();

let before_string = String::from_utf16_lossy(before_vec_utf_16.as_slice());
new_content.push_str(&before_string);

let replacement_utf_16_vec = split_content
.iter()
.skip(range.start + offset)
.take(range.end - range.start)
.map(|c| c.clone())
.collect::<Vec<u16>>();

// for the content that should be ignored, we need to push a blank string
// with the *same* length as the utf-8 slice of the string that should be ignored
// if we were to simple push a string of the utf-16 length,
// the regex matching later on would produce false positions

// utf 16 length
let replacement_len_utf_16 = replacement_utf_16_vec.len();
// utf 8 length
let replacement_len_utf_8 =
String::from_utf16_lossy(replacement_utf_16_vec.as_slice()).len();
// now create a string with the same length as the placeholder, but no content
let placeholder_to_push = create_string_with_n_characters(replacement_len_utf_8, '_');
_offset += replacement_len_utf_8 - replacement_len_utf_16;
new_content.push_str(&placeholder_to_push);

let after_vec_utf_16 = split_content
.iter()
.skip(range.end + offset)
.map(|c| c.clone())
.collect::<Vec<u16>>();

let after_string = String::from_utf16_lossy(after_vec_utf_16.as_slice());
new_content.push_str(&after_string);

content = new_content
}

content
}

pub fn get_sanitized_content(&mut self) -> &String {
if self._sanitized_content.is_empty() {
self._sanitized_content = Note::sanitize_content(self.content.clone(), self._ignore.clone());
self._sanitized_content =
Note::sanitize_content(self.content.clone(), self._ignore.clone());
}
&self._sanitized_content
}
Expand All @@ -128,7 +184,8 @@ pub fn note_from_js_value(js: JsValue) -> Option<Note> {
}

pub fn array_to_string_vec(array: Array) -> Vec<String> {
array.iter()
array
.iter()
.filter_map(|a: JsValue| a.as_string())
.collect()
}
}
6 changes: 3 additions & 3 deletions src/rs/text/text_util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// Creates a string with n times character c.
pub fn create_string_with_n_characters(n: &usize, c: char) -> String {
pub fn create_string_with_n_characters(n: usize, c: char) -> String {
let mut s = String::new();
for _ in 0..*n {
for _ in 0..n {
s.push(c);
}
s
Expand All @@ -22,4 +22,4 @@ pub fn get_nearest_char_boundary(text: &str, position: usize, do_expand_left: bo
}
}
i
}
}
Loading

0 comments on commit a2dc073

Please sign in to comment.