diff --git a/.gitignore b/.gitignore index 51f142db..f56d30f6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ build /browser.js emsdk-portable package-lock.json +.vscode +/bin/ \ No newline at end of file diff --git a/binding.gyp b/binding.gyp index 249e9e82..0b6ce111 100644 --- a/binding.gyp +++ b/binding.gyp @@ -3,7 +3,7 @@ { "target_name": "superstring", "dependencies": [ - "superstring_core" + "superstring_core", ], "sources": [ "src/bindings/bindings.cc", @@ -18,8 +18,11 @@ "src/bindings/text-writer.cc", ], "include_dirs": [ - "src/core", - " exports) { - PointWrapper::init(); - RangeWrapper::init(); +Object Init(Env env, Object exports) { PatchWrapper::init(exports); MarkerIndexWrapper::init(exports); TextBufferWrapper::init(exports); TextWriter::init(exports); TextReader::init(exports); - TextBufferSnapshotWrapper::init(); + TextBufferSnapshotWrapper::init(env); + return exports; } -NODE_MODULE(superstring, Init) +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/bindings/marker-index-wrapper.cc b/src/bindings/marker-index-wrapper.cc index f53853e3..2021a1f8 100644 --- a/src/bindings/marker-index-wrapper.cc +++ b/src/bindings/marker-index-wrapper.cc @@ -1,137 +1,99 @@ -#include "marker-index-wrapper.h" #include + +#include "v8.h" +#include "napi.h" +#include "marker-index-wrapper.h" #include "marker-index.h" -#include "nan.h" -#include "noop.h" #include "optional.h" #include "point-wrapper.h" #include "range.h" +#include "util.h" -using namespace v8; +using namespace Napi; using std::unordered_map; -static Nan::Persistent marker_index_constructor_template; -static Nan::Persistent start_string; -static Nan::Persistent end_string; -static Nan::Persistent touch_string; -static Nan::Persistent inside_string; -static Nan::Persistent overlap_string; -static Nan::Persistent surround_string; -static Nan::Persistent containing_start_string; -static Nan::Persistent boundaries_string; -static Nan::Persistent position_string; -static Nan::Persistent starting_string; -static Nan::Persistent ending_string; - -void MarkerIndexWrapper::init(Local exports) { - Local constructor_template = Nan::New(construct); - constructor_template->SetClassName(Nan::New("MarkerIndex").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - - const auto &prototype_template = constructor_template->PrototypeTemplate(); - - Nan::SetTemplate(prototype_template, Nan::New("delete").ToLocalChecked(), Nan::New(noop), None); - Nan::SetTemplate(prototype_template, Nan::New("generateRandomNumber").ToLocalChecked(), - Nan::New(generate_random_number), None); - Nan::SetTemplate(prototype_template, Nan::New("insert").ToLocalChecked(), Nan::New(insert), None); - Nan::SetTemplate(prototype_template, Nan::New("setExclusive").ToLocalChecked(), Nan::New(set_exclusive), None); - Nan::SetTemplate(prototype_template, Nan::New("remove").ToLocalChecked(), Nan::New(remove), None); - Nan::SetTemplate(prototype_template, Nan::New("has").ToLocalChecked(), Nan::New(has), None); - Nan::SetTemplate(prototype_template, Nan::New("splice").ToLocalChecked(), Nan::New(splice), None); - Nan::SetTemplate(prototype_template, Nan::New("getStart").ToLocalChecked(), Nan::New(get_start), None); - Nan::SetTemplate(prototype_template, Nan::New("getEnd").ToLocalChecked(), Nan::New(get_end), None); - Nan::SetTemplate(prototype_template, Nan::New("getRange").ToLocalChecked(), Nan::New(get_range), None); - Nan::SetTemplate(prototype_template, Nan::New("compare").ToLocalChecked(), Nan::New(compare), None); - Nan::SetTemplate(prototype_template, Nan::New("findIntersecting").ToLocalChecked(), - Nan::New(find_intersecting), None); - Nan::SetTemplate(prototype_template, Nan::New("findContaining").ToLocalChecked(), - Nan::New(find_containing), None); - Nan::SetTemplate(prototype_template, Nan::New("findContainedIn").ToLocalChecked(), - Nan::New(find_contained_in), None); - Nan::SetTemplate(prototype_template, Nan::New("findStartingIn").ToLocalChecked(), - Nan::New(find_starting_in), None); - Nan::SetTemplate(prototype_template, Nan::New("findStartingAt").ToLocalChecked(), - Nan::New(find_starting_at), None); - Nan::SetTemplate(prototype_template, Nan::New("findEndingIn").ToLocalChecked(), Nan::New(find_ending_in), None); - Nan::SetTemplate(prototype_template, Nan::New("findEndingAt").ToLocalChecked(), Nan::New(find_ending_at), None); - Nan::SetTemplate(prototype_template, Nan::New("findBoundariesAfter").ToLocalChecked(), Nan::New(find_boundaries_after), None); - Nan::SetTemplate(prototype_template, Nan::New("dump").ToLocalChecked(), Nan::New(dump), None); - - start_string.Reset(Nan::Persistent(Nan::New("start").ToLocalChecked())); - end_string.Reset(Nan::Persistent(Nan::New("end").ToLocalChecked())); - touch_string.Reset(Nan::Persistent(Nan::New("touch").ToLocalChecked())); - inside_string.Reset(Nan::Persistent(Nan::New("inside").ToLocalChecked())); - overlap_string.Reset(Nan::Persistent(Nan::New("overlap").ToLocalChecked())); - surround_string.Reset(Nan::Persistent(Nan::New("surround").ToLocalChecked())); - containing_start_string.Reset(Nan::Persistent(Nan::New("containingStart").ToLocalChecked())); - boundaries_string.Reset(Nan::Persistent(Nan::New("boundaries").ToLocalChecked())); - position_string.Reset(Nan::Persistent(Nan::New("position").ToLocalChecked())); - starting_string.Reset(Nan::Persistent(Nan::New("starting").ToLocalChecked())); - ending_string.Reset(Nan::Persistent(Nan::New("ending").ToLocalChecked())); - - marker_index_constructor_template.Reset(constructor_template); - Nan::Set(exports, Nan::New("MarkerIndex").ToLocalChecked(), Nan::GetFunction(constructor_template).ToLocalChecked()); +FunctionReference MarkerIndexWrapper::constructor; + +void MarkerIndexWrapper::init(Object exports) { + Napi::Env env = exports.Env(); + Napi::Function func = DefineClass(env, "MarkerIndex", { + InstanceMethod("generateRandomNumber", &MarkerIndexWrapper::generate_random_number), + InstanceMethod("insert", &MarkerIndexWrapper::insert), + InstanceMethod("setExclusive", &MarkerIndexWrapper::set_exclusive), + InstanceMethod("remove", &MarkerIndexWrapper::remove), + InstanceMethod("has", &MarkerIndexWrapper::has), + InstanceMethod("splice", &MarkerIndexWrapper::splice), + InstanceMethod("getStart", &MarkerIndexWrapper::get_start), + InstanceMethod("getEnd", &MarkerIndexWrapper::get_end), + InstanceMethod("getRange", &MarkerIndexWrapper::get_range), + InstanceMethod("compare", &MarkerIndexWrapper::compare), + InstanceMethod("findIntersecting", &MarkerIndexWrapper::find_intersecting), + InstanceMethod("findContaining", &MarkerIndexWrapper::find_containing), + InstanceMethod("findContainedIn", &MarkerIndexWrapper::find_contained_in), + InstanceMethod("findStartingIn", &MarkerIndexWrapper::find_starting_in), + InstanceMethod("findStartingAt", &MarkerIndexWrapper::find_starting_at), + InstanceMethod("findEndingIn", &MarkerIndexWrapper::find_ending_in), + InstanceMethod("findEndingAt", &MarkerIndexWrapper::find_ending_at), + InstanceMethod("findBoundariesAfter", &MarkerIndexWrapper::find_boundaries_after), + InstanceMethod("dump", &MarkerIndexWrapper::dump), + }); + + constructor.Reset(func, 1); + exports.Set("MarkerIndex", func); } -MarkerIndex *MarkerIndexWrapper::from_js(Local value) { - auto js_marker_index = Local::Cast(value); - if (!Nan::New(marker_index_constructor_template)->HasInstance(js_marker_index)) { +MarkerIndex *MarkerIndexWrapper::from_js(Napi::Value value) { + if (!value.IsObject()) { return nullptr; } - return &Nan::ObjectWrap::Unwrap(js_marker_index)->marker_index; + + return Unwrap(value.As())->marker_index.get(); } -void MarkerIndexWrapper::construct(const Nan::FunctionCallbackInfo &info) { - auto seed = Nan::To(info[0]); - MarkerIndexWrapper *marker_index = new MarkerIndexWrapper(seed.IsJust() ? seed.FromJust() : 0u); - marker_index->Wrap(info.This()); +MarkerIndexWrapper::MarkerIndexWrapper(const CallbackInfo &info): ObjectWrap(info) { + unsigned seed = (info.Length() > 0 && info[0].IsNumber()) ? info[0].As().Uint32Value() : 0u; + marker_index.reset(new MarkerIndex(seed)); } -void MarkerIndexWrapper::generate_random_number(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - int random = wrapper->marker_index.generate_random_number(); - info.GetReturnValue().Set(Nan::New(random)); +Napi::Value MarkerIndexWrapper::generate_random_number(const CallbackInfo &info) { + auto env = info.Env(); + return Number::New(env, marker_index->generate_random_number()); } -Local MarkerIndexWrapper::marker_ids_set_to_js(const MarkerIndex::MarkerIdSet &marker_ids) { - Isolate *isolate = v8::Isolate::GetCurrent(); - Local context = isolate->GetCurrentContext(); - Local js_set = v8::Set::New(isolate); +Napi::Value MarkerIndexWrapper::marker_ids_set_to_js(const MarkerIndex::MarkerIdSet &marker_ids) { + v8::Isolate *isolate = v8::Isolate::GetCurrent(); + v8::Local context = isolate->GetCurrentContext(); + v8::Local js_set = v8::Set::New(isolate); for (MarkerIndex::MarkerId id : marker_ids) { - // Not sure why Set::Add warns if we don't use its return value, but - // just doing it to avoid the warning. - js_set = js_set->Add(context, Nan::New(id)).ToLocalChecked(); + (void)js_set->Add(context, v8::Integer::New(isolate, id)); } - return js_set; + return Napi::Value(Env(), JsValueFromV8LocalValue(js_set)); } -Local MarkerIndexWrapper::marker_ids_vector_to_js(const std::vector &marker_ids) { - Local js_array = Nan::New(marker_ids.size()); +Array MarkerIndexWrapper::marker_ids_vector_to_js(const std::vector &marker_ids) { + Array js_array = Array::New(Env(), marker_ids.size()); - Isolate *isolate = v8::Isolate::GetCurrent(); - Local context = isolate->GetCurrentContext(); for (size_t i = 0; i < marker_ids.size(); i++) { - js_array->Set(context, i, Nan::New(marker_ids[i])); + js_array[i] = marker_ids[i]; } return js_array; } -Local MarkerIndexWrapper::snapshot_to_js(const unordered_map &snapshot) { - Local result_object = Nan::New(); - Isolate *isolate = v8::Isolate::GetCurrent(); - Local context = isolate->GetCurrentContext(); +Object MarkerIndexWrapper::snapshot_to_js(const unordered_map &snapshot) { + auto env = Env(); + Object result_object = Object::New(env); for (auto &pair : snapshot) { - Local range = Nan::New(); - range->Set(context, Nan::New(start_string), PointWrapper::from_point(pair.second.start)); - range->Set(context, Nan::New(end_string), PointWrapper::from_point(pair.second.end)); - result_object->Set(context, Nan::New(pair.first), range); + Object range = Object::New(Env()); + range.Set("start", PointWrapper::from_point(env, pair.second.start)); + range.Set("end", PointWrapper::from_point(env, pair.second.end)); + result_object.Set(pair.first, range); } return result_object; } -optional MarkerIndexWrapper::marker_id_from_js(Local value) { +optional MarkerIndexWrapper::marker_id_from_js(Napi::Value value) { auto result = unsigned_from_js(value); if (result) { return *result; @@ -140,245 +102,254 @@ optional MarkerIndexWrapper::marker_id_from_js(Local MarkerIndexWrapper::unsigned_from_js(Local value) { - Nan::Maybe result = Nan::To(value); - if (!result.IsJust()) { - Nan::ThrowTypeError("Expected an non-negative integer value."); +optional MarkerIndexWrapper::unsigned_from_js(Napi::Value value) { + if (!value.IsNumber()) { + Error::New(Env(), "Expected an non-negative integer value.").ThrowAsJavaScriptException(); return optional{}; } - return result.FromJust(); + + return value.As().Uint32Value(); } -optional MarkerIndexWrapper::bool_from_js(Local value) { - Nan::MaybeLocal maybe_boolean = Nan::To(value); - Local boolean; - if (!maybe_boolean.ToLocal(&boolean)) { - Nan::ThrowTypeError("Expected an boolean."); +optional MarkerIndexWrapper::bool_from_js(Napi::Value value) { + if (!value.IsBoolean()) { + Error::New(Env(), "Expected an boolean.").ThrowAsJavaScriptException(); return optional{}; } - return boolean->Value(); + return value.As().Value(); } -void MarkerIndexWrapper::insert(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - +void MarkerIndexWrapper::insert(const CallbackInfo &info) { optional id = marker_id_from_js(info[0]); + if (!id) return; + optional start = PointWrapper::point_from_js(info[1]); + if (!start) return; + optional end = PointWrapper::point_from_js(info[2]); + if (!end) return; - if (id && start && end) { - wrapper->marker_index.insert(*id, *start, *end); - } + this->marker_index->insert(*id, *start, *end); } -void MarkerIndexWrapper::set_exclusive(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - +void MarkerIndexWrapper::set_exclusive(const CallbackInfo &info) { optional id = marker_id_from_js(info[0]); optional exclusive = bool_from_js(info[1]); if (id && exclusive) { - wrapper->marker_index.set_exclusive(*id, *exclusive); + this->marker_index->set_exclusive(*id, *exclusive); } } -void MarkerIndexWrapper::remove(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - +void MarkerIndexWrapper::remove(const CallbackInfo &info) { optional id = marker_id_from_js(info[0]); if (id) { - wrapper->marker_index.remove(*id); + this->marker_index->remove(*id); } } -void MarkerIndexWrapper::has(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - +Napi::Value MarkerIndexWrapper::has(const CallbackInfo &info) { + auto env = info.Env(); optional id = marker_id_from_js(info[0]); if (id) { - bool result = wrapper->marker_index.has(*id); - info.GetReturnValue().Set(Nan::New(result)); + bool result = this->marker_index->has(*id); + return Boolean::New(env, result); } -} -void MarkerIndexWrapper::splice(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); + return env.Undefined(); +} +Napi::Value MarkerIndexWrapper::splice(const CallbackInfo &info) { + auto env = info.Env(); optional start = PointWrapper::point_from_js(info[0]); optional old_extent = PointWrapper::point_from_js(info[1]); optional new_extent = PointWrapper::point_from_js(info[2]); if (start && old_extent && new_extent) { - MarkerIndex::SpliceResult result = wrapper->marker_index.splice(*start, *old_extent, *new_extent); - - Local invalidated = Nan::New(); - Nan::Set(invalidated, Nan::New(touch_string), marker_ids_set_to_js(result.touch)); - Nan::Set(invalidated, Nan::New(inside_string), marker_ids_set_to_js(result.inside)); - Nan::Set(invalidated, Nan::New(inside_string), marker_ids_set_to_js(result.inside)); - Nan::Set(invalidated, Nan::New(overlap_string), marker_ids_set_to_js(result.overlap)); - Nan::Set(invalidated, Nan::New(surround_string), marker_ids_set_to_js(result.surround)); - info.GetReturnValue().Set(invalidated); + MarkerIndex::SpliceResult result = this->marker_index->splice(*start, *old_extent, *new_extent); + + Object invalidated = Object::New(env); + invalidated.Set("touch", marker_ids_set_to_js(result.touch)); + invalidated.Set("inside", marker_ids_set_to_js(result.inside)); + invalidated.Set("inside", marker_ids_set_to_js(result.inside)); + invalidated.Set("overlap", marker_ids_set_to_js(result.overlap)); + invalidated.Set("surround", marker_ids_set_to_js(result.surround)); + return invalidated; } + + return env.Undefined(); } -void MarkerIndexWrapper::get_start(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::get_start(const CallbackInfo &info) { + auto env = Env(); optional id = marker_id_from_js(info[0]); if (id) { - Point result = wrapper->marker_index.get_start(*id); - info.GetReturnValue().Set(PointWrapper::from_point(result)); + Point result = this->marker_index->get_start(*id); + return PointWrapper::from_point(env, result); } + + return env.Undefined(); } -void MarkerIndexWrapper::get_end(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::get_end(const CallbackInfo &info) { + auto env = Env(); optional id = marker_id_from_js(info[0]); if (id) { - Point result = wrapper->marker_index.get_end(*id); - info.GetReturnValue().Set(PointWrapper::from_point(result)); + Point result = this->marker_index->get_end(*id); + return PointWrapper::from_point(env, result); } + + return env.Undefined(); } -void MarkerIndexWrapper::get_range(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::get_range(const CallbackInfo &info) { + auto env = Env(); optional id = marker_id_from_js(info[0]); if (id) { - Range range = wrapper->marker_index.get_range(*id); - auto result = Nan::New(); - Nan::Set(result, Nan::New(start_string), PointWrapper::from_point(range.start)); - Nan::Set(result, Nan::New(end_string), PointWrapper::from_point(range.end)); - info.GetReturnValue().Set(result); + Range range = this->marker_index->get_range(*id); + auto result = Object::New(env); + result.Set("start", PointWrapper::from_point(env, range.start)); + result.Set("end", PointWrapper::from_point(env, range.end)); + return result; } + + return env.Undefined(); } -void MarkerIndexWrapper::compare(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::compare(const CallbackInfo &info) { + auto env = info.Env(); optional id1 = marker_id_from_js(info[0]); optional id2 = marker_id_from_js(info[1]); if (id1 && id2) { - info.GetReturnValue().Set(wrapper->marker_index.compare(*id1, *id2)); + return Number::New(env, this->marker_index->compare(*id1, *id2)); } + + return env.Undefined(); } -void MarkerIndexWrapper::find_intersecting(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::find_intersecting(const CallbackInfo &info) { + auto env = info.Env(); optional start = PointWrapper::point_from_js(info[0]); optional end = PointWrapper::point_from_js(info[1]); if (start && end) { - MarkerIndex::MarkerIdSet result = wrapper->marker_index.find_intersecting(*start, *end); - info.GetReturnValue().Set(marker_ids_set_to_js(result)); + MarkerIndex::MarkerIdSet result = this->marker_index->find_intersecting(*start, *end); + return marker_ids_set_to_js(result); } + + return env.Undefined(); } -void MarkerIndexWrapper::find_containing(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::find_containing(const CallbackInfo &info) { + auto env = info.Env(); optional start = PointWrapper::point_from_js(info[0]); optional end = PointWrapper::point_from_js(info[1]); if (start && end) { - MarkerIndex::MarkerIdSet result = wrapper->marker_index.find_containing(*start, *end); - info.GetReturnValue().Set(marker_ids_set_to_js(result)); + MarkerIndex::MarkerIdSet result = this->marker_index->find_containing(*start, *end); + return marker_ids_set_to_js(result); } + return env.Undefined(); } -void MarkerIndexWrapper::find_contained_in(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::find_contained_in(const CallbackInfo &info) { + auto env = info.Env(); optional start = PointWrapper::point_from_js(info[0]); optional end = PointWrapper::point_from_js(info[1]); if (start && end) { - MarkerIndex::MarkerIdSet result = wrapper->marker_index.find_contained_in(*start, *end); - info.GetReturnValue().Set(marker_ids_set_to_js(result)); + MarkerIndex::MarkerIdSet result = this->marker_index->find_contained_in(*start, *end); + return marker_ids_set_to_js(result); } + return env.Undefined(); } -void MarkerIndexWrapper::find_starting_in(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::find_starting_in(const CallbackInfo &info) { + auto env = info.Env(); optional start = PointWrapper::point_from_js(info[0]); optional end = PointWrapper::point_from_js(info[1]); if (start && end) { - MarkerIndex::MarkerIdSet result = wrapper->marker_index.find_starting_in(*start, *end); - info.GetReturnValue().Set(marker_ids_set_to_js(result)); + MarkerIndex::MarkerIdSet result = this->marker_index->find_starting_in(*start, *end); + return marker_ids_set_to_js(result); } + return env.Undefined(); } -void MarkerIndexWrapper::find_starting_at(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::find_starting_at(const CallbackInfo &info) { + auto env = info.Env(); optional position = PointWrapper::point_from_js(info[0]); if (position) { - MarkerIndex::MarkerIdSet result = wrapper->marker_index.find_starting_at(*position); - info.GetReturnValue().Set(marker_ids_set_to_js(result)); + MarkerIndex::MarkerIdSet result = this->marker_index->find_starting_at(*position); + return marker_ids_set_to_js(result); } + return env.Undefined(); } -void MarkerIndexWrapper::find_ending_in(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::find_ending_in(const CallbackInfo &info) { + auto env = info.Env(); optional start = PointWrapper::point_from_js(info[0]); optional end = PointWrapper::point_from_js(info[1]); if (start && end) { - MarkerIndex::MarkerIdSet result = wrapper->marker_index.find_ending_in(*start, *end); - info.GetReturnValue().Set(marker_ids_set_to_js(result)); + MarkerIndex::MarkerIdSet result = this->marker_index->find_ending_in(*start, *end); + return marker_ids_set_to_js(result); } + return env.Undefined(); } -void MarkerIndexWrapper::find_ending_at(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value MarkerIndexWrapper::find_ending_at(const CallbackInfo &info) { + auto env = info.Env(); optional position = PointWrapper::point_from_js(info[0]); if (position) { - MarkerIndex::MarkerIdSet result = wrapper->marker_index.find_ending_at(*position); - info.GetReturnValue().Set(marker_ids_set_to_js(result)); + MarkerIndex::MarkerIdSet result = this->marker_index->find_ending_at(*position); + return marker_ids_set_to_js(result); } + return env.Undefined(); } -void MarkerIndexWrapper::find_boundaries_after(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - +Napi::Value MarkerIndexWrapper::find_boundaries_after(const CallbackInfo &info) { + auto env = info.Env(); optional start = PointWrapper::point_from_js(info[0]); optional max_count; - Local js_max_count; - if (Nan::To(info[1]).ToLocal(&js_max_count)) { - max_count = Nan::To(js_max_count).FromMaybe(0); + if (info[1].IsNumber()) { + max_count = info[1].As().Uint32Value(); } if (start && max_count) { - MarkerIndex::BoundaryQueryResult result = wrapper->marker_index.find_boundaries_after(*start, *max_count); - Local js_result = Nan::New(); - Nan::Set(js_result, Nan::New(containing_start_string), marker_ids_vector_to_js(result.containing_start)); + MarkerIndex::BoundaryQueryResult result = this->marker_index->find_boundaries_after(*start, *max_count); + Object js_result = Object::New(env); + js_result.Set("containingStart", marker_ids_vector_to_js(result.containing_start)); - Local js_boundaries = Nan::New(result.boundaries.size()); + Array js_boundaries = Array::New(env, result.boundaries.size()); for (size_t i = 0; i < result.boundaries.size(); i++) { MarkerIndex::Boundary boundary = result.boundaries[i]; - Local js_boundary = Nan::New(); - Nan::Set(js_boundary, Nan::New(position_string), PointWrapper::from_point(boundary.position)); - Nan::Set(js_boundary, Nan::New(starting_string), marker_ids_set_to_js(boundary.starting)); - Nan::Set(js_boundary, Nan::New(ending_string), marker_ids_set_to_js(boundary.ending)); - Nan::Set(js_boundaries, i, js_boundary); + Object js_boundary = Object::New(env); + js_boundary.Set("position", PointWrapper::from_point(env, boundary.position)); + js_boundary.Set("starting", marker_ids_set_to_js(boundary.starting)); + js_boundary.Set("ending", marker_ids_set_to_js(boundary.ending)); + js_boundaries[i] = js_boundary; } - Nan::Set(js_result, Nan::New(boundaries_string), js_boundaries); + js_result.Set("boundaries", js_boundaries); - info.GetReturnValue().Set(js_result); + return js_result; } + return env.Undefined(); } -void MarkerIndexWrapper::dump(const Nan::FunctionCallbackInfo &info) { - MarkerIndexWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - unordered_map snapshot = wrapper->marker_index.dump(); - info.GetReturnValue().Set(snapshot_to_js(snapshot)); +Napi::Value MarkerIndexWrapper::dump(const CallbackInfo &info) { + unordered_map snapshot = this->marker_index->dump(); + return snapshot_to_js(snapshot); } - -MarkerIndexWrapper::MarkerIndexWrapper(unsigned seed) : marker_index{seed} {} diff --git a/src/bindings/marker-index-wrapper.h b/src/bindings/marker-index-wrapper.h index 2f64cc54..0bc476ec 100644 --- a/src/bindings/marker-index-wrapper.h +++ b/src/bindings/marker-index-wrapper.h @@ -1,41 +1,45 @@ -#include "nan.h" +#pragma once + +#include "napi.h" #include "marker-index.h" #include "optional.h" #include "range.h" -class MarkerIndexWrapper : public Nan::ObjectWrap { +class MarkerIndexWrapper : public Napi::ObjectWrap { public: - static void init(v8::Local exports); - static MarkerIndex *from_js(v8::Local); + static void init(Napi::Object exports); + static MarkerIndex *from_js(Napi::Value); + + MarkerIndexWrapper(const Napi::CallbackInfo &info); private: - static void construct(const Nan::FunctionCallbackInfo &info); - static void generate_random_number(const Nan::FunctionCallbackInfo &info); - static bool is_finite(v8::Local number); - static v8::Local marker_ids_set_to_js(const MarkerIndex::MarkerIdSet &marker_ids); - static v8::Local marker_ids_vector_to_js(const std::vector &marker_ids); - static v8::Local snapshot_to_js(const std::unordered_map &snapshot); - static optional marker_id_from_js(v8::Local value); - static optional unsigned_from_js(v8::Local value); - static optional bool_from_js(v8::Local value); - static void insert(const Nan::FunctionCallbackInfo &info); - static void set_exclusive(const Nan::FunctionCallbackInfo &info); - static void remove(const Nan::FunctionCallbackInfo &info); - static void has(const Nan::FunctionCallbackInfo &info); - static void splice(const Nan::FunctionCallbackInfo &info); - static void get_start(const Nan::FunctionCallbackInfo &info); - static void get_end(const Nan::FunctionCallbackInfo &info); - static void get_range(const Nan::FunctionCallbackInfo &info); - static void compare(const Nan::FunctionCallbackInfo &info); - static void find_intersecting(const Nan::FunctionCallbackInfo &info); - static void find_containing(const Nan::FunctionCallbackInfo &info); - static void find_contained_in(const Nan::FunctionCallbackInfo &info); - static void find_starting_in(const Nan::FunctionCallbackInfo &info); - static void find_starting_at(const Nan::FunctionCallbackInfo &info); - static void find_ending_in(const Nan::FunctionCallbackInfo &info); - static void find_ending_at(const Nan::FunctionCallbackInfo &info); - static void find_boundaries_after(const Nan::FunctionCallbackInfo &info); - static void dump(const Nan::FunctionCallbackInfo &info); - MarkerIndexWrapper(unsigned seed); - MarkerIndex marker_index; + Napi::Value generate_random_number(const Napi::CallbackInfo &info); + bool is_finite(Napi::Number number); + Napi::Value marker_ids_set_to_js(const MarkerIndex::MarkerIdSet &marker_ids); + Napi::Array marker_ids_vector_to_js(const std::vector &marker_ids); + Napi::Object snapshot_to_js(const std::unordered_map &snapshot); + optional marker_id_from_js(Napi::Value value); + optional unsigned_from_js(Napi::Value value); + optional bool_from_js(Napi::Value value); + void insert(const Napi::CallbackInfo &info); + void set_exclusive(const Napi::CallbackInfo &info); + void remove(const Napi::CallbackInfo &info); + Napi::Value has(const Napi::CallbackInfo &info); + Napi::Value splice(const Napi::CallbackInfo &info); + Napi::Value get_start(const Napi::CallbackInfo &info); + Napi::Value get_end(const Napi::CallbackInfo &info); + Napi::Value get_range(const Napi::CallbackInfo &info); + Napi::Value compare(const Napi::CallbackInfo &info); + Napi::Value find_intersecting(const Napi::CallbackInfo &info); + Napi::Value find_containing(const Napi::CallbackInfo &info); + Napi::Value find_contained_in(const Napi::CallbackInfo &info); + Napi::Value find_starting_in(const Napi::CallbackInfo &info); + Napi::Value find_starting_at(const Napi::CallbackInfo &info); + Napi::Value find_ending_in(const Napi::CallbackInfo &info); + Napi::Value find_ending_at(const Napi::CallbackInfo &info); + Napi::Value find_boundaries_after(const Napi::CallbackInfo &info); + Napi::Value dump(const Napi::CallbackInfo &info); + + std::unique_ptr marker_index; + static Napi::FunctionReference constructor; }; diff --git a/src/bindings/noop.h b/src/bindings/noop.h deleted file mode 100644 index 6b446a5a..00000000 --- a/src/bindings/noop.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "nan.h" - -static void noop(const Nan::FunctionCallbackInfo&) {} diff --git a/src/bindings/number-conversion.h b/src/bindings/number-conversion.h index cc063be9..31caf35a 100644 --- a/src/bindings/number-conversion.h +++ b/src/bindings/number-conversion.h @@ -1,18 +1,15 @@ #ifndef SUPERSTRING_NUMBER_CONVERSION_H #define SUPERSTRING_NUMBER_CONVERSION_H -#include "nan.h" +#include "napi.h" #include "optional.h" namespace number_conversion { template - optional number_from_js(v8::Local js_value) { - v8::Local js_number; - if (Nan::To(js_value).ToLocal(&js_number)) { - auto maybe_number = Nan::To(js_number); - if (maybe_number.IsJust()) { - return maybe_number.FromJust(); - } + optional number_from_js(Napi::Value js_value) { + if (js_value.IsNumber()) { + Napi::Number js_number = js_value.As(); + return static_cast(js_number); } return optional{}; } diff --git a/src/bindings/patch-wrapper.cc b/src/bindings/patch-wrapper.cc index fc610aaf..78f709f9 100644 --- a/src/bindings/patch-wrapper.cc +++ b/src/bindings/patch-wrapper.cc @@ -1,176 +1,126 @@ -#include "noop.h" -#include "patch-wrapper.h" #include #include #include + +#include "patch-wrapper.h" #include "point-wrapper.h" #include "string-conversion.h" -using namespace v8; +using namespace Napi; using std::move; using std::vector; using std::u16string; -static Nan::Persistent new_text_string; -static Nan::Persistent old_text_string; -static Nan::Persistent change_wrapper_constructor; -static Nan::Persistent patch_wrapper_constructor_template; -static Nan::Persistent patch_wrapper_constructor; - static const char *InvalidSpliceMessage = "Patch does not apply"; -class ChangeWrapper : public Nan::ObjectWrap { - public: - static void init() { - new_text_string.Reset(Nan::New("newText").ToLocalChecked()); - old_text_string.Reset(Nan::New("oldText").ToLocalChecked()); - static Nan::Persistent old_text_string; - - Local constructor_template = Nan::New(construct); - constructor_template->SetClassName(Nan::New("Change").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - const auto &instance_template = constructor_template->InstanceTemplate(); - Nan::SetAccessor(instance_template, Nan::New("oldStart").ToLocalChecked(), get_old_start); - Nan::SetAccessor(instance_template, Nan::New("newStart").ToLocalChecked(), get_new_start); - Nan::SetAccessor(instance_template, Nan::New("oldEnd").ToLocalChecked(), get_old_end); - Nan::SetAccessor(instance_template, Nan::New("newEnd").ToLocalChecked(), get_new_end); - - const auto &prototype_template = constructor_template->PrototypeTemplate(); - Nan::SetTemplate(prototype_template, Nan::New("toString").ToLocalChecked(), Nan::New(to_string), None); - change_wrapper_constructor.Reset(Nan::GetFunction(constructor_template).ToLocalChecked()); - } - - static Local FromChange(Patch::Change change) { - Local result; - if (Nan::NewInstance(Nan::New(change_wrapper_constructor)).ToLocal(&result)) { - (new ChangeWrapper(change))->Wrap(result); - if (change.new_text) { - Nan::Set( - result, - Nan::New(new_text_string), - string_conversion::string_to_js(change.new_text->content) - ); - } - if (change.old_text) { - Nan::Set( - result, - Nan::New(old_text_string), - string_conversion::string_to_js(change.old_text->content) - ); - } - return result; - } else { - return Nan::Null(); - } - } - - private: - ChangeWrapper(Patch::Change change) : change(change) {} - - static void construct(const Nan::FunctionCallbackInfo &info) {} +FunctionReference PatchWrapper::constructor; - static void get_old_start(v8::Local property, const Nan::PropertyCallbackInfo &info) { - Patch::Change &change = Nan::ObjectWrap::Unwrap(info.This())->change; - info.GetReturnValue().Set(PointWrapper::from_point(change.old_start)); - } - - static void get_new_start(v8::Local property, const Nan::PropertyCallbackInfo &info) { - Patch::Change &change = Nan::ObjectWrap::Unwrap(info.This())->change; - info.GetReturnValue().Set(PointWrapper::from_point(change.new_start)); - } +class ChangeWrapper : public ObjectWrap { + public: + static void init(Napi::Env env) { + Function func = DefineClass(env, "Change", { + InstanceMethod<&ChangeWrapper::to_string>("toString"), + }); - static void get_old_end(v8::Local property, const Nan::PropertyCallbackInfo &info) { - Patch::Change &change = Nan::ObjectWrap::Unwrap(info.This())->change; - info.GetReturnValue().Set(PointWrapper::from_point(change.old_end)); + constructor.Reset(func, 1); } - static void get_new_end(v8::Local property, const Nan::PropertyCallbackInfo &info) { - Patch::Change &change = Nan::ObjectWrap::Unwrap(info.This())->change; - info.GetReturnValue().Set(PointWrapper::from_point(change.new_end)); + static Napi::Value FromChange(Napi::Env env, Patch::Change change) { + auto wrapper = External::New(env, &change); + Napi::Object js_change_wrapper = constructor.New({wrapper}); + return js_change_wrapper; } - static void get_preceding_old_text_length(v8::Local property, const Nan::PropertyCallbackInfo &info) { - Patch::Change &change = Nan::ObjectWrap::Unwrap(info.This())->change; - info.GetReturnValue().Set(Nan::New(change.preceding_old_text_size)); - } + ChangeWrapper(const CallbackInfo &info): ObjectWrap(info) { + auto env = info.Env(); + if (info[0].IsExternal()) { + auto js_wrapper = info[0].As>(); + change = *js_wrapper.Data(); + Object js_change = info.This().As(); + js_change.Set("oldStart", PointWrapper::from_point(env, change.old_start)); + js_change.Set("newStart", PointWrapper::from_point(env, change.new_start)); + js_change.Set("oldEnd", PointWrapper::from_point(env, change.old_end)); + js_change.Set("newEnd", PointWrapper::from_point(env, change.new_end)); - static void get_preceding_new_text_length(v8::Local property, const Nan::PropertyCallbackInfo &info) { - Patch::Change &change = Nan::ObjectWrap::Unwrap(info.This())->change; - info.GetReturnValue().Set(Nan::New(change.preceding_new_text_size)); + if (change.new_text) { + js_change.Set("newText", string_conversion::string_to_js(env, change.new_text->content)); + js_change.Set("oldText", string_conversion::string_to_js(env, change.old_text->content)); + } + } } - static void to_string(const Nan::FunctionCallbackInfo &info) { - Patch::Change &change = Nan::ObjectWrap::Unwrap(info.This())->change; + private: + Napi::Value to_string(const CallbackInfo &info) { std::stringstream result; - result << change; - info.GetReturnValue().Set(Nan::New(result.str()).ToLocalChecked()); + result << this->change; + return String::New(Env(), result.str()); } Patch::Change change; + static FunctionReference constructor; }; -void PatchWrapper::init(Local exports) { - ChangeWrapper::init(); - - Local constructor_template_local = Nan::New(construct); - constructor_template_local->SetClassName(Nan::New("Patch").ToLocalChecked()); - Nan::SetTemplate(constructor_template_local, Nan::New("deserialize").ToLocalChecked(), Nan::New(deserialize), None); - Nan::SetTemplate(constructor_template_local, Nan::New("compose").ToLocalChecked(), Nan::New(compose), None); - constructor_template_local->InstanceTemplate()->SetInternalFieldCount(1); - const auto &prototype_template = constructor_template_local->PrototypeTemplate(); - Nan::SetTemplate(prototype_template, Nan::New("delete").ToLocalChecked(), Nan::New(noop), None); - Nan::SetTemplate(prototype_template, Nan::New("splice").ToLocalChecked(), Nan::New(splice), None); - Nan::SetTemplate(prototype_template, Nan::New("spliceOld").ToLocalChecked(), Nan::New(splice_old), None); - Nan::SetTemplate(prototype_template, Nan::New("copy").ToLocalChecked(), Nan::New(copy), None); - Nan::SetTemplate(prototype_template, Nan::New("invert").ToLocalChecked(), Nan::New(invert), None); - Nan::SetTemplate(prototype_template, Nan::New("getChanges").ToLocalChecked(), Nan::New(get_changes), None); - Nan::SetTemplate(prototype_template, Nan::New("getChangesInOldRange").ToLocalChecked(), - Nan::New(get_changes_in_old_range), None); - Nan::SetTemplate(prototype_template, Nan::New("getChangesInNewRange").ToLocalChecked(), - Nan::New(get_changes_in_new_range), None); - Nan::SetTemplate(prototype_template, Nan::New("changeForOldPosition").ToLocalChecked(), - Nan::New(change_for_old_position), None); - Nan::SetTemplate(prototype_template, Nan::New("changeForNewPosition").ToLocalChecked(), - Nan::New(change_for_new_position), None); - Nan::SetTemplate(prototype_template, Nan::New("serialize").ToLocalChecked(), Nan::New(serialize), None); - Nan::SetTemplate(prototype_template, Nan::New("getDotGraph").ToLocalChecked(), Nan::New(get_dot_graph), None); - Nan::SetTemplate(prototype_template, Nan::New("getJSON").ToLocalChecked(), Nan::New(get_json), None); - Nan::SetTemplate(prototype_template, Nan::New("rebalance").ToLocalChecked(), Nan::New(rebalance), None); - Nan::SetTemplate(prototype_template, Nan::New("getChangeCount").ToLocalChecked(), Nan::New(get_change_count), None); - Nan::SetTemplate(prototype_template, Nan::New("getBounds").ToLocalChecked(), Nan::New(get_bounds), None); - patch_wrapper_constructor_template.Reset(constructor_template_local); - patch_wrapper_constructor.Reset(Nan::GetFunction(constructor_template_local).ToLocalChecked()); - Nan::Set(exports, Nan::New("Patch").ToLocalChecked(), Nan::New(patch_wrapper_constructor)); +FunctionReference ChangeWrapper::constructor; + +void PatchWrapper::init(Object exports) { + auto env = exports.Env(); + ChangeWrapper::init(env); + + Function func = DefineClass(env, "Patch", { + StaticMethod<&PatchWrapper::deserialize>("deserialize"), + StaticMethod<&PatchWrapper::compose>("compose"), + InstanceMethod<&PatchWrapper::splice>("splice"), + InstanceMethod<&PatchWrapper::splice_old>("spliceOld"), + InstanceMethod<&PatchWrapper::copy>("copy"), + InstanceMethod<&PatchWrapper::invert>("invert"), + InstanceMethod<&PatchWrapper::get_changes>("getChanges"), + InstanceMethod<&PatchWrapper::get_changes_in_old_range>("getChangesInOldRange"), + InstanceMethod<&PatchWrapper::get_changes_in_new_range>("getChangesInNewRange"), + InstanceMethod<&PatchWrapper::change_for_old_position>("changeForOldPosition"), + InstanceMethod<&PatchWrapper::change_for_new_position>("changeForNewPosition"), + InstanceMethod<&PatchWrapper::serialize>("serialize"), + InstanceMethod<&PatchWrapper::get_dot_graph>("getDotGraph"), + InstanceMethod<&PatchWrapper::get_json>("getJSON"), + InstanceMethod<&PatchWrapper::rebalance>("rebalance"), + InstanceMethod<&PatchWrapper::get_change_count>("getChangeCount"), + InstanceMethod<&PatchWrapper::get_bounds>("getBounds"), + }); + + constructor.Reset(func, 1); + exports.Set("Patch", func); } -PatchWrapper::PatchWrapper(Patch &&patch) : patch{std::move(patch)} {} +Napi::Value PatchWrapper::from_patch(Napi::Env env, Patch &&patch) { + auto wrapper = External::New(env, &patch); + Napi::Object js_patch = constructor.New({wrapper}); -Local PatchWrapper::from_patch(Patch &&patch) { - Local result; - if (Nan::NewInstance(Nan::New(patch_wrapper_constructor)).ToLocal(&result)) { - (new PatchWrapper(move(patch)))->Wrap(result); - return result; - } else { - return Nan::Null(); - } + return js_patch; } -void PatchWrapper::construct(const Nan::FunctionCallbackInfo &info) { +PatchWrapper::PatchWrapper(const CallbackInfo &info): ObjectWrap(info) { + if (info[0].IsExternal()) { + auto patch = info[0].As>(); + this->patch = move(*patch.Data()); + return; + } + bool merges_adjacent_changes = true; - Local options; - if (info.Length() > 0 && Nan::To(info[0]).ToLocal(&options)) { - Local js_merge_adjacent_changes; - if (Nan::Get(options, Nan::New("mergeAdjacentChanges").ToLocalChecked()).ToLocal(&js_merge_adjacent_changes)) { - merges_adjacent_changes = Nan::To(js_merge_adjacent_changes).FromMaybe(false); + if (info[0].IsObject()) { + Object options = info[0].As(); + if (options.Has("mergeAdjacentChanges")) { + Napi::Value js_merge_adjacent_changes = options.Get("mergeAdjacentChanges"); + if (js_merge_adjacent_changes.IsBoolean()) { + merges_adjacent_changes = js_merge_adjacent_changes.As(); + } } } - PatchWrapper *patch = new PatchWrapper(Patch{merges_adjacent_changes}); - patch->Wrap(info.This()); + + patch = Patch{merges_adjacent_changes}; } -void PatchWrapper::splice(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +void PatchWrapper::splice(const CallbackInfo &info) { + Patch &patch = this->patch; optional start = PointWrapper::point_from_js(info[0]); optional deletion_extent = PointWrapper::point_from_js(info[1]); @@ -199,13 +149,13 @@ void PatchWrapper::splice(const Nan::FunctionCallbackInfo &info) { move(deleted_text), move(inserted_text) )) { - Nan::ThrowError(InvalidSpliceMessage); + Error::New(Env(), InvalidSpliceMessage).ThrowAsJavaScriptException(); } } } -void PatchWrapper::splice_old(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +void PatchWrapper::splice_old(const CallbackInfo &info) { + Patch &patch = this->patch; optional start = PointWrapper::point_from_js(info[0]); optional deletion_extent = PointWrapper::point_from_js(info[1]); @@ -216,195 +166,185 @@ void PatchWrapper::splice_old(const Nan::FunctionCallbackInfo &info) { } } -void PatchWrapper::copy(const Nan::FunctionCallbackInfo &info) { - Local result; - if (Nan::NewInstance(Nan::New(patch_wrapper_constructor)).ToLocal(&result)) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; - auto wrapper = new PatchWrapper{patch.copy()}; - wrapper->Wrap(result); - info.GetReturnValue().Set(result); - } +Napi::Value PatchWrapper::copy(const CallbackInfo &info) { + return from_patch(info.Env(), patch.copy()); } -void PatchWrapper::invert(const Nan::FunctionCallbackInfo &info) { - Local result; - if (Nan::NewInstance(Nan::New(patch_wrapper_constructor)).ToLocal(&result)) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; - auto wrapper = new PatchWrapper{patch.invert()}; - wrapper->Wrap(result); - info.GetReturnValue().Set(result); - } +Napi::Value PatchWrapper::invert(const CallbackInfo &info) { + return from_patch(info.Env(), patch.invert()); } -void PatchWrapper::get_changes(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::get_changes(const CallbackInfo &info) { + Napi::Env env = info.Env(); + + Patch &patch = this->patch; - Local js_result = Nan::New(); + Array js_result = Array::New(env); size_t i = 0; for (auto change : patch.get_changes()) { - Nan::Set(js_result, i++, ChangeWrapper::FromChange(change)); + js_result[i++] = ChangeWrapper::FromChange(env, change); } - info.GetReturnValue().Set(js_result); + return js_result; } -void PatchWrapper::get_changes_in_old_range(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::get_changes_in_old_range(const CallbackInfo &info) { + Napi::Env env = info.Env(); + Patch &patch = this->patch; optional start = PointWrapper::point_from_js(info[0]); optional end = PointWrapper::point_from_js(info[1]); if (start && end) { - Local js_result = Nan::New(); + Array js_result = Array::New(env); size_t i = 0; for (auto change : patch.grab_changes_in_old_range(*start, *end)) { - Nan::Set(js_result, i++, ChangeWrapper::FromChange(change)); + js_result[i++] = ChangeWrapper::FromChange(env, change); } - info.GetReturnValue().Set(js_result); + return js_result; } + + return env.Undefined(); } -void PatchWrapper::get_changes_in_new_range(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::get_changes_in_new_range(const CallbackInfo &info) { + Napi::Env env = info.Env(); + Patch &patch = this->patch; optional start = PointWrapper::point_from_js(info[0]); optional end = PointWrapper::point_from_js(info[1]); if (start && end) { - Local js_result = Nan::New(); + Array js_result = Array::New(env); size_t i = 0; for (auto change : patch.grab_changes_in_new_range(*start, *end)) { - Nan::Set(js_result, i++, ChangeWrapper::FromChange(change)); + js_result[i++] = ChangeWrapper::FromChange(env, change); } - info.GetReturnValue().Set(js_result); + return js_result; } + + return env.Undefined(); } -void PatchWrapper::change_for_old_position(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::change_for_old_position(const CallbackInfo &info) { + Napi::Env env = info.Env(); + Patch &patch = this->patch; optional start = PointWrapper::point_from_js(info[0]); if (start) { auto change = patch.grab_change_starting_before_old_position(*start); if (change) { - info.GetReturnValue().Set(ChangeWrapper::FromChange(*change)); - } else { - info.GetReturnValue().Set(Nan::Undefined()); + return ChangeWrapper::FromChange(env, *change); } } + + return env.Undefined(); } -void PatchWrapper::change_for_new_position(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::change_for_new_position(const CallbackInfo &info) { + Napi::Env env = info.Env(); + Patch &patch = this->patch; optional start = PointWrapper::point_from_js(info[0]); if (start) { auto change = patch.grab_change_starting_before_new_position(*start); if (change) { - info.GetReturnValue().Set(ChangeWrapper::FromChange(*change)); - } else { - info.GetReturnValue().Set(Nan::Undefined()); + return ChangeWrapper::FromChange(env, *change); } } + return env.Undefined(); } -void PatchWrapper::serialize(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::serialize(const CallbackInfo &info) { + Patch &patch = this->patch; static vector output; output.clear(); Serializer serializer(output); patch.serialize(serializer); - Local result; - auto maybe_result = - Nan::CopyBuffer(reinterpret_cast(output.data()), output.size()); - if (maybe_result.ToLocal(&result)) { - info.GetReturnValue().Set(result); - } + return Buffer::Copy(Env(), output.data(), output.size()); } -void PatchWrapper::deserialize(const Nan::FunctionCallbackInfo &info) { - Local result; - if (Nan::NewInstance(Nan::New(patch_wrapper_constructor)).ToLocal(&result)) { - if (info[0]->IsUint8Array()) { - auto *data = node::Buffer::Data(info[0]); - - static vector input; - input.assign(data, data + node::Buffer::Length(info[0])); - Deserializer deserializer(input); - PatchWrapper *wrapper = new PatchWrapper(Patch{deserializer}); - wrapper->Wrap(result); - info.GetReturnValue().Set(result); - } +Napi::Value PatchWrapper::deserialize(const CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() > 0 && info[0].IsTypedArray()) { + Uint8Array js_array = info[0].As(); + static vector input; + input.assign(js_array.Data(), js_array.Data() + js_array.ByteLength()); + Deserializer deserializer(input); + return from_patch(env, Patch{deserializer}); } -} -void PatchWrapper::compose(const Nan::FunctionCallbackInfo &info) { - Local result; - if (Nan::NewInstance(Nan::New(patch_wrapper_constructor)).ToLocal(&result)) { - Local js_patches = Local::Cast(info[0]); - if (!js_patches->IsArray()) { - Nan::ThrowTypeError("Compose requires an array of patches"); - return; - } + return env.Undefined(); +} - Patch combination; - bool left_to_right = true; - for (uint32_t i = 0, n = js_patches->Length(); i < n; i++) { - if (!Nan::Get(js_patches, i).ToLocalChecked()->IsObject()) { - Nan::ThrowTypeError("Patch.compose must be called with an array of patches"); - return; - } +Napi::Value PatchWrapper::compose(const CallbackInfo &info) { + Napi::Env env = info.Env(); + if (!info[0].IsArray()) { + Error::New(env, "Compose requires an array of patches").ThrowAsJavaScriptException(); + return env.Undefined(); + } - Local js_patch = Local::Cast(Nan::Get(js_patches, i).ToLocalChecked()); - if (!Nan::New(patch_wrapper_constructor_template)->HasInstance(js_patch)) { - Nan::ThrowTypeError("Patch.compose must be called with an array of patches"); - return; - } + Array js_patches = info[0].As(); + Patch combination; + bool left_to_right = true; + for (uint32_t i = 0, n = js_patches.Length(); i < n; i++) { + Napi::Value js_patch_v = js_patches[i]; + if (!js_patch_v.IsObject()) { + Error::New(env, "Patch.compose must be called with an array of patches").ThrowAsJavaScriptException(); + return env.Undefined(); + } - Patch &patch = Nan::ObjectWrap::Unwrap(js_patch)->patch; - if (!combination.combine(patch, left_to_right)) { - Nan::ThrowTypeError(InvalidSpliceMessage); - return; - } - left_to_right = !left_to_right; + Object js_patch = js_patch_v.As(); + if (!js_patch.InstanceOf(constructor.Value())) { + Error::New(env, "Patch.compose must be called with an array of patches").ThrowAsJavaScriptException(); + return env.Undefined(); } - (new PatchWrapper{move(combination)})->Wrap(result); - info.GetReturnValue().Set(result); + Patch &patch = Unwrap(js_patch)->patch; + if (!combination.combine(patch, left_to_right)) { + TypeError::New(env, InvalidSpliceMessage).ThrowAsJavaScriptException();; + return env.Undefined(); + } + left_to_right = !left_to_right; } + + return from_patch(env, move(combination)); } -void PatchWrapper::get_dot_graph(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::get_dot_graph(const CallbackInfo &info) { + Patch &patch = this->patch; std::string graph = patch.get_dot_graph(); - info.GetReturnValue().Set(Nan::New(graph).ToLocalChecked()); + return String::New(info.Env(), graph); } -void PatchWrapper::get_json(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::get_json(const CallbackInfo &info) { + Patch &patch = this->patch; std::string graph = patch.get_json(); - info.GetReturnValue().Set(Nan::New(graph).ToLocalChecked()); + return String::New(info.Env(), graph); } -void PatchWrapper::get_change_count(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::get_change_count(const CallbackInfo &info) { + Patch &patch = this->patch; uint32_t change_count = patch.get_change_count(); - info.GetReturnValue().Set(Nan::New(change_count)); + return Number::New(Env(), change_count); } -void PatchWrapper::get_bounds(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +Napi::Value PatchWrapper::get_bounds(const CallbackInfo &info) { + Napi::Env env = info.Env(); + Patch &patch = this->patch; auto bounds = patch.get_bounds(); if (bounds) { - info.GetReturnValue().Set(ChangeWrapper::FromChange(*bounds)); + return ChangeWrapper::FromChange(env, *bounds); } + return env.Undefined(); } -void PatchWrapper::rebalance(const Nan::FunctionCallbackInfo &info) { - Patch &patch = Nan::ObjectWrap::Unwrap(info.This())->patch; +void PatchWrapper::rebalance(const CallbackInfo &info) { + Patch &patch = this->patch; patch.rebalance(); } diff --git a/src/bindings/patch-wrapper.h b/src/bindings/patch-wrapper.h index 97fc6133..46209f7c 100644 --- a/src/bindings/patch-wrapper.h +++ b/src/bindings/patch-wrapper.h @@ -1,31 +1,33 @@ -#include +#include "napi.h" #include "patch.h" -class PatchWrapper : public Nan::ObjectWrap { +class PatchWrapper : public Napi::ObjectWrap { public: - static void init(v8::Local exports); - static v8::Local from_patch(Patch &&); + static void init(Napi::Object exports); + static Napi::Value from_patch(Napi::Env, Patch &&); + + PatchWrapper(const Napi::CallbackInfo &info); private: - PatchWrapper(Patch &&patch); - static void construct(const Nan::FunctionCallbackInfo &info); - static void splice(const Nan::FunctionCallbackInfo &info); - static void splice_old(const Nan::FunctionCallbackInfo &info); - static void copy(const Nan::FunctionCallbackInfo &info); - static void invert(const Nan::FunctionCallbackInfo &info); - static void get_changes(const Nan::FunctionCallbackInfo &info); - static void get_changes_in_old_range(const Nan::FunctionCallbackInfo &info); - static void get_changes_in_new_range(const Nan::FunctionCallbackInfo &info); - static void change_for_old_position(const Nan::FunctionCallbackInfo &info); - static void change_for_new_position(const Nan::FunctionCallbackInfo &info); - static void serialize(const Nan::FunctionCallbackInfo &info); - static void deserialize(const Nan::FunctionCallbackInfo &info); - static void compose(const Nan::FunctionCallbackInfo &info); - static void get_dot_graph(const Nan::FunctionCallbackInfo &info); - static void get_json(const Nan::FunctionCallbackInfo &info); - static void get_change_count(const Nan::FunctionCallbackInfo &info); - static void get_bounds(const Nan::FunctionCallbackInfo &info); - static void rebalance(const Nan::FunctionCallbackInfo &info); + static Napi::Value deserialize(const Napi::CallbackInfo &info); + static Napi::Value compose(const Napi::CallbackInfo &info); + + void splice(const Napi::CallbackInfo &info); + void splice_old(const Napi::CallbackInfo &info); + Napi::Value copy(const Napi::CallbackInfo &info); + Napi::Value invert(const Napi::CallbackInfo &info); + Napi::Value get_changes(const Napi::CallbackInfo &info); + Napi::Value get_changes_in_old_range(const Napi::CallbackInfo &info); + Napi::Value get_changes_in_new_range(const Napi::CallbackInfo &info); + Napi::Value change_for_old_position(const Napi::CallbackInfo &info); + Napi::Value change_for_new_position(const Napi::CallbackInfo &info); + Napi::Value serialize(const Napi::CallbackInfo &info); + Napi::Value get_dot_graph(const Napi::CallbackInfo &info); + Napi::Value get_json(const Napi::CallbackInfo &info); + Napi::Value get_change_count(const Napi::CallbackInfo &info); + Napi::Value get_bounds(const Napi::CallbackInfo &info); + void rebalance(const Napi::CallbackInfo &info); Patch patch; + static Napi::FunctionReference constructor; }; diff --git a/src/bindings/point-wrapper.cc b/src/bindings/point-wrapper.cc index 7bd159f7..aca2f180 100644 --- a/src/bindings/point-wrapper.cc +++ b/src/bindings/point-wrapper.cc @@ -1,15 +1,11 @@ -#include "point-wrapper.h" #include -#include "nan.h" -using namespace v8; +#include "point-wrapper.h" -static Nan::Persistent row_string; -static Nan::Persistent column_string; -static Nan::Persistent constructor; +using namespace Napi; -static uint32_t number_from_js(Local js_number) { - double number = Nan::To(js_number).FromMaybe(0); +static uint32_t number_from_js(Number js_number) { + double number = js_number.DoubleValue(); if (number > 0 && !std::isfinite(number)) { return UINT32_MAX; } else { @@ -17,65 +13,34 @@ static uint32_t number_from_js(Local js_number) { } } -optional PointWrapper::point_from_js(Local value) { - Nan::MaybeLocal maybe_object = Nan::To(value); - Local object; - if (!maybe_object.ToLocal(&object)) { - Nan::ThrowTypeError("Expected an object with 'row' and 'column' properties."); +optional PointWrapper::point_from_js(Napi::Value value) { + Napi::Env env = value.Env(); + if (!value.IsObject()) { + Error::New(env, "Expected an object with 'row' and 'column' properties.").ThrowAsJavaScriptException(); return optional{}; } - Nan::MaybeLocal maybe_row = Nan::To(Nan::Get(object, Nan::New(row_string)).ToLocalChecked()); - Local js_row; - if (!maybe_row.ToLocal(&js_row)) { - Nan::ThrowTypeError("Expected an object with 'row' and 'column' properties."); + Object object = value.As(); + Napi::Value maybe_row = object.Get("row"); + if (!maybe_row.IsNumber()) { + Error::New(env, "Expected an object with 'row' and 'column' properties.").ThrowAsJavaScriptException(); return optional{}; } + Number js_row = maybe_row.As(); - Nan::MaybeLocal maybe_column = Nan::To(Nan::Get(object, Nan::New(column_string)).ToLocalChecked()); - Local js_column; - if (!maybe_column.ToLocal(&js_column)) { - Nan::ThrowTypeError("Expected an object with 'row' and 'column' properties."); + Napi::Value maybe_column = object.Get("column"); + if (!maybe_column.IsNumber()) { + Error::New(env, "Expected an object with 'row' and 'column' properties.").ThrowAsJavaScriptException(); return optional{}; } - + Number js_column = maybe_column.As(); return Point(number_from_js(js_row), number_from_js(js_column)); } -void PointWrapper::init() { - row_string.Reset(Nan::Persistent(Nan::New("row").ToLocalChecked())); - column_string.Reset(Nan::Persistent(Nan::New("column").ToLocalChecked())); - - Local constructor_template = Nan::New(construct); - constructor_template->SetClassName(Nan::New("Point").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - Nan::SetAccessor(constructor_template->InstanceTemplate(), Nan::New(row_string), get_row); - Nan::SetAccessor(constructor_template->InstanceTemplate(), Nan::New(column_string), get_column); - constructor.Reset(Nan::GetFunction(constructor_template).ToLocalChecked()); -} - -Local PointWrapper::from_point(Point point) { - Local result; - if (Nan::New(constructor)->NewInstance(Nan::GetCurrentContext()).ToLocal(&result)) { - (new PointWrapper(point))->Wrap(result); - return result; - } else { - return Nan::Null(); - } -} - -PointWrapper::PointWrapper(Point point) : point(point) {} - -void PointWrapper::construct(const Nan::FunctionCallbackInfo &info) {} - -void PointWrapper::get_row(v8::Local property, const Nan::PropertyCallbackInfo &info) { - PointWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - Point &point = wrapper->point; - info.GetReturnValue().Set(Nan::New(point.row)); -} +Value PointWrapper::from_point(Napi::Env env, Point point) { + Object js_point_wrapper = Object::New(env); + js_point_wrapper.Set("row", Number::New(env, point.row)); + js_point_wrapper.Set("column", Number::New(env, point.column)); -void PointWrapper::get_column(v8::Local property, const Nan::PropertyCallbackInfo &info) { - PointWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - Point &point = wrapper->point; - info.GetReturnValue().Set(Nan::New(point.column)); + return js_point_wrapper; } diff --git a/src/bindings/point-wrapper.h b/src/bindings/point-wrapper.h index fc06b263..cdac82dd 100644 --- a/src/bindings/point-wrapper.h +++ b/src/bindings/point-wrapper.h @@ -1,28 +1,14 @@ #ifndef SUPERSTRING_POINT_WRAPPER_H #define SUPERSTRING_POINT_WRAPPER_H -#include "nan.h" +#include "napi.h" #include "optional.h" #include "point.h" -class PointWrapper : public Nan::ObjectWrap { +class PointWrapper { public: - static void init(); - static v8::Local from_point(Point point); - static optional point_from_js(v8::Local); - -private: - PointWrapper(Point point); - - static void construct(const Nan::FunctionCallbackInfo &info); - - static void get_row(v8::Local property, - const Nan::PropertyCallbackInfo &info); - - static void get_column(v8::Local property, - const Nan::PropertyCallbackInfo &info); - - Point point; + static Napi::Value from_point(Napi::Env env, Point point); + static optional point_from_js(Napi::Value); }; #endif // SUPERSTRING_POINT_WRAPPER_H diff --git a/src/bindings/range-wrapper.cc b/src/bindings/range-wrapper.cc index b0aaf5ab..1dfbc17e 100644 --- a/src/bindings/range-wrapper.cc +++ b/src/bindings/range-wrapper.cc @@ -1,64 +1,31 @@ #include "range-wrapper.h" #include "point-wrapper.h" -#include "nan.h" -using namespace v8; -static Nan::Persistent start_string; -static Nan::Persistent end_string; -static Nan::Persistent constructor; +using namespace Napi; -optional RangeWrapper::range_from_js(Local value) { - Local object; - if (!Nan::To(value).ToLocal(&object)) { - Nan::ThrowTypeError("Expected an object with 'start' and 'end' properties."); +optional RangeWrapper::range_from_js(Napi::Value value) { + Napi::Env env = value.Env(); + if (!value.IsObject()) { + Error::New(env, "Expected an object with 'start' and 'end' properties.").ThrowAsJavaScriptException(); return optional{}; } - auto start = PointWrapper::point_from_js(Nan::Get(object, Nan::New(start_string)).ToLocalChecked()); - auto end = PointWrapper::point_from_js(Nan::Get(object, Nan::New(end_string)).ToLocalChecked()); + Object object = value.As(); + + auto start = PointWrapper::point_from_js(object.Get("start")); + auto end = PointWrapper::point_from_js(object.Get("end")); if (start && end) { return Range{*start, *end}; } else { - Nan::ThrowTypeError("Expected an object with 'start' and 'end' properties."); + Error::New(env, "Expected an object with 'start' and 'end' properties.").ThrowAsJavaScriptException(); return optional{}; } } -void RangeWrapper::init() { - start_string.Reset(Nan::Persistent(Nan::New("start").ToLocalChecked())); - end_string.Reset(Nan::Persistent(Nan::New("end").ToLocalChecked())); - - Local constructor_template = Nan::New(construct); - constructor_template->SetClassName(Nan::New("Range").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - Nan::SetAccessor(constructor_template->InstanceTemplate(), Nan::New(start_string), get_start); - Nan::SetAccessor(constructor_template->InstanceTemplate(), Nan::New(end_string), get_end); - constructor.Reset(Nan::GetFunction(constructor_template).ToLocalChecked()); -} - -Local RangeWrapper::from_range(Range range) { - Local result; - if (Nan::New(constructor)->NewInstance(Nan::GetCurrentContext()).ToLocal(&result)) { - (new RangeWrapper(range))->Wrap(result); - return result; - } else { - return Nan::Null(); - } -} - -RangeWrapper::RangeWrapper(Range range) : range(range) {} - -void RangeWrapper::construct(const Nan::FunctionCallbackInfo &info) {} - -void RangeWrapper::get_start(v8::Local property, const Nan::PropertyCallbackInfo &info) { - RangeWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - Range &range = wrapper->range; - info.GetReturnValue().Set(PointWrapper::from_point(range.start)); -} - -void RangeWrapper::get_end(v8::Local property, const Nan::PropertyCallbackInfo &info) { - RangeWrapper *wrapper = Nan::ObjectWrap::Unwrap(info.This()); - Range &range = wrapper->range; - info.GetReturnValue().Set(PointWrapper::from_point(range.end)); +Value RangeWrapper::from_range(Napi::Env env, Range range) { + Object js_range_wrapper = Object::New(env); + js_range_wrapper.Set("start", PointWrapper::from_point(env, range.start)); + js_range_wrapper.Set("end", PointWrapper::from_point(env, range.end)); + return js_range_wrapper; } diff --git a/src/bindings/range-wrapper.h b/src/bindings/range-wrapper.h index de08dd59..30ea73cf 100644 --- a/src/bindings/range-wrapper.h +++ b/src/bindings/range-wrapper.h @@ -1,25 +1,15 @@ #ifndef SUPERSTRING_RANGE_WRAPPER_H #define SUPERSTRING_RANGE_WRAPPER_H -#include "nan.h" +#include "napi.h" #include "optional.h" #include "point.h" #include "range.h" -class RangeWrapper : public Nan::ObjectWrap { +class RangeWrapper : public Napi::ObjectWrap { public: - static void init(); - static v8::Local from_range(Range); - static optional range_from_js(v8::Local); - -private: - RangeWrapper(Range); - - static void construct(const Nan::FunctionCallbackInfo &); - static void get_start(v8::Local, const Nan::PropertyCallbackInfo &); - static void get_end(v8::Local, const Nan::PropertyCallbackInfo &); - - Range range; + static Napi::Value from_range(Napi::Env, Range); + static optional range_from_js(Napi::Value); }; #endif // SUPERSTRING_RANGE_WRAPPER_H diff --git a/src/bindings/string-conversion.cc b/src/bindings/string-conversion.cc index 669feac4..b2e58f12 100644 --- a/src/bindings/string-conversion.cc +++ b/src/bindings/string-conversion.cc @@ -1,54 +1,24 @@ #include "string-conversion.h" #include "text.h" -using namespace v8; +using namespace Napi; using std::u16string; -optional string_conversion::string_from_js(Local value) { - Local string; - if (!Nan::To(value).ToLocal(&string)) { - Nan::ThrowTypeError("Expected a string."); +optional string_conversion::string_from_js(Value value) { + Env env = value.Env(); + if (!value.IsString()) { + Error::New(env, "Expected a string.").ThrowAsJavaScriptException(); return optional{}; } - u16string result; - result.resize(string->Length()); - string->Write( - - // Nan doesn't wrap this functionality - #if NODE_MAJOR_VERSION >= 12 - Isolate::GetCurrent(), - #endif - - reinterpret_cast(&result[0]), - 0, - -1, - String::WriteOptions::NO_NULL_TERMINATION - ); - return result; + String string = value.As(); + return optional{string}; } -Local string_conversion::string_to_js(const u16string &text, const char *failure_message) { - Local result; - if (Nan::New( - reinterpret_cast(text.data()), - text.size() - ).ToLocal(&result)) { - return result; - } else { - if (!failure_message) failure_message = "Couldn't convert text to a String"; - Nan::ThrowError(failure_message); - return Nan::New("").ToLocalChecked(); - } +String string_conversion::string_to_js(const Env env, const u16string &text, const char *failure_message) { + return String::New(env, text); } -Local string_conversion::char_to_js(const uint16_t c, const char *failure_message) { - Local result; - if (Nan::New(&c, 1).ToLocal(&result)) { - return result; - } else { - if (!failure_message) failure_message = "Couldn't convert character to a String"; - Nan::ThrowError(failure_message); - return Nan::New("").ToLocalChecked(); - } +String string_conversion::char_to_js(const Env env, const uint16_t c, const char *failure_message) { + return String::New(env, u16string{c}); } diff --git a/src/bindings/string-conversion.h b/src/bindings/string-conversion.h index f8178041..8e8d8306 100644 --- a/src/bindings/string-conversion.h +++ b/src/bindings/string-conversion.h @@ -2,20 +2,23 @@ #define SUPERSTRING_STRING_CONVERSION_H #include -#include "nan.h" + +#include "napi.h" #include "optional.h" #include "text.h" namespace string_conversion { - v8::Local string_to_js( + Napi::String string_to_js( + const Napi::Env env, const std::u16string &, const char *failure_message = nullptr ); - v8::Local char_to_js( + Napi::String char_to_js( + const Napi::Env env, const std::uint16_t, const char *failure_message = nullptr ); - optional string_from_js(v8::Local); + optional string_from_js(Napi::Value); }; #endif // SUPERSTRING_STRING_CONVERSION_H diff --git a/src/bindings/text-buffer-snapshot-wrapper.cc b/src/bindings/text-buffer-snapshot-wrapper.cc index d613b6a6..6ebc4bef 100644 --- a/src/bindings/text-buffer-snapshot-wrapper.cc +++ b/src/bindings/text-buffer-snapshot-wrapper.cc @@ -2,53 +2,43 @@ #include "text-buffer-wrapper.h" #include "text-buffer-snapshot-wrapper.h" -using namespace v8; +using namespace Napi; -static Nan::Persistent snapshot_wrapper_constructor; +FunctionReference TextBufferSnapshotWrapper::constructor; -void TextBufferSnapshotWrapper::init() { - auto class_name = Nan::New("Snapshot").ToLocalChecked(); +void TextBufferSnapshotWrapper::init(Napi::Env env) { + Napi::Function func = DefineClass(env, "Snapshot", { + InstanceMethod<&TextBufferSnapshotWrapper::destroy>("destroy"), + }); - auto constructor_template = Nan::New(construct); - constructor_template->SetClassName(class_name); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - - const auto &prototype_template = constructor_template->PrototypeTemplate(); - Nan::SetTemplate(prototype_template, Nan::New("destroy").ToLocalChecked(), Nan::New(destroy), None); - - snapshot_wrapper_constructor.Reset(Nan::GetFunction(constructor_template).ToLocalChecked()); + constructor.Reset(func, 1); } -TextBufferSnapshotWrapper::TextBufferSnapshotWrapper(Local js_buffer, void *snapshot) : - snapshot{snapshot} { - slices_ = reinterpret_cast(snapshot)->primitive_chunks(); - js_text_buffer.Reset(Isolate::GetCurrent(), js_buffer); -} +TextBufferSnapshotWrapper::TextBufferSnapshotWrapper(const CallbackInfo &info) : ObjectWrap(info) { + if (info[0].IsObject() && info[1].IsExternal()) { + auto js_buffer = info[0].As(); + auto js_wrapper = info[1].As>(); -TextBufferSnapshotWrapper::~TextBufferSnapshotWrapper() { - if (snapshot) { - delete reinterpret_cast(snapshot); + js_text_buffer.Reset(js_buffer, 1); + snapshot = js_wrapper.Data(); + slices_ = snapshot->primitive_chunks(); } } -Local TextBufferSnapshotWrapper::new_instance(Local js_buffer, void *snapshot) { - Local result; - if (Nan::NewInstance(Nan::New(snapshot_wrapper_constructor)).ToLocal(&result)) { - (new TextBufferSnapshotWrapper(js_buffer, snapshot))->Wrap(result); - return result; - } else { - return Nan::Null(); +TextBufferSnapshotWrapper::~TextBufferSnapshotWrapper() { + if (snapshot) { + delete snapshot; } } -void TextBufferSnapshotWrapper::construct(const Nan::FunctionCallbackInfo &info) { - info.GetReturnValue().Set(Nan::Null()); +Value TextBufferSnapshotWrapper::new_instance(Napi::Env env, Object js_buffer, TextBuffer::Snapshot *snapshot) { + auto wrapper = External::New(env, snapshot); + return constructor.New({js_buffer, wrapper}); } -void TextBufferSnapshotWrapper::destroy(const Nan::FunctionCallbackInfo &info) { - auto reader = Nan::ObjectWrap::Unwrap(Nan::To(info.This()).ToLocalChecked()); - if (reader->snapshot) { - delete reinterpret_cast(reader->snapshot); - reader->snapshot = nullptr; +void TextBufferSnapshotWrapper::destroy(const CallbackInfo &info) { + if (this->snapshot) { + delete this->snapshot; + this->snapshot = nullptr; } } diff --git a/src/bindings/text-buffer-snapshot-wrapper.h b/src/bindings/text-buffer-snapshot-wrapper.h index bb23b4ce..80639a49 100644 --- a/src/bindings/text-buffer-snapshot-wrapper.h +++ b/src/bindings/text-buffer-snapshot-wrapper.h @@ -1,33 +1,35 @@ #ifndef SUPERSTRING_TEXT_BUFFER_SNAPSHOT_WRAPPER_H #define SUPERSTRING_TEXT_BUFFER_SNAPSHOT_WRAPPER_H -#include "nan.h" #include +#include "napi.h" +#include "text-buffer.h" + // This header can be included by other native node modules, allowing them // to access the content of a TextBuffer::Snapshot without having to call // any superstring APIs. -class TextBufferSnapshotWrapper : public Nan::ObjectWrap { +class TextBufferSnapshotWrapper : public Napi::ObjectWrap { public: - static void init(); + static void init(Napi::Env env); - static v8::Local new_instance(v8::Local, void *); + static Napi::Value new_instance(Napi::Env, Napi::Object, TextBuffer::Snapshot *); inline const std::vector> *slices() { return &slices_; } -private: - TextBufferSnapshotWrapper(v8::Local js_buffer, void *snapshot); + TextBufferSnapshotWrapper(const Napi::CallbackInfo &info); ~TextBufferSnapshotWrapper(); - static void construct(const Nan::FunctionCallbackInfo &info); - static void destroy(const Nan::FunctionCallbackInfo &info); +private: + void destroy(const Napi::CallbackInfo &info); - v8::Persistent js_text_buffer; - void *snapshot; + Napi::ObjectReference js_text_buffer; + TextBuffer::Snapshot *snapshot; std::vector> slices_; + static Napi::FunctionReference constructor; }; #endif // SUPERSTRING_TEXT_BUFFER_SNAPSHOT_WRAPPER_H diff --git a/src/bindings/text-buffer-wrapper.cc b/src/bindings/text-buffer-wrapper.cc index 3c633269..4f8761f4 100644 --- a/src/bindings/text-buffer-wrapper.cc +++ b/src/bindings/text-buffer-wrapper.cc @@ -1,7 +1,11 @@ -#include "text-buffer-wrapper.h" #include #include #include +#include + +#include "v8.h" +#include "node.h" +#include "text-buffer-wrapper.h" #include "number-conversion.h" #include "point-wrapper.h" #include "range-wrapper.h" @@ -12,10 +16,9 @@ #include "text-writer.h" #include "text-slice.h" #include "text-diff.h" -#include "noop.h" -#include +#include "util.h" -using namespace v8; +using namespace Napi; using std::move; using std::pair; using std::string; @@ -25,6 +28,8 @@ using std::wstring; using SubsequenceMatch = TextBuffer::SubsequenceMatch; +#define REGEX_CACHE_KEY "__textBufferRegex" + #ifdef WIN32 #include @@ -72,226 +77,252 @@ static FILE *open_file(const std::string &name, const char *flags) { static size_t CHUNK_SIZE = 10 * 1024; -class RegexWrapper : public Nan::ObjectWrap { - public: - Regex regex; - static Nan::Persistent constructor; - static void construct(const Nan::FunctionCallbackInfo &info) {} +class RegexWrapper : public ObjectWrap { +public: + RegexWrapper(const CallbackInfo &info): ObjectWrap(info) { + if (info[0].IsExternal()) { + auto wrapper = info[0].As>(); + regex.reset(wrapper.Data()); + } + } - RegexWrapper(Regex &®ex) : regex{move(regex)} {} + static const Regex *regex_from_js(const Napi::Value &value) { + auto env = value.Env(); - static const Regex *regex_from_js(const Local &value) { - Local js_pattern; - Local js_regex; - Local cache_key = Nan::New("__textBufferRegex").ToLocalChecked(); + String js_pattern; bool ignore_case = false; bool unicode = false; + Object js_regex; - if (value->IsString()) { - js_pattern = Local::Cast(value); - } else if (value->IsRegExp()) { - js_regex = Local::Cast(value); - Local stored_regex = Nan::Get(js_regex, cache_key).ToLocalChecked(); - if (!stored_regex->IsUndefined()) { - return &Nan::ObjectWrap::Unwrap(Nan::To(stored_regex).ToLocalChecked())->regex; - } - js_pattern = js_regex->GetSource(); - if (js_regex->GetFlags() & RegExp::kIgnoreCase) ignore_case = true; - if (js_regex->GetFlags() & RegExp::kUnicode) unicode = true; + if (value.IsString()) { + js_pattern = value.As(); } else { - Nan::ThrowTypeError("Argument must be a RegExp"); - return nullptr; + v8::Local js_regex_value = V8LocalValueFromJsValue(value); + if (!value.IsObject() || !js_regex_value->IsRegExp()) { + Napi::Error::New(env, "Argument must be a RegExp").ThrowAsJavaScriptException(); + return nullptr; + } + + // Check if there is any cached regex inside the js object. + js_regex = value.As(); + if (js_regex.Has(REGEX_CACHE_KEY)) { + Napi::Value js_regex_wrapper = js_regex.Get(REGEX_CACHE_KEY); + if (js_regex_wrapper.IsObject()) { + return Unwrap(js_regex_wrapper.As())->regex.get(); + } + } + + // Extract necessary parameters from RegExp + v8::Local v8_regex = js_regex_value.As(); + js_pattern = Napi::Value(env, JsValueFromV8LocalValue(v8_regex->GetSource())).As(); + if (v8_regex->GetFlags() & v8::RegExp::kIgnoreCase) ignore_case = true; + if (v8_regex->GetFlags() & v8::RegExp::kUnicode) unicode = true; } + // initialize Regex u16string error_message; optional pattern = string_conversion::string_from_js(js_pattern); - Regex regex(*pattern, &error_message, ignore_case, unicode); + Regex regex = Regex(*pattern, &error_message, ignore_case, unicode); if (!error_message.empty()) { - Nan::ThrowError(string_conversion::string_to_js(error_message)); + Napi::Error::New(env, string_conversion::string_to_js(env, error_message)).ThrowAsJavaScriptException(); return nullptr; } - Local result; - if (!Nan::New(constructor)->NewInstance(Nan::GetCurrentContext()).ToLocal(&result)) { - Nan::ThrowError("Could not create regex wrapper"); - return nullptr; + // initialize RegexWrapper + auto wrapper = External::New(env, new Regex(move(regex))); + auto js_regex_wrapper = constructor.New({wrapper}); + + // cache Regex + if (!js_regex.IsEmpty()) { + js_regex.Set(REGEX_CACHE_KEY, js_regex_wrapper); } - auto regex_wrapper = new RegexWrapper(move(regex)); - regex_wrapper->Wrap(result); - if (!js_regex.IsEmpty()) Nan::Set(js_regex, cache_key, result); - return ®ex_wrapper->regex; + return Unwrap(js_regex_wrapper)->regex.get(); } - static void init() { - Local constructor_template = Nan::New(construct); - constructor_template->SetClassName(Nan::New("TextBufferRegex").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - constructor.Reset(Nan::GetFunction(constructor_template).ToLocalChecked()); + static void init(Napi::Env env) { + Function func = DefineClass(env, "RegexWrapper", {}); + constructor.Reset(func, 1); } + +private: + static FunctionReference constructor; + std::unique_ptr regex; }; -Nan::Persistent RegexWrapper::constructor; +FunctionReference RegexWrapper::constructor; -class SubsequenceMatchWrapper : public Nan::ObjectWrap { +class SubsequenceMatchWrapper : public ObjectWrap { public: - static Nan::Persistent constructor; - - SubsequenceMatchWrapper(SubsequenceMatch &&match) : - match(std::move(match)) {} - - static void init() { - Local constructor_template = Nan::New(); - constructor_template->SetClassName(Nan::New("SubsequenceMatch").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - const auto &instance_template = constructor_template->InstanceTemplate(); + static void init(Napi::Env env) { + Function func = DefineClass(env, "SubsequenceMatch", { + InstanceAccessor<&SubsequenceMatchWrapper::get_word>("word", static_cast(napi_enumerable | napi_configurable)), + InstanceAccessor<&SubsequenceMatchWrapper::get_match_indices>("matchIndices", static_cast(napi_enumerable | napi_configurable)), + InstanceAccessor<&SubsequenceMatchWrapper::get_score, &SubsequenceMatchWrapper::set_score>( + "score", static_cast(napi_enumerable | napi_configurable) + ), + }); - Nan::SetAccessor(instance_template, Nan::New("word").ToLocalChecked(), get_word); - Nan::SetAccessor(instance_template, Nan::New("matchIndices").ToLocalChecked(), get_match_indices); - Nan::SetAccessor(instance_template, Nan::New("score").ToLocalChecked(), get_score); + constructor.Reset(func, 1); + } - constructor.Reset(Nan::GetFunction(constructor_template).ToLocalChecked()); + static Napi::Value from_subsequence_match(Napi::Env env, SubsequenceMatch match) { + auto wrapper = External::New(env, &match); + return constructor.New({wrapper}); } - static Local from_subsequence_match(SubsequenceMatch match) { - Local result; - if (Nan::NewInstance(Nan::New(constructor)).ToLocal(&result)) { - (new SubsequenceMatchWrapper(std::move(match)))->Wrap(result); - return result; - } else { - return Nan::Null(); + SubsequenceMatchWrapper(const CallbackInfo &info): ObjectWrap(info) { + if (info[0].IsExternal()) { + auto wrapper = info[0].As>(); + match = std::move(*wrapper.Data()); } } private: - static void get_word(v8::Local property, const Nan::PropertyCallbackInfo &info) { - SubsequenceMatch &match = Nan::ObjectWrap::Unwrap(info.This())->match; - info.GetReturnValue().Set(string_conversion::string_to_js(match.word)); + Napi::Value get_word(const CallbackInfo &info) { + return string_conversion::string_to_js(info.Env(), this->match.word); } - static void get_match_indices(v8::Local property, const Nan::PropertyCallbackInfo &info) { - SubsequenceMatch &match = Nan::ObjectWrap::Unwrap(info.This())->match; - Local js_result = Nan::New(); + Napi::Value get_match_indices(const CallbackInfo &info) { + auto env = info.Env(); + SubsequenceMatch &match = this->match; + Array js_result = Array::New(env); for (size_t i = 0; i < match.match_indices.size(); i++) { - Nan::Set(js_result, i, Nan::New(match.match_indices[i])); + js_result[i] = Number::New(env, match.match_indices[i]); } - info.GetReturnValue().Set(js_result); + return js_result; } - static void get_score(v8::Local property, const Nan::PropertyCallbackInfo &info) { - SubsequenceMatch &match = Nan::ObjectWrap::Unwrap(info.This())->match; - info.GetReturnValue().Set(Nan::New(match.score)); + Napi::Value get_score(const CallbackInfo &info) { + return Number::New(info.Env(), this->match.score); + } + + void set_score(const CallbackInfo &info, const Napi::Value &value) { + if (value.IsNumber()) { + this->match.score = value.As().DoubleValue(); + } else { + auto env = info.Env(); + Error::New(env, "Expected a number.").ThrowAsJavaScriptException(); + } } TextBuffer::SubsequenceMatch match; + static FunctionReference constructor; }; -Nan::Persistent SubsequenceMatchWrapper::constructor; - -void TextBufferWrapper::init(Local exports) { - Local constructor_template = Nan::New(construct); - constructor_template->SetClassName(Nan::New("TextBuffer").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - const auto &prototype_template = constructor_template->PrototypeTemplate(); - Nan::SetTemplate(prototype_template, Nan::New("delete").ToLocalChecked(), Nan::New(noop), None); - Nan::SetTemplate(prototype_template, Nan::New("getLength").ToLocalChecked(), Nan::New(get_length), None); - Nan::SetTemplate(prototype_template, Nan::New("getExtent").ToLocalChecked(), Nan::New(get_extent), None); - Nan::SetTemplate(prototype_template, Nan::New("getLineCount").ToLocalChecked(), Nan::New(get_line_count), None); - Nan::SetTemplate(prototype_template, Nan::New("hasAstral").ToLocalChecked(), Nan::New(has_astral), None); - Nan::SetTemplate(prototype_template, Nan::New("getCharacterAtPosition").ToLocalChecked(), Nan::New(get_character_at_position), None); - Nan::SetTemplate(prototype_template, Nan::New("getTextInRange").ToLocalChecked(), Nan::New(get_text_in_range), None); - Nan::SetTemplate(prototype_template, Nan::New("setTextInRange").ToLocalChecked(), Nan::New(set_text_in_range), None); - Nan::SetTemplate(prototype_template, Nan::New("getText").ToLocalChecked(), Nan::New(get_text), None); - Nan::SetTemplate(prototype_template, Nan::New("setText").ToLocalChecked(), Nan::New(set_text), None); - Nan::SetTemplate(prototype_template, Nan::New("lineForRow").ToLocalChecked(), Nan::New(line_for_row), None); - Nan::SetTemplate(prototype_template, Nan::New("lineLengthForRow").ToLocalChecked(), Nan::New(line_length_for_row), None); - Nan::SetTemplate(prototype_template, Nan::New("lineEndingForRow").ToLocalChecked(), Nan::New(line_ending_for_row), None); - Nan::SetTemplate(prototype_template, Nan::New("getLines").ToLocalChecked(), Nan::New(get_lines), None); - Nan::SetTemplate(prototype_template, Nan::New("characterIndexForPosition").ToLocalChecked(), Nan::New(character_index_for_position), None); - Nan::SetTemplate(prototype_template, Nan::New("positionForCharacterIndex").ToLocalChecked(), Nan::New(position_for_character_index), None); - Nan::SetTemplate(prototype_template, Nan::New("isModified").ToLocalChecked(), Nan::New(is_modified), None); - Nan::SetTemplate(prototype_template, Nan::New("load").ToLocalChecked(), Nan::New(load), None); - Nan::SetTemplate(prototype_template, Nan::New("baseTextMatchesFile").ToLocalChecked(), Nan::New(base_text_matches_file), None); - Nan::SetTemplate(prototype_template, Nan::New("save").ToLocalChecked(), Nan::New(save), None); - Nan::SetTemplate(prototype_template, Nan::New("loadSync").ToLocalChecked(), Nan::New(load_sync), None); - Nan::SetTemplate(prototype_template, Nan::New("serializeChanges").ToLocalChecked(), Nan::New(serialize_changes), None); - Nan::SetTemplate(prototype_template, Nan::New("deserializeChanges").ToLocalChecked(), Nan::New(deserialize_changes), None); - Nan::SetTemplate(prototype_template, Nan::New("reset").ToLocalChecked(), Nan::New(reset), None); - Nan::SetTemplate(prototype_template, Nan::New("baseTextDigest").ToLocalChecked(), Nan::New(base_text_digest), None); - Nan::SetTemplate(prototype_template, Nan::New("find").ToLocalChecked(), Nan::New(find), None); - Nan::SetTemplate(prototype_template, Nan::New("findSync").ToLocalChecked(), Nan::New(find_sync), None); - Nan::SetTemplate(prototype_template, Nan::New("findAll").ToLocalChecked(), Nan::New(find_all), None); - Nan::SetTemplate(prototype_template, Nan::New("findAllSync").ToLocalChecked(), Nan::New(find_all_sync), None); - Nan::SetTemplate(prototype_template, Nan::New("findAndMarkAllSync").ToLocalChecked(), Nan::New(find_and_mark_all_sync), None); - Nan::SetTemplate(prototype_template, Nan::New("findWordsWithSubsequenceInRange").ToLocalChecked(), Nan::New(find_words_with_subsequence_in_range), None); - Nan::SetTemplate(prototype_template, Nan::New("getDotGraph").ToLocalChecked(), Nan::New(dot_graph), None); - Nan::SetTemplate(prototype_template, Nan::New("getSnapshot").ToLocalChecked(), Nan::New(get_snapshot), None); - RegexWrapper::init(); - SubsequenceMatchWrapper::init(); - Nan::Set(exports, Nan::New("TextBuffer").ToLocalChecked(), Nan::GetFunction(constructor_template).ToLocalChecked()); +void TextBufferWrapper::init(Object exports) { + auto env = exports.Env(); + + RegexWrapper::init(env); + SubsequenceMatchWrapper::init(env); + + + Napi::Function func = DefineClass(env, "TextBuffer", { + InstanceMethod<&TextBufferWrapper::get_length>("getLength", napi_default_method), + InstanceMethod<&TextBufferWrapper::get_extent>("getExtent", napi_default_method), + InstanceMethod<&TextBufferWrapper::get_line_count>("getLineCount", napi_default_method), + InstanceMethod<&TextBufferWrapper::has_astral>("hasAstral", napi_default_method), + InstanceMethod<&TextBufferWrapper::get_character_at_position>("getCharacterAtPosition", napi_default_method), + InstanceMethod<&TextBufferWrapper::get_text_in_range>("getTextInRange", napi_default_method), + InstanceMethod<&TextBufferWrapper::set_text_in_range>("setTextInRange", napi_default_method), + InstanceMethod<&TextBufferWrapper::get_text>("getText", napi_default_method), + InstanceMethod<&TextBufferWrapper::set_text>("setText", napi_default_method), + InstanceMethod<&TextBufferWrapper::line_for_row>("lineForRow", napi_default_method), + InstanceMethod<&TextBufferWrapper::line_length_for_row>("lineLengthForRow", napi_default_method), + InstanceMethod<&TextBufferWrapper::line_ending_for_row>("lineEndingForRow", napi_default_method), + InstanceMethod<&TextBufferWrapper::get_lines>("getLines", napi_default_method), + InstanceMethod<&TextBufferWrapper::character_index_for_position>("characterIndexForPosition", napi_default_method), + InstanceMethod<&TextBufferWrapper::position_for_character_index>("positionForCharacterIndex", napi_default_method), + InstanceMethod<&TextBufferWrapper::is_modified>("isModified", napi_default_method), + InstanceMethod<&TextBufferWrapper::load>("load", napi_default_method), + InstanceMethod<&TextBufferWrapper::base_text_matches_file>("baseTextMatchesFile", napi_default_method), + InstanceMethod<&TextBufferWrapper::save>("save", napi_default_method), + InstanceMethod<&TextBufferWrapper::load_sync>("loadSync", napi_default_method), + InstanceMethod<&TextBufferWrapper::serialize_changes>("serializeChanges", napi_default_method), + InstanceMethod<&TextBufferWrapper::deserialize_changes>("deserializeChanges", napi_default_method), + InstanceMethod<&TextBufferWrapper::reset>("reset", napi_default_method), + InstanceMethod<&TextBufferWrapper::base_text_digest>("baseTextDigest", napi_default_method), + InstanceMethod<&TextBufferWrapper::find>("find", napi_default_method), + InstanceMethod<&TextBufferWrapper::find_sync>("findSync", napi_default_method), + InstanceMethod<&TextBufferWrapper::find_all>("findAll", napi_default_method), + InstanceMethod<&TextBufferWrapper::find_all_sync>("findAllSync", napi_default_method), + InstanceMethod<&TextBufferWrapper::find_and_mark_all_sync>("findAndMarkAllSync", napi_default_method), + InstanceMethod<&TextBufferWrapper::find_words_with_subsequence_in_range>("findWordsWithSubsequenceInRange", napi_default_method), + InstanceMethod<&TextBufferWrapper::dot_graph>("getDotGraph", napi_default_method), + InstanceMethod<&TextBufferWrapper::get_snapshot>("getSnapshot", napi_default_method), + }); + + constructor.Reset(func, 1); + exports.Set("TextBuffer", func); } +FunctionReference SubsequenceMatchWrapper::constructor; +FunctionReference TextBufferWrapper::constructor; -void TextBufferWrapper::construct(const Nan::FunctionCallbackInfo &info) { - TextBufferWrapper *wrapper = new TextBufferWrapper(); - if (info.Length() > 0 && info[0]->IsString()) { +TextBufferWrapper::TextBufferWrapper(const CallbackInfo &info): ObjectWrap(info) { + if (info.Length() > 0 && info[0].IsString()) { auto text = string_conversion::string_from_js(info[0]); if (text) { - wrapper->text_buffer.reset(move(*text)); + this->text_buffer.reset(move(*text)); } } - wrapper->Wrap(info.This()); } -void TextBufferWrapper::get_length(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - info.GetReturnValue().Set(Nan::New(text_buffer.size())); +Napi::Value TextBufferWrapper::get_length(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + return Number::New(info.Env(), text_buffer.size()); } -void TextBufferWrapper::get_extent(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - info.GetReturnValue().Set(PointWrapper::from_point(text_buffer.extent())); +Napi::Value TextBufferWrapper::get_extent(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + return PointWrapper::from_point(info.Env(), text_buffer.extent()); } -void TextBufferWrapper::get_line_count(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - info.GetReturnValue().Set(Nan::New(text_buffer.extent().row + 1)); +Napi::Value TextBufferWrapper::get_line_count(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + return Number::New(info.Env(), text_buffer.extent().row + 1); } -void TextBufferWrapper::has_astral(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - info.GetReturnValue().Set(Nan::New(text_buffer.has_astral())); +Napi::Value TextBufferWrapper::has_astral(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + return Boolean::New(info.Env(), text_buffer.has_astral()); } -void TextBufferWrapper::get_character_at_position(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::get_character_at_position(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; auto point = PointWrapper::point_from_js(info[0]); if (point) { - info.GetReturnValue().Set(string_conversion::char_to_js(text_buffer.character_at(*point))); + return string_conversion::char_to_js(env, text_buffer.character_at(*point)); } + + return env.Undefined(); } -void TextBufferWrapper::get_text_in_range(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::get_text_in_range(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; auto range = RangeWrapper::range_from_js(info[0]); if (range) { - info.GetReturnValue().Set(string_conversion::string_to_js(text_buffer.text_in_range(*range))); + return string_conversion::string_to_js(env, text_buffer.text_in_range(*range)); } + + return env.Undefined(); } -void TextBufferWrapper::get_text(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - info.GetReturnValue().Set(string_conversion::string_to_js( +Napi::Value TextBufferWrapper::get_text(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + return string_conversion::string_to_js( + info.Env(), text_buffer.text(), "This buffer's content is too large to fit into a string.\n" "\n" "Consider using APIs like `getTextInRange` to access the data you need." - )); + ); } -void TextBufferWrapper::set_text_in_range(const Nan::FunctionCallbackInfo &info) { - auto text_buffer_wrapper = Nan::ObjectWrap::Unwrap(info.This()); - text_buffer_wrapper->cancel_queued_workers(); - auto &text_buffer = text_buffer_wrapper->text_buffer; +void TextBufferWrapper::set_text_in_range(const CallbackInfo &info) { + this->cancel_queued_workers(); + auto &text_buffer = this->text_buffer; auto range = RangeWrapper::range_from_js(info[0]); auto text = string_conversion::string_from_js(info[1]); if (range && text) { @@ -299,125 +330,118 @@ void TextBufferWrapper::set_text_in_range(const Nan::FunctionCallbackInfo } } -void TextBufferWrapper::set_text(const Nan::FunctionCallbackInfo &info) { - auto text_buffer_wrapper = Nan::ObjectWrap::Unwrap(info.This()); - text_buffer_wrapper->cancel_queued_workers(); - auto &text_buffer = text_buffer_wrapper->text_buffer; +void TextBufferWrapper::set_text(const CallbackInfo &info) { + this->cancel_queued_workers(); + auto &text_buffer = this->text_buffer; auto text = string_conversion::string_from_js(info[0]); if (text) { text_buffer.set_text(move(*text)); } } -void TextBufferWrapper::line_for_row(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - auto maybe_row = Nan::To(info[0]); - - if (maybe_row.IsJust()) { - uint32_t row = maybe_row.FromJust(); +Napi::Value TextBufferWrapper::line_for_row(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + Napi::Value result; + if (info.Length() > 0 && info[0].IsNumber()) { + uint32_t row = info[0].As().Uint32Value(); if (row <= text_buffer.extent().row) { - text_buffer.with_line_for_row(row, [&info](const char16_t *data, uint32_t size) { - Local result; - if (Nan::New(reinterpret_cast(data), size).ToLocal(&result)) { - info.GetReturnValue().Set(result); - } + text_buffer.with_line_for_row(row, [&info, &result](const char16_t *data, uint32_t size) { + auto env = info.Env(); + result = String::New(env, data, size); }); } } + return result; } -void TextBufferWrapper::line_length_for_row(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - auto maybe_row = Nan::To(info[0]); - if (maybe_row.IsJust()) { - auto result = text_buffer.line_length_for_row(maybe_row.FromJust()); +Napi::Value TextBufferWrapper::line_length_for_row(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; + if (info.Length() > 0 && info[0].IsNumber()) { + uint32_t row = info[0].As().Uint32Value(); + auto result = text_buffer.line_length_for_row(row); if (result) { - info.GetReturnValue().Set(Nan::New(*result)); + return Number::New(env, *result); } } + return env.Undefined(); } -void TextBufferWrapper::line_ending_for_row(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - auto maybe_row = Nan::To(info[0]); - if (maybe_row.IsJust()) { - auto result = text_buffer.line_ending_for_row(maybe_row.FromJust()); +Napi::Value TextBufferWrapper::line_ending_for_row(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; + if (info.Length() > 0 && info[0].IsNumber()) { + uint32_t row = info[0].As().Uint32Value(); + auto result = text_buffer.line_ending_for_row(row); if (result) { - info.GetReturnValue().Set(Nan::New(result).ToLocalChecked()); + return String::New(env, reinterpret_cast(result)); } } + return env.Undefined(); } -void TextBufferWrapper::get_lines(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - auto result = Nan::New(); +Napi::Value TextBufferWrapper::get_lines(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; + auto result = Array::New(env); for (uint32_t row = 0, row_count = text_buffer.extent().row + 1; row < row_count; row++) { auto text = text_buffer.text_in_range({{row, 0}, {row, UINT32_MAX}}); - Nan::Set(result, row, string_conversion::string_to_js(text)); + result[row] = string_conversion::string_to_js(env, text); } - info.GetReturnValue().Set(result); + return result; } -void TextBufferWrapper::character_index_for_position(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::character_index_for_position(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; auto position = PointWrapper::point_from_js(info[0]); if (position) { - info.GetReturnValue().Set( - Nan::New(text_buffer.clip_position(*position).offset) - ); + return Number::New(env, text_buffer.clip_position(*position).offset); } + + return env.Undefined(); } -void TextBufferWrapper::position_for_character_index(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - auto maybe_offset = Nan::To(info[0]); - if (maybe_offset.IsJust()) { - int64_t offset = maybe_offset.FromJust(); - info.GetReturnValue().Set( - PointWrapper::from_point(text_buffer.position_for_offset( +Napi::Value TextBufferWrapper::position_for_character_index(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; + if (info.Length() > 0 && info[0].IsNumber()) { + int64_t offset = info[0].As().Int64Value(); + return PointWrapper::from_point(env, text_buffer.position_for_offset( std::max(0, offset) - )) - ); + )); } + return env.Undefined(); } -static Local encode_ranges(const vector &ranges) { +static Value encode_ranges(Env env, const vector &ranges) { auto length = ranges.size() * 4; - auto buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), length * sizeof(uint32_t)); - auto result = v8::Uint32Array::New(buffer, 0, length); - #if (V8_MAJOR_VERSION < 8) - auto data = buffer->GetContents().Data(); - #else - auto data = buffer->GetBackingStore()->Data(); - #endif - memcpy(data, ranges.data(), length * sizeof(uint32_t)); - return result; + Uint32Array js_array_buffer = Uint32Array::New(env, length); + memcpy(js_array_buffer.Data(), ranges.data(), length * sizeof(uint32_t)); + return js_array_buffer; } template -class TextBufferSearcher : public Nan::AsyncWorker { +class TextBufferSearcher : public Napi::AsyncWorker { const TextBuffer::Snapshot *snapshot; const Regex *regex; Range search_range; vector matches; - Nan::Persistent argument; public: - TextBufferSearcher(Nan::Callback *completion_callback, + TextBufferSearcher(Function &completion_callback, const TextBuffer::Snapshot *snapshot, const Regex *regex, - const Range &search_range, - Local arg) : + const Range &search_range) : AsyncWorker(completion_callback, "TextBuffer.find"), snapshot{snapshot}, regex{regex}, search_range(search_range) { - argument.Reset(arg); } - void Execute() { + void Execute() override { if (single_result) { auto find_result = snapshot->find(*regex, search_range); if (find_result) { @@ -428,21 +452,23 @@ class TextBufferSearcher : public Nan::AsyncWorker { } } - void HandleOKCallback() { + void OnOK() override{ + auto env = Env(); delete snapshot; - Local argv[] = {Nan::Null(), encode_ranges(matches)}; - callback->Call(2, argv, async_resource); + snapshot = nullptr; + Callback().Call({env.Null(), encode_ranges(env, matches)}); } }; -void TextBufferWrapper::find_sync(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::find_sync(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; const Regex *regex = RegexWrapper::regex_from_js(info[0]); if (regex) { optional search_range; - if (info[1]->IsObject()) { + if (info[1].IsObject()) { search_range = RangeWrapper::range_from_js(info[1]); - if (!search_range) return; + if (!search_range) return env.Null(); } auto match = text_buffer.find( @@ -452,18 +478,21 @@ void TextBufferWrapper::find_sync(const Nan::FunctionCallbackInfo &info) vector matches; if (match) matches.push_back(*match); - info.GetReturnValue().Set(encode_ranges(matches)); + return encode_ranges(env, matches); } + + return env.Undefined(); } -void TextBufferWrapper::find_all_sync(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::find_all_sync(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; const Regex *regex = RegexWrapper::regex_from_js(info[0]); if (regex) { optional search_range; - if (info[1]->IsObject()) { + if (info[1].IsObject()) { search_range = RangeWrapper::range_from_js(info[1]); - if (!search_range) return; + if (!search_range) return env.Null(); } vector matches = text_buffer.find_all( @@ -471,93 +500,106 @@ void TextBufferWrapper::find_all_sync(const Nan::FunctionCallbackInfo &in search_range ? *search_range : Range::all_inclusive() ); - info.GetReturnValue().Set(encode_ranges(matches)); + return encode_ranges(env, matches); } + + return env.Undefined(); } -void TextBufferWrapper::find_and_mark_all_sync(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::find_and_mark_all_sync(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; MarkerIndex *marker_index = MarkerIndexWrapper::from_js(info[0]); - if (!marker_index) return; - auto next_id = Nan::To(info[1]); - if (!next_id.IsJust()) return; - if (!info[2]->IsBoolean()) return; - bool exclusive = Nan::To(info[2]).FromMaybe(false); + if (!marker_index) return env.Undefined(); + + if (!info[1].IsNumber()) { + return env.Undefined(); + } + auto next_id = info[1].As().Uint32Value(); + + if (!info[2].IsBoolean()) { + return env.Undefined(); + } + bool exclusive = info[2].As().Value(); + if (info.Length() < 4) return env.Undefined(); const Regex *regex = RegexWrapper::regex_from_js(info[3]); if (regex) { optional search_range; - if (info[4]->IsObject()) { + if (info.Length() > 4 && info[4].IsObject()) { search_range = RangeWrapper::range_from_js(info[4]); - if (!search_range) return; + if (!search_range) return env.Undefined(); } unsigned count = text_buffer.find_and_mark_all( *marker_index, - next_id.FromJust(), + next_id, exclusive, *regex, search_range ? *search_range : Range::all_inclusive() ); - info.GetReturnValue().Set(Nan::New(count)); + return Number::New(env, count); } + + return env.Undefined(); } -void TextBufferWrapper::find(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - auto callback = new Nan::Callback(info[1].As()); +void TextBufferWrapper::find(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + auto callback = info[1].As(); const Regex *regex = RegexWrapper::regex_from_js(info[0]); if (regex) { optional search_range; - if (info[2]->IsObject()) { + if (info[2].IsObject()) { search_range = RangeWrapper::range_from_js(info[2]); if (!search_range) return; } - Nan::AsyncQueueWorker(new TextBufferSearcher( + + auto async_worker = new TextBufferSearcher( callback, text_buffer.create_snapshot(), regex, - search_range ? *search_range : Range::all_inclusive(), - info[0] - )); + search_range ? *search_range : Range::all_inclusive() + ); + async_worker->Queue(); } } -void TextBufferWrapper::find_all(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - auto callback = new Nan::Callback(info[1].As()); +void TextBufferWrapper::find_all(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + auto callback = info[1].As(); const Regex *regex = RegexWrapper::regex_from_js(info[0]); if (regex) { optional search_range; - if (info[2]->IsObject()) { + if (info[2].IsObject()) { search_range = RangeWrapper::range_from_js(info[2]); if (!search_range) return; } - Nan::AsyncQueueWorker(new TextBufferSearcher( + auto async_worker = new TextBufferSearcher( callback, text_buffer.create_snapshot(), regex, - search_range ? *search_range : Range::all_inclusive(), - info[0] - )); + search_range ? *search_range : Range::all_inclusive() + ); + async_worker->Queue(); } } -void TextBufferWrapper::find_words_with_subsequence_in_range(const Nan::FunctionCallbackInfo &info) { - class FindWordsWithSubsequenceInRangeWorker : public Nan::AsyncWorker, public CancellableWorker { - Nan::Persistent buffer; +void TextBufferWrapper::find_words_with_subsequence_in_range(const CallbackInfo &info) { + class FindWordsWithSubsequenceInRangeWorker : public Napi::AsyncWorker { + Napi::ObjectReference buffer; const TextBuffer::Snapshot *snapshot; const u16string query; const u16string extra_word_characters; const size_t max_count; const Range range; vector result; - uv_rwlock_t snapshot_lock; + TextBufferWrapper *text_buffer_wrapper; public: - FindWordsWithSubsequenceInRangeWorker(Local buffer, - Nan::Callback *completion_callback, + FindWordsWithSubsequenceInRangeWorker(Object buffer, + Function &completion_callback, const u16string query, const u16string extra_word_characters, const size_t max_count, @@ -567,59 +609,57 @@ void TextBufferWrapper::find_words_with_subsequence_in_range(const Nan::Function extra_word_characters{extra_word_characters}, max_count{max_count}, range(range) { - uv_rwlock_init(&snapshot_lock); - this->buffer.Reset(buffer); - auto &text_buffer = Nan::ObjectWrap::Unwrap(buffer)->text_buffer; - snapshot = text_buffer.create_snapshot(); + this->buffer.Reset(buffer, 1); + text_buffer_wrapper = TextBufferWrapper::Unwrap(buffer); + snapshot = text_buffer_wrapper->text_buffer.create_snapshot(); } ~FindWordsWithSubsequenceInRangeWorker() { - uv_rwlock_destroy(&snapshot_lock); + if (snapshot) { + delete snapshot; + snapshot = nullptr; + } } - void Execute() { - uv_rwlock_rdlock(&snapshot_lock); - if (!snapshot) { - uv_rwlock_rdunlock(&snapshot_lock); - return; + void OnWorkComplete(Napi::Env env, napi_status status) override { + if (status == napi_cancelled) { + Callback().Call({env.Null()}); } - result = snapshot->find_words_with_subsequence_in_range(query, extra_word_characters, range); - uv_rwlock_rdunlock(&snapshot_lock); + + AsyncWorker::OnWorkComplete(env, status); } - void CancelIfQueued() { - int lock_status = uv_rwlock_trywrlock(&snapshot_lock); - if (lock_status == 0) { - delete snapshot; - snapshot = nullptr; - uv_rwlock_wrunlock(&snapshot_lock); + void Execute() override { + { + std::lock_guard guard(text_buffer_wrapper->outstanding_workers_mutex); + text_buffer_wrapper->outstanding_workers.erase(this); + } + + if (!snapshot) { + return; } + result = snapshot->find_words_with_subsequence_in_range(query, extra_word_characters, range); } - void HandleOKCallback() { + void OnOK() override { + auto env = Env(); if (!snapshot) { - Local argv[] = {Nan::Null()}; - callback->Call(1, argv, async_resource); + Callback().Call({env.Null()}); return; } delete snapshot; - auto text_buffer_wrapper = Nan::ObjectWrap::Unwrap(Nan::New(buffer)); - text_buffer_wrapper->outstanding_workers.erase(this); + snapshot = nullptr; - Local js_matches_array = Nan::New(); + Array js_matches_array = Array::New(env); uint32_t positions_buffer_size = 0; for (const auto &subsequence_match : result) { positions_buffer_size += sizeof(uint32_t) + subsequence_match.positions.size() * sizeof(Point); } - auto positions_buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), positions_buffer_size); - #if (V8_MAJOR_VERSION < 8) - uint32_t *positions_data = reinterpret_cast(positions_buffer->GetContents().Data()); - #else - uint32_t *positions_data = reinterpret_cast(positions_buffer->GetBackingStore()->Data()); - #endif + auto positions_buffer = ArrayBuffer::New(env, positions_buffer_size); + uint32_t *positions_data = reinterpret_cast(positions_buffer.Data()); uint32_t positions_array_index = 0; for (size_t i = 0; i < result.size() && i < max_count; i++) { @@ -632,25 +672,22 @@ void TextBufferWrapper::find_words_with_subsequence_in_range(const Nan::Function bytes_to_copy ); positions_array_index += bytes_to_copy / sizeof(uint32_t); - Nan::Set(js_matches_array, i, SubsequenceMatchWrapper::from_subsequence_match(match)); + js_matches_array[i] = SubsequenceMatchWrapper::from_subsequence_match(env, match); } - auto positions_array = v8::Uint32Array::New(positions_buffer, 0, positions_buffer_size / sizeof(uint32_t)); - Local argv[] = {js_matches_array, positions_array}; - callback->Call(2, argv, async_resource); + auto positions_array = Uint32Array::New(env, positions_buffer_size / sizeof(uint32_t), positions_buffer, 0); + Callback().Call({js_matches_array, positions_array}); } }; - auto query = string_conversion::string_from_js(info[0]); auto extra_word_characters = string_conversion::string_from_js(info[1]); auto max_count = number_conversion::number_from_js(info[2]); auto range = RangeWrapper::range_from_js(info[3]); - auto callback = new Nan::Callback(info[4].As()); + Function callback = info[4].As(); if (query && extra_word_characters && max_count && range && callback) { - auto js_buffer = info.This(); - auto text_buffer_wrapper = Nan::ObjectWrap::Unwrap(js_buffer); + Napi::Object js_buffer = info.This().As(); auto worker = new FindWordsWithSubsequenceInRangeWorker( js_buffer, @@ -661,32 +698,37 @@ void TextBufferWrapper::find_words_with_subsequence_in_range(const Nan::Function *range ); - text_buffer_wrapper->outstanding_workers.insert(worker); - Nan::AsyncQueueWorker(worker); + { + std::lock_guard guard(outstanding_workers_mutex); + this->outstanding_workers.insert(worker); + } + worker->Queue(); } else { - Nan::ThrowError("Invalid arguments"); + Napi::Error::New(Env(), "Invalid arguments").ThrowAsJavaScriptException(); } } -void TextBufferWrapper::is_modified(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - info.GetReturnValue().Set(Nan::New(text_buffer.is_modified())); +Napi::Value TextBufferWrapper::is_modified(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + return Boolean::New(info.Env(), text_buffer.is_modified()); } static const int INVALID_ENCODING = -1; -struct Error { - int number; - const char *syscall; -}; +namespace textbuffer { + struct Error { + int number; + const char *syscall; + }; -static Local error_to_js(Error error, string encoding_name, string file_name) { - if (error.number == INVALID_ENCODING) { - return Nan::Error(("Invalid encoding name: " + encoding_name).c_str()); - } else { - return node::ErrnoException( - v8::Isolate::GetCurrent(), error.number, error.syscall, error.syscall, file_name.c_str() - ); + static Napi::Value error_to_js(Env env, Error error, string encoding_name, string file_name) { + if (error.number == INVALID_ENCODING) { + return Napi::Error::New(env, ("Invalid encoding name: " + encoding_name).c_str()).Value(); + } else { + return Napi::Value(env, JsValueFromV8LocalValue(node::ErrnoException( + v8::Isolate::GetCurrent(), error.number, error.syscall, error.syscall, file_name.c_str() + ))); + } } } @@ -694,24 +736,24 @@ template static u16string load_file( const string &file_name, const string &encoding_name, - optional *error, + optional *error, const Callback &callback ) { auto conversion = transcoding_from(encoding_name.c_str()); if (!conversion) { - *error = Error{INVALID_ENCODING, nullptr}; + *error = textbuffer::Error{INVALID_ENCODING, nullptr}; return u""; } FILE *file = open_file(file_name, "rb"); if (!file) { - *error = Error{errno, "open"}; + *error = textbuffer::Error{errno, "open"}; return u""; } size_t file_size = get_file_size(file); if (file_size == static_cast(-1)) { - *error = Error{errno, "stat"}; + *error = textbuffer::Error{errno, "stat"}; return u""; } @@ -727,7 +769,7 @@ static u16string load_file( callback(percent_done); } )) { - *error = Error{errno, "read"}; + *error = textbuffer::Error{errno, "read"}; } fclose(file); @@ -735,14 +777,13 @@ static u16string load_file( } class Loader { - Nan::Callback *progress_callback; - Nan::AsyncResource *async_resource; + FunctionReference progress_callback; TextBuffer *buffer; TextBuffer::Snapshot *snapshot; string file_name; string encoding_name; optional loaded_text; - optional error; + optional error; Patch patch; bool force; bool compute_patch; @@ -750,53 +791,53 @@ class Loader { public: bool cancelled; - Loader(Nan::Callback *progress_callback, Nan::AsyncResource *async_resource, + Loader(FunctionReference progress_callback, TextBuffer *buffer, TextBuffer::Snapshot *snapshot, string &&file_name, string &&encoding_name, bool force, bool compute_patch) : - progress_callback{progress_callback}, - async_resource{async_resource}, + progress_callback{move(progress_callback)}, buffer{buffer}, snapshot{snapshot}, file_name{move(file_name)}, encoding_name{move(encoding_name)}, force{force}, compute_patch{compute_patch}, - cancelled{false} {} + cancelled{false} { + } - Loader(Nan::Callback *progress_callback, Nan::AsyncResource *async_resource, + Loader(FunctionReference progress_callback, TextBuffer *buffer, TextBuffer::Snapshot *snapshot, Text &&text, bool force, bool compute_patch) : - progress_callback{progress_callback}, + progress_callback{move(progress_callback)}, buffer{buffer}, snapshot{snapshot}, loaded_text{move(text)}, force{force}, compute_patch{compute_patch}, - cancelled{false} {} - - ~Loader() { - if (progress_callback) delete progress_callback; - } + cancelled{false} { + } - template - void Execute(const Callback &callback) { + template + void Execute(const Function &callback) { if (!loaded_text) loaded_text = Text{load_file(file_name, encoding_name, &error, callback)}; if (!error && compute_patch) patch = text_diff(snapshot->base_text(), *loaded_text); } - pair, Local> Finish(Nan::AsyncResource* caller_async_resource = nullptr) { + pair Finish(Napi::Env env) { if (error) { delete snapshot; - return {error_to_js(*error, encoding_name, file_name), Nan::Undefined()}; + snapshot = nullptr; + return {textbuffer::error_to_js(env, *error, encoding_name, file_name), env.Undefined()}; } if (cancelled || (!force && buffer->is_modified())) { delete snapshot; - return {Nan::Null(), Nan::Null()}; + snapshot = nullptr; + return {env.Null(), env.Null()}; } Patch inverted_changes = buffer->get_inverted_changes(snapshot); delete snapshot; + snapshot = nullptr; if (compute_patch && inverted_changes.get_change_count() > 0) { inverted_changes.combine(patch); @@ -804,26 +845,19 @@ class Loader { } bool has_changed; - Local patch_wrapper; + Value patch_wrapper; if (compute_patch) { has_changed = !compute_patch || patch.get_change_count() > 0; - patch_wrapper = PatchWrapper::from_patch(move(patch)); + patch_wrapper = PatchWrapper::from_patch(env, move(patch)); } else { has_changed = true; - patch_wrapper = Nan::Null(); + patch_wrapper = env.Null(); } - if (progress_callback) { - Local argv[] = {Nan::New(100), patch_wrapper}; - MaybeLocal progress_result; - Nan::AsyncResource* resource = caller_async_resource ? caller_async_resource : async_resource; - if (resource) { - progress_result = progress_callback->Call(2, argv, resource); - } else { - progress_result = Nan::Call(*progress_callback, 2, argv); - } - if (!progress_result.IsEmpty() && progress_result.ToLocalChecked()->IsFalse()) { - return {Nan::Null(), Nan::Null()}; + if (!progress_callback.IsEmpty()) { + Napi::Value progress_result = progress_callback.Call({Number::New(env, 100), patch_wrapper}); + if (!progress_result.IsEmpty() && progress_result.IsBoolean() && !progress_result.As().Value()) { + return {env.Null(), env.Null()}; } } @@ -833,84 +867,81 @@ class Loader { buffer->flush_changes(); } - return {Nan::Null(), patch_wrapper}; + return {env.Null(), patch_wrapper}; } void CallProgressCallback(size_t percent_done) { - if (!cancelled && progress_callback) { - Nan::HandleScope scope; - Local argv[] = {Nan::New(static_cast(percent_done))}; - MaybeLocal progress_result; - if (async_resource) { - progress_result = progress_callback->Call(1, argv, async_resource); - } else { - progress_result = Nan::Call(*progress_callback, 1, argv); - } - - if (!progress_result.IsEmpty() && progress_result.ToLocalChecked()->IsFalse()) cancelled = true; + if (!cancelled && !progress_callback.IsEmpty()) { + auto env = progress_callback.Env(); + Napi::Value progress_result = progress_callback.Call({Number::New(env, static_cast(percent_done))}); + if (!progress_result.IsEmpty() && progress_result.IsBoolean() && !progress_result.As().Value()) cancelled = true; } } }; -class LoadWorker : public Nan::AsyncProgressWorkerBase { +class LoadWorker : public AsyncProgressWorker { Loader loader; public: - LoadWorker(Nan::Callback *completion_callback, Nan::Callback *progress_callback, + LoadWorker(Function &completion_callback, FunctionReference progress_callback, TextBuffer *buffer, TextBuffer::Snapshot *snapshot, string &&file_name, string &&encoding_name, bool force, bool compute_patch) : - AsyncProgressWorkerBase(completion_callback, "TextBuffer.load"), - loader(progress_callback, async_resource, buffer, snapshot, move(file_name), move(encoding_name), force, compute_patch) {} + AsyncProgressWorker(completion_callback, "TextBuffer.load"), + loader(move(progress_callback), buffer, snapshot, move(file_name), move(encoding_name), force, compute_patch) {} - LoadWorker(Nan::Callback *completion_callback, Nan::Callback *progress_callback, + LoadWorker(Function &completion_callback, FunctionReference progress_callback, TextBuffer *buffer, TextBuffer::Snapshot *snapshot, Text &&text, bool force, bool compute_patch) : - AsyncProgressWorkerBase(completion_callback, "TextBuffer.load"), - loader(progress_callback, async_resource, buffer, snapshot, move(text), force, compute_patch) {} + AsyncProgressWorker(completion_callback, "TextBuffer.load"), + loader(move(progress_callback), buffer, snapshot, move(text), force, compute_patch) {} - void Execute(const Nan::AsyncProgressWorkerBase::ExecutionProgress &progress) { - loader.Execute([&progress](size_t percent_done) { + void Execute(const ExecutionProgress &progress) override { + loader.Execute([&progress](uint32_t percent_done) { progress.Send(&percent_done, 1); }); } - void HandleProgressCallback(const size_t *percent_done, size_t count) { - if (percent_done) loader.CallProgressCallback(*percent_done); + void OnProgress(const uint32_t *percent_done, size_t count) override { + if (percent_done) { + loader.CallProgressCallback(*percent_done); + } } - void HandleOKCallback() { - auto results = loader.Finish(async_resource); - Local argv[] = {results.first, results.second}; - callback->Call(2, argv, async_resource); + void OnOK() override { + auto results = loader.Finish(Env()); + Callback().Call({results.first, results.second}); } }; -void TextBufferWrapper::load_sync(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::load_sync(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; if (text_buffer.is_modified()) { - info.GetReturnValue().Set(Nan::Null()); - return; + return env.Undefined(); } - Local js_file_path; - if (!Nan::To(info[0]).ToLocal(&js_file_path)) return; - string file_path = *Nan::Utf8String(js_file_path); + if (!info[0].IsString()) { + return env.Undefined(); + } - Local js_encoding_name; - if (!Nan::To(info[1]).ToLocal(&js_encoding_name)) return; - string encoding_name = *Nan::Utf8String(js_encoding_name); + String js_file_path = info[0].As(); + string file_path =js_file_path.Utf8Value(); - Nan::Callback *progress_callback = nullptr; - if (info[2]->IsFunction()) { - progress_callback = new Nan::Callback(info[2].As()); + if (!info[1].IsString()) { + return env.Undefined(); } - Nan::HandleScope scope; + String js_encoding_name = info[1].As(); + string encoding_name = js_encoding_name.Utf8Value(); + + FunctionReference progress_callback; + if (info[2].IsFunction()) { + progress_callback = Persistent(info[2].As()); + } Loader worker( - progress_callback, - nullptr, + move(progress_callback), &text_buffer, text_buffer.create_snapshot(), move(file_path), @@ -923,54 +954,50 @@ void TextBufferWrapper::load_sync(const Nan::FunctionCallbackInfo &info) worker.CallProgressCallback(percent_done); }); - auto results = worker.Finish(); - if (results.first->IsNull()) { - info.GetReturnValue().Set(results.second); + auto results = worker.Finish(env); + if (results.first.IsNull()) { + return results.second; } else { - Nan::ThrowError(results.first); + results.first.As().ThrowAsJavaScriptException(); } + return env.Undefined(); } -void TextBufferWrapper::load(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +void TextBufferWrapper::load(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; bool force = false; - if (info[2]->IsTrue()) force = true; + if (info[2].IsBoolean() && info[2].As()) force = true; bool compute_patch = true; - if (info[3]->IsFalse()) compute_patch = false; + if (info[3].IsBoolean() && !info[3].As()) compute_patch = false; if (!force && text_buffer.is_modified()) { - Local argv[] = {Nan::Null(), Nan::Null()}; auto callback = info[0].As(); - #if (V8_MAJOR_VERSION > 9 || (V8_MAJOR_VERSION == 9 && V8_MINOR_VERION > 4)) - Nan::Call(callback, callback->GetCreationContext().ToLocalChecked()->Global(), 2, argv); - #else - Nan::Call(callback, callback->CreationContext()->Global(), 2, argv); - #endif + callback.Call({env.Null(), env.Null()}); return; } - Nan::Callback *completion_callback = new Nan::Callback(info[0].As()); + Function completion_callback = info[0].As(); - Nan::Callback *progress_callback = nullptr; - if (info[1]->IsFunction()) { - progress_callback = new Nan::Callback(info[1].As()); + FunctionReference progress_callback; + if (info[1].IsFunction()) { + progress_callback = Persistent(info[1].As()); } LoadWorker *worker; - if (info[4]->IsString()) { - Local js_file_path; - if (!Nan::To(info[4]).ToLocal(&js_file_path)) return; - string file_path = *Nan::Utf8String(js_file_path); + if (info[4].IsString()) { + String js_file_path =info[4].As(); + string file_path = js_file_path.Utf8Value(); - Local js_encoding_name; - if (!Nan::To(info[5]).ToLocal(&js_encoding_name)) return; - string encoding_name = *Nan::Utf8String(info[5].As()); + if (!info[5].IsString()) return; + String js_encoding_name = info[5].As(); + string encoding_name = js_encoding_name.Utf8Value(); worker = new LoadWorker( completion_callback, - progress_callback, + move(progress_callback), &text_buffer, text_buffer.create_snapshot(), move(file_path), @@ -979,10 +1006,10 @@ void TextBufferWrapper::load(const Nan::FunctionCallbackInfo &info) { compute_patch ); } else { - auto text_writer = Nan::ObjectWrap::Unwrap(Nan::To(info[4]).ToLocalChecked()); + auto text_writer = TextWriter::Unwrap(info[4].As()); worker = new LoadWorker( completion_callback, - progress_callback, + move(progress_callback), &text_buffer, text_buffer.create_snapshot(), text_writer->get_text(), @@ -991,18 +1018,18 @@ void TextBufferWrapper::load(const Nan::FunctionCallbackInfo &info) { ); } - Nan::AsyncQueueWorker(worker); + worker->Queue(); } -class BaseTextComparisonWorker : public Nan::AsyncWorker { +class BaseTextComparisonWorker : public AsyncWorker { TextBuffer::Snapshot *snapshot; string file_name; string encoding_name; - optional error; + optional error; bool result; public: - BaseTextComparisonWorker(Nan::Callback *completion_callback, TextBuffer::Snapshot *snapshot, + BaseTextComparisonWorker(Function &completion_callback, TextBuffer::Snapshot *snapshot, string &&file_name, string &&encoding_name) : AsyncWorker(completion_callback, "TextBuffer.baseTextMatchesFile"), snapshot{snapshot}, @@ -1010,80 +1037,74 @@ class BaseTextComparisonWorker : public Nan::AsyncWorker { encoding_name{move(encoding_name)}, result{false} {} - void Execute() { + void Execute() override { u16string file_contents = load_file(file_name, encoding_name, &error, [](size_t progress) {}); result = std::equal(file_contents.begin(), file_contents.end(), snapshot->base_text().begin()); } - void HandleOKCallback() { + void OnOK() override { + auto env = Env(); delete snapshot; + snapshot = nullptr; if (error) { - Local argv[] = {error_to_js(*error, encoding_name, file_name)}; - callback->Call(1, argv, async_resource); + Callback().Call({error_to_js(env, *error, encoding_name, file_name)}); } else { - Local argv[] = {Nan::Null(), Nan::New(result)}; - callback->Call(2, argv, async_resource); + Callback().Call({env.Null(), Boolean::New(env, result)}); } } }; -void TextBufferWrapper::base_text_matches_file(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +void TextBufferWrapper::base_text_matches_file(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; - if (info[1]->IsString()) { - Nan::Callback *completion_callback = new Nan::Callback(info[0].As()); - Local js_file_path; - if (!Nan::To(info[1]).ToLocal(&js_file_path)) return; - string file_path = *Nan::Utf8String(js_file_path); + if (info[1].IsString()) { + Function completion_callback = info[0].As(); + String js_file_path = info[1].As(); + string file_path = js_file_path.Utf8Value(); - Local js_encoding_name; - if (!Nan::To(info[2]).ToLocal(&js_encoding_name)) return; - string encoding_name = *Nan::Utf8String(info[2].As()); + if (!info[2].IsString()) return; + String js_encoding_name = info[2].As(); + string encoding_name = js_encoding_name.Utf8Value(); - Nan::AsyncQueueWorker(new BaseTextComparisonWorker( + (new BaseTextComparisonWorker( completion_callback, text_buffer.create_snapshot(), move(file_path), move(encoding_name) - )); + ))->Queue(); } else { - auto file_contents = Nan::ObjectWrap::Unwrap(Nan::To(info[1]).ToLocalChecked())->get_text(); + auto file_contents = TextWriter::Unwrap(info[1].As())->get_text(); bool result = std::equal(file_contents.begin(), file_contents.end(), text_buffer.base_text().begin()); - Local argv[] = {Nan::Null(), Nan::New(result)}; auto callback = info[0].As(); - - #if (V8_MAJOR_VERSION > 9 || (V8_MAJOR_VERSION == 9 && V8_MINOR_VERION > 4)) - Nan::Call(callback, callback->GetCreationContext().ToLocalChecked()->Global(), 2, argv); - #else - Nan::Call(callback, callback->CreationContext()->Global(), 2, argv); - #endif + callback.Call({env.Null(), Boolean::New(env, result)}); } } -class SaveWorker : public Nan::AsyncWorker { +class SaveWorker : public AsyncWorker { TextBuffer::Snapshot *snapshot; string file_name; string encoding_name; - optional error; + optional error; public: - SaveWorker(Nan::Callback *completion_callback, TextBuffer::Snapshot *snapshot, + SaveWorker(Function &completion_callback, TextBuffer::Snapshot *snapshot, string &&file_name, string &&encoding_name) : AsyncWorker(completion_callback, "TextBuffer.save"), snapshot{snapshot}, file_name{file_name}, encoding_name(encoding_name) {} - void Execute() { + void Execute() override { auto conversion = transcoding_to(encoding_name.c_str()); if (!conversion) { - error = Error{INVALID_ENCODING, nullptr}; + error = textbuffer::Error{INVALID_ENCODING, nullptr}; return; } FILE *file = open_file(file_name, "wb+"); if (!file) { - error = Error{errno, "open"}; + error = textbuffer::Error{errno, "open"}; return; } @@ -1096,7 +1117,7 @@ class SaveWorker : public Nan::AsyncWorker { file, output_buffer )) { - error = Error{errno, "write"}; + error = textbuffer::Error{errno, "write"}; fclose(file); return; } @@ -1105,104 +1126,112 @@ class SaveWorker : public Nan::AsyncWorker { fclose(file); } - Local Finish() { + Value Finish() { + auto env = Env(); if (error) { delete snapshot; - return error_to_js(*error, encoding_name, file_name); + snapshot = nullptr; + return error_to_js(env, *error, encoding_name, file_name); } else { snapshot->flush_preceding_changes(); delete snapshot; - return Nan::Null(); + snapshot = nullptr; + return env.Null(); } } - void HandleOKCallback() { - Local argv[] = {Finish()}; - callback->Call(1, argv, async_resource); + void OnOK() override { + Callback().Call({Finish()}); } }; -void TextBufferWrapper::save(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +void TextBufferWrapper::save(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; - Local js_file_path; - if (!Nan::To(info[0]).ToLocal(&js_file_path)) return; - string file_path = *Nan::Utf8String(js_file_path); + if (!info[0].IsString()) return; + String js_file_path = info[0].As(); + string file_path = js_file_path.Utf8Value(); - Local js_encoding_name; - if (!Nan::To(info[1]).ToLocal(&js_encoding_name)) return; - string encoding_name = *Nan::Utf8String(info[1].As()); + if (!info[1].IsString()) return; + String js_encoding_name = info[1].As(); + string encoding_name = js_encoding_name.Utf8Value(); - Nan::Callback *completion_callback = new Nan::Callback(info[2].As()); - Nan::AsyncQueueWorker(new SaveWorker( + Function completion_callback = info[2].As(); + (new SaveWorker( completion_callback, text_buffer.create_snapshot(), move(file_path), move(encoding_name) - )); + ))->Queue(); } -void TextBufferWrapper::serialize_changes(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::serialize_changes(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; static vector output; output.clear(); Serializer serializer(output); text_buffer.serialize_changes(serializer); - Local result; - if (Nan::CopyBuffer(reinterpret_cast(output.data()), output.size()).ToLocal(&result)) { - info.GetReturnValue().Set(result); - } + auto result = Buffer::Copy(env, reinterpret_cast(output.data()), output.size()); + return result; } -void TextBufferWrapper::deserialize_changes(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - if (info[0]->IsUint8Array()) { - auto *data = node::Buffer::Data(info[0]); +void TextBufferWrapper::deserialize_changes(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; + if (info[0].IsTypedArray()) { + Uint8Array array = info[0].As(); + uint8_t *data = array.Data(); static vector input; - input.assign(data, data + node::Buffer::Length(info[0])); + input.assign(data, data + array.ByteLength()); Deserializer deserializer(input); text_buffer.deserialize_changes(deserializer); } } -void TextBufferWrapper::reset(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +void TextBufferWrapper::reset(const CallbackInfo &info) { + auto &text_buffer = this->text_buffer; auto text = string_conversion::string_from_js(info[0]); if (text) { text_buffer.reset(move(*text)); } } -void TextBufferWrapper::base_text_digest(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::base_text_digest(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; std::stringstream stream; stream << std::setfill('0') << std::setw(2 * sizeof(size_t)) << std::hex << text_buffer.base_text().digest(); - Local result; - if (Nan::New(stream.str()).ToLocal(&result)) { - info.GetReturnValue().Set(result); - } + String result = String::New(env, stream.str()); + return result; } -void TextBufferWrapper::get_snapshot(const Nan::FunctionCallbackInfo &info) { - Nan::HandleScope scope; - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; +Napi::Value TextBufferWrapper::get_snapshot(const CallbackInfo &info) { + auto env = info.Env(); + Napi::HandleScope scope(env); + auto &text_buffer = this->text_buffer; auto snapshot = text_buffer.create_snapshot(); - info.GetReturnValue().Set(TextBufferSnapshotWrapper::new_instance(info.This(), reinterpret_cast(snapshot))); + return TextBufferSnapshotWrapper::new_instance(env, info.This().As(), snapshot); } -void TextBufferWrapper::dot_graph(const Nan::FunctionCallbackInfo &info) { - auto &text_buffer = Nan::ObjectWrap::Unwrap(info.This())->text_buffer; - info.GetReturnValue().Set(Nan::New(text_buffer.get_dot_graph()).ToLocalChecked()); +Napi::Value TextBufferWrapper::dot_graph(const CallbackInfo &info) { + auto env = info.Env(); + auto &text_buffer = this->text_buffer; + return String::New(env, text_buffer.get_dot_graph()); } void TextBufferWrapper::cancel_queued_workers() { - for (auto worker : outstanding_workers) { - worker->CancelIfQueued(); + std::lock_guard guard(outstanding_workers_mutex); + auto env = Env(); + + for (auto worker: outstanding_workers) { + worker->Cancel(); + if (env.IsExceptionPending()) { + auto e = env.GetAndClearPendingException(); + } } - outstanding_workers.clear(); } diff --git a/src/bindings/text-buffer-wrapper.h b/src/bindings/text-buffer-wrapper.h index ee2261ba..2245fe54 100644 --- a/src/bindings/text-buffer-wrapper.h +++ b/src/bindings/text-buffer-wrapper.h @@ -1,58 +1,62 @@ #ifndef SUPERSTRING_TEXT_BUFFER_WRAPPER_H #define SUPERSTRING_TEXT_BUFFER_WRAPPER_H -#include "nan.h" -#include "text-buffer.h" #include +#include "napi.h" +#include "text-buffer.h" + class CancellableWorker { public: virtual void CancelIfQueued() = 0; }; -class TextBufferWrapper : public Nan::ObjectWrap { +class TextBufferWrapper : public Napi::ObjectWrap { public: - static void init(v8::Local exports); + static void init(Napi::Object exports); TextBuffer text_buffer; - std::unordered_set outstanding_workers; + std::unordered_set outstanding_workers; + std::mutex outstanding_workers_mutex; + + TextBufferWrapper(const Napi::CallbackInfo &info); private: - static void construct(const Nan::FunctionCallbackInfo &info); - static void get_length(const Nan::FunctionCallbackInfo &info); - static void get_extent(const Nan::FunctionCallbackInfo &info); - static void get_line_count(const Nan::FunctionCallbackInfo &info); - static void has_astral(const Nan::FunctionCallbackInfo &info); - static void get_text(const Nan::FunctionCallbackInfo &info); - static void get_character_at_position(const Nan::FunctionCallbackInfo &info); - static void get_text_in_range(const Nan::FunctionCallbackInfo &info); - static void set_text(const Nan::FunctionCallbackInfo &info); - static void set_text_in_range(const Nan::FunctionCallbackInfo &info); - static void line_for_row(const Nan::FunctionCallbackInfo &info); - static void line_length_for_row(const Nan::FunctionCallbackInfo &info); - static void line_ending_for_row(const Nan::FunctionCallbackInfo &info); - static void get_lines(const Nan::FunctionCallbackInfo &info); - static void character_index_for_position(const Nan::FunctionCallbackInfo &info); - static void position_for_character_index(const Nan::FunctionCallbackInfo &info); - static void find(const Nan::FunctionCallbackInfo &info); - static void find_sync(const Nan::FunctionCallbackInfo &info); - static void find_all(const Nan::FunctionCallbackInfo &info); - static void find_all_sync(const Nan::FunctionCallbackInfo &info); - static void find_and_mark_all_sync(const Nan::FunctionCallbackInfo &info); - static void find_words_with_subsequence_in_range(const Nan::FunctionCallbackInfo &info); - static void is_modified(const Nan::FunctionCallbackInfo &info); - static void load(const Nan::FunctionCallbackInfo &info); - static void base_text_matches_file(const Nan::FunctionCallbackInfo &info); - static void save(const Nan::FunctionCallbackInfo &info); - static void load_sync(const Nan::FunctionCallbackInfo &info); - static void save_sync(const Nan::FunctionCallbackInfo &info); - static void serialize_changes(const Nan::FunctionCallbackInfo &info); - static void deserialize_changes(const Nan::FunctionCallbackInfo &info); - static void reset(const Nan::FunctionCallbackInfo &info); - static void base_text_digest(const Nan::FunctionCallbackInfo &info); - static void get_snapshot(const Nan::FunctionCallbackInfo &info); - static void dot_graph(const Nan::FunctionCallbackInfo &info); + Napi::Value get_length(const Napi::CallbackInfo &info); + Napi::Value get_extent(const Napi::CallbackInfo &info); + Napi::Value get_line_count(const Napi::CallbackInfo &info); + Napi::Value has_astral(const Napi::CallbackInfo &info); + Napi::Value get_text(const Napi::CallbackInfo &info); + Napi::Value get_character_at_position(const Napi::CallbackInfo &info); + Napi::Value get_text_in_range(const Napi::CallbackInfo &info); + void set_text(const Napi::CallbackInfo &info); + void set_text_in_range(const Napi::CallbackInfo &info); + Napi::Value line_for_row(const Napi::CallbackInfo &info); + Napi::Value line_length_for_row(const Napi::CallbackInfo &info); + Napi::Value line_ending_for_row(const Napi::CallbackInfo &info); + Napi::Value get_lines(const Napi::CallbackInfo &info); + Napi::Value character_index_for_position(const Napi::CallbackInfo &info); + Napi::Value position_for_character_index(const Napi::CallbackInfo &info); + void find(const Napi::CallbackInfo &info); + Napi::Value find_sync(const Napi::CallbackInfo &info); + void find_all(const Napi::CallbackInfo &info); + Napi::Value find_all_sync(const Napi::CallbackInfo &info); + Napi::Value find_and_mark_all_sync(const Napi::CallbackInfo &info); + void find_words_with_subsequence_in_range(const Napi::CallbackInfo &info); + Napi::Value is_modified(const Napi::CallbackInfo &info); + void load(const Napi::CallbackInfo &info); + void base_text_matches_file(const Napi::CallbackInfo &info); + void save(const Napi::CallbackInfo &info); + Napi::Value load_sync(const Napi::CallbackInfo &info); + Napi::Value save_sync(const Napi::CallbackInfo &info); + Napi::Value serialize_changes(const Napi::CallbackInfo &info); + void deserialize_changes(const Napi::CallbackInfo &info); + void reset(const Napi::CallbackInfo &info); + Napi::Value base_text_digest(const Napi::CallbackInfo &info); + Napi::Value get_snapshot(const Napi::CallbackInfo &info); + Napi::Value dot_graph(const Napi::CallbackInfo &info); void cancel_queued_workers(); + static Napi::FunctionReference constructor; }; #endif // SUPERSTRING_TEXT_BUFFER_WRAPPER_H diff --git a/src/bindings/text-reader.cc b/src/bindings/text-reader.cc index 6abdd1e5..372586a5 100644 --- a/src/bindings/text-reader.cc +++ b/src/bindings/text-reader.cc @@ -5,70 +5,66 @@ using std::move; using std::string; -using namespace v8; +using namespace Napi; -void TextReader::init(Local exports) { - Local constructor_template = Nan::New(construct); - constructor_template->SetClassName(Nan::New("TextReader").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - const auto &prototype_template = constructor_template->PrototypeTemplate(); - Nan::SetTemplate(prototype_template, Nan::New("read").ToLocalChecked(), Nan::New(read), None); - Nan::SetTemplate(prototype_template, Nan::New("end").ToLocalChecked(), Nan::New(end), None); - Nan::SetTemplate(prototype_template, Nan::New("destroy").ToLocalChecked(), Nan::New(destroy), None); - Nan::Set(exports, Nan::New("TextReader").ToLocalChecked(), Nan::GetFunction(constructor_template).ToLocalChecked()); -} +FunctionReference TextReader::constructor; + +void TextReader::init(Object exports) { + auto env = exports.Env(); + Napi::Function func = DefineClass(env, "TextReader", { + InstanceMethod<&TextReader::read>("read"), + InstanceMethod<&TextReader::end>("end"), + InstanceMethod<&TextReader::destroy>("destroy"), + }); -TextReader::TextReader(Local js_buffer, - TextBuffer::Snapshot *snapshot, - EncodingConversion &&conversion) : - snapshot{snapshot}, - slices{snapshot->chunks()}, - slice_index{0}, - text_offset{slices[0].start_offset()}, - conversion{move(conversion)} { - js_text_buffer.Reset(Isolate::GetCurrent(), js_buffer); + TextReader::constructor.Reset(func, 1); + exports.Set("TextReader", func); } TextReader::~TextReader() { if (snapshot) delete snapshot; } -void TextReader::construct(const Nan::FunctionCallbackInfo &info) { - Local js_text_buffer; - if (!Nan::To(info[0]).ToLocal(&js_text_buffer)) return; - auto &text_buffer = Nan::ObjectWrap::Unwrap(js_text_buffer)->text_buffer; - auto snapshot = text_buffer.create_snapshot(); +TextReader::TextReader(const CallbackInfo &info): + ObjectWrap(info), + slice_index{0} { + Object js_buffer = info[0].As(); + auto &text_buffer = TextBufferWrapper::Unwrap(js_buffer)->text_buffer; + snapshot = text_buffer.create_snapshot(); + slices = snapshot->chunks(); + text_offset=slices[0].start_offset(); - Local js_encoding_name; - if (!Nan::To(info[1]).ToLocal(&js_encoding_name)) return; - Nan::Utf8String encoding_name(js_encoding_name); - auto conversion = transcoding_to(*encoding_name); - if (!conversion) { - Nan::ThrowError((string("Invalid encoding name: ") + *encoding_name).c_str()); + String js_encoding_name = info[1].As(); + std::string encoding_name = js_encoding_name.Utf8Value(); + auto _conversion = transcoding_to(encoding_name.c_str()); + if (!_conversion) { + Error::New(Env(), (string("Invalid encoding name: ") + encoding_name).c_str()).ThrowAsJavaScriptException(); return; } + conversion.reset(new EncodingConversion(move(*_conversion))); - TextReader *reader = new TextReader(js_text_buffer, snapshot, move(*conversion)); - reader->Wrap(info.This()); + js_text_buffer.Reset(js_buffer, 1); } -void TextReader::read(const Nan::FunctionCallbackInfo &info) { - TextReader *reader = Nan::ObjectWrap::Unwrap(Nan::To(info.This()).ToLocalChecked()); +Napi::Value TextReader::read(const CallbackInfo &info) { + auto env = info.Env(); + TextReader *reader = this; - if (!info[0]->IsUint8Array()) { - Nan::ThrowError("Expected a buffer"); - return; + if (!info[0].IsTypedArray()) { + Error::New(env, "Expected a buffer").ThrowAsJavaScriptException(); + return env.Undefined(); } - char *buffer = node::Buffer::Data(info[0]); - size_t buffer_length = node::Buffer::Length(info[0]); + auto js_buffer_array = info[0].As(); + char *buffer = reinterpret_cast(js_buffer_array.Data()); + size_t buffer_length = js_buffer_array.ByteLength(); size_t total_bytes_written = 0; for (;;) { if (reader->slice_index == reader->slices.size()) break; TextSlice &slice = reader->slices[reader->slice_index]; size_t end_offset = slice.end_offset(); - size_t bytes_written = reader->conversion.encode( + size_t bytes_written = reader->conversion->encode( slice.text->content, &reader->text_offset, end_offset, @@ -84,11 +80,11 @@ void TextReader::read(const Nan::FunctionCallbackInfo &info) { } } - info.GetReturnValue().Set(Nan::New(total_bytes_written)); + return Number::New(env, total_bytes_written); } -void TextReader::end(const Nan::FunctionCallbackInfo &info) { - TextReader *reader = Nan::ObjectWrap::Unwrap(Nan::To(info.This()).ToLocalChecked()); +void TextReader::end(const CallbackInfo &info) { + TextReader *reader = this; if (reader->snapshot) { reader->snapshot->flush_preceding_changes(); delete reader->snapshot; @@ -96,8 +92,8 @@ void TextReader::end(const Nan::FunctionCallbackInfo &info) { } } -void TextReader::destroy(const Nan::FunctionCallbackInfo &info) { - TextReader *reader = Nan::ObjectWrap::Unwrap(Nan::To(info.This()).ToLocalChecked()); +void TextReader::destroy(const CallbackInfo &info) { + TextReader *reader = this; if (reader->snapshot) { delete reader->snapshot; reader->snapshot = nullptr; diff --git a/src/bindings/text-reader.h b/src/bindings/text-reader.h index 200b6ce6..deadf640 100644 --- a/src/bindings/text-reader.h +++ b/src/bindings/text-reader.h @@ -1,31 +1,29 @@ #ifndef SUPERSTRING_TEXT_READER_H #define SUPERSTRING_TEXT_READER_H -#include "nan.h" +#include "napi.h" #include "text.h" #include "text-buffer.h" #include "encoding-conversion.h" -class TextReader : public Nan::ObjectWrap { +class TextReader : public Napi::ObjectWrap { public: - static void init(v8::Local exports); - -private: - TextReader(v8::Local js_buffer, TextBuffer::Snapshot *snapshot, - EncodingConversion &&conversion); + static void init(Napi::Object exports); + TextReader(const Napi::CallbackInfo &info); ~TextReader(); - static void construct(const Nan::FunctionCallbackInfo &info); - static void read(const Nan::FunctionCallbackInfo &info); - static void end(const Nan::FunctionCallbackInfo &info); - static void destroy(const Nan::FunctionCallbackInfo &info); +private: + Napi::Value read(const Napi::CallbackInfo &info); + void end(const Napi::CallbackInfo &info); + void destroy(const Napi::CallbackInfo &info); - v8::Persistent js_text_buffer; + Napi::ObjectReference js_text_buffer; TextBuffer::Snapshot *snapshot; std::vector slices; size_t slice_index; size_t text_offset; - EncodingConversion conversion; + std::unique_ptr conversion; + static Napi::FunctionReference constructor; }; #endif // SUPERSTRING_TEXT_READER_H diff --git a/src/bindings/text-writer.cc b/src/bindings/text-writer.cc index 862c2a5d..f41a83d6 100644 --- a/src/bindings/text-writer.cc +++ b/src/bindings/text-writer.cc @@ -3,56 +3,42 @@ using std::string; using std::move; using std::u16string; -using namespace v8; +using namespace Napi; -void TextWriter::init(Local exports) { - Local constructor_template = Nan::New(construct); - constructor_template->SetClassName(Nan::New("TextWriter").ToLocalChecked()); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - const auto &prototype_template = constructor_template->PrototypeTemplate(); - Nan::SetTemplate(prototype_template, Nan::New("write").ToLocalChecked(), Nan::New(write), None); - Nan::SetTemplate(prototype_template, Nan::New("end").ToLocalChecked(), Nan::New(end), None); - Nan::Set(exports, Nan::New("TextWriter").ToLocalChecked(), Nan::GetFunction(constructor_template).ToLocalChecked()); -} +FunctionReference TextWriter::constructor; + +void TextWriter::init(Object exports) { + auto env = exports.Env(); + Napi::Function func = DefineClass(env, "TextWriter", { + InstanceMethod<&TextWriter::write>("write"), + InstanceMethod<&TextWriter::end>("end"), + }); -TextWriter::TextWriter(EncodingConversion &&conversion) : conversion{move(conversion)} {} + constructor.Reset(func, 1); + exports.Set("TextWriter", func); +} -void TextWriter::construct(const Nan::FunctionCallbackInfo &info) { - Local js_encoding_name; - if (!Nan::To(info[0]).ToLocal(&js_encoding_name)) return; - Nan::Utf8String encoding_name(js_encoding_name); - auto conversion = transcoding_from(*encoding_name); - if (!conversion) { - Nan::ThrowError((string("Invalid encoding name: ") + *encoding_name).c_str()); +TextWriter::TextWriter(const CallbackInfo &info):ObjectWrap(info) { + String js_encoding_name = info[0].As(); + auto encoding_name = js_encoding_name.Utf8Value(); + auto _conversion = transcoding_from(encoding_name.c_str()); + if (!_conversion) { + Error::New(Env(), (string("Invalid encoding name: ") + encoding_name).c_str()).ThrowAsJavaScriptException(); return; } - - TextWriter *wrapper = new TextWriter(move(*conversion)); - wrapper->Wrap(info.This()); + conversion.reset(new EncodingConversion(move(*_conversion))); } -void TextWriter::write(const Nan::FunctionCallbackInfo &info) { - auto writer = Nan::ObjectWrap::Unwrap(info.This()); +void TextWriter::write(const CallbackInfo &info) { + auto writer = this; - Local js_chunk; - if (Nan::To(info[0]).ToLocal(&js_chunk)) { - size_t size = writer->content.size(); - writer->content.resize(size + js_chunk->Length()); - js_chunk->Write( - -// Nan doesn't wrap this functionality -#if NODE_MAJOR_VERSION >= 12 - Isolate::GetCurrent(), -#endif - - reinterpret_cast(&writer->content[0]) + size, - 0, - -1, - String::WriteOptions::NO_NULL_TERMINATION - ); - } else if (info[0]->IsUint8Array()) { - auto *data = node::Buffer::Data(info[0]); - size_t length = node::Buffer::Length(info[0]); + if (info[0].IsString()) { + String js_chunk = info[0].As(); + writer->content.assign(js_chunk.Utf16Value()); + } else if (info[0].IsTypedArray()) { + auto js_buffer = info[0].As(); + char* data = reinterpret_cast(js_buffer.Data()); + size_t length = js_buffer.ByteLength(); if (!writer->leftover_bytes.empty()) { writer->leftover_bytes.insert( writer->leftover_bytes.end(), @@ -62,7 +48,7 @@ void TextWriter::write(const Nan::FunctionCallbackInfo &info) { data = writer->leftover_bytes.data(); length = writer->leftover_bytes.size(); } - size_t bytes_written = writer->conversion.decode( + size_t bytes_written = writer->conversion->decode( writer->content, data, length @@ -75,10 +61,10 @@ void TextWriter::write(const Nan::FunctionCallbackInfo &info) { } } -void TextWriter::end(const Nan::FunctionCallbackInfo &info) { - auto writer = Nan::ObjectWrap::Unwrap(info.This()); +void TextWriter::end(const CallbackInfo &info) { + auto writer = this; if (!writer->leftover_bytes.empty()) { - writer->conversion.decode( + writer->conversion->decode( writer->content, writer->leftover_bytes.data(), writer->leftover_bytes.size(), diff --git a/src/bindings/text-writer.h b/src/bindings/text-writer.h index 97fde24b..cd92b53f 100644 --- a/src/bindings/text-writer.h +++ b/src/bindings/text-writer.h @@ -1,24 +1,24 @@ #ifndef SUPERSTRING_TEXT_WRITER_H #define SUPERSTRING_TEXT_WRITER_H -#include "nan.h" +#include "napi.h" #include "text.h" #include "encoding-conversion.h" -class TextWriter : public Nan::ObjectWrap { +class TextWriter : public Napi::ObjectWrap { public: - static void init(v8::Local exports); - TextWriter(EncodingConversion &&conversion); + static void init(Napi::Object exports); + TextWriter(const Napi::CallbackInfo &info); std::u16string get_text(); private: - static void construct(const Nan::FunctionCallbackInfo &info); - static void write(const Nan::FunctionCallbackInfo &info); - static void end(const Nan::FunctionCallbackInfo &info); + void write(const Napi::CallbackInfo &info); + void end(const Napi::CallbackInfo &info); - EncodingConversion conversion; + std::unique_ptr conversion; std::vector leftover_bytes; std::u16string content; + static Napi::FunctionReference constructor; }; #endif // SUPERSTRING_TEXT_WRITER_H diff --git a/src/bindings/util.h b/src/bindings/util.h new file mode 100644 index 00000000..a6b20f16 --- /dev/null +++ b/src/bindings/util.h @@ -0,0 +1,21 @@ +#pragma once + +// Copied from https://github.com/nodejs/node/blob/main/src/js_native_api_v8.h +//=== Conversion between V8 Handles and napi_value ======================== + +// This asserts v8::Local<> will always be implemented with a single +// pointer field so that we can pass it around as a void*. +static_assert(sizeof(v8::Local) == sizeof(napi_value), + "Cannot convert between v8::Local and napi_value"); + +inline napi_value JsValueFromV8LocalValue(v8::Local local) { + return reinterpret_cast(*local); +} + +inline v8::Local V8LocalValueFromJsValue(napi_value v) { + v8::Local local; + memcpy(static_cast(&local), &v, sizeof(v)); + return local; +} + +// End diff --git a/test/js/patch.test.js b/test/js/patch.test.js index 30862d84..7d4df278 100644 --- a/test/js/patch.test.js +++ b/test/js/patch.test.js @@ -30,7 +30,6 @@ describe('Patch', function () { } ]) - patch.delete(); }) it('honors the mergeAdjacentChanges option set to true', function () { @@ -56,8 +55,6 @@ describe('Patch', function () { newStart: {row: 0, column: 5}, newEnd: {row: 0, column: 11} } ]) - - patch.delete(); }) describe('.compose', () => { @@ -95,11 +92,6 @@ describe('Patch', function () { assert.throws(() => Patch.compose([{}, {}])) assert.throws(() => Patch.compose([1, 'a'])) - - for (let patch of patches) - patch.delete(); - - composedPatch.delete(); }) it('throws an Error if the patches do not apply', () => { @@ -170,9 +162,6 @@ describe('Patch', function () { } ]) - patch.delete(); - invertedPatch.delete(); - patch2.delete(); }) it('can copy patches', function () { @@ -186,8 +175,6 @@ describe('Patch', function () { patch2.splice({row: 0, column: 10}, {row: 0, column: 5}, {row: 0, column: 5}) assert.deepEqual(patch2.copy().getChanges(), patch2.getChanges()) - patch.delete(); - patch2.delete(); }) it('can serialize/deserialize patches', () => { @@ -207,9 +194,6 @@ describe('Patch', function () { oldText: 'hello', newText: 'world' }]) - - patch1.delete(); - patch2.delete(); }) it('removes a change when it becomes empty', () => { @@ -236,7 +220,7 @@ describe('Patch', function () { const random = new Random(seed) const originalDocument = new TestDocument(seed) const mutatedDocument = originalDocument.clone() - const mergeAdjacentChanges = random(2) + const mergeAdjacentChanges = random(2) == 1; const patch = new Patch({mergeAdjacentChanges}) for (let j = 0; j < 20; j++) { @@ -369,7 +353,6 @@ describe('Patch', function () { assert.deepEqual(patchCopy2.changeForOldPosition(oldPoint), patch.changeForOldPosition(oldPoint), seedMessage) } - patch.delete(); } }) diff --git a/test/js/text-buffer.test.js b/test/js/text-buffer.test.js index 1959ba2c..fa79cdcc 100644 --- a/test/js/text-buffer.test.js +++ b/test/js/text-buffer.test.js @@ -87,7 +87,9 @@ describe('TextBuffer', () => { fs.writeFileSync(filePath, content) const percentages = [] - return buffer.load(filePath, (percentDone) => percentages.push(percentDone)) + return buffer.load(filePath, (percentDone) => { + return percentages.push(percentDone); + }) .then(() => { assert.equal(buffer.getText(), content) assert.deepEqual(percentages, percentages.map(Number).sort((a, b) => a - b))