Skip to content

Commit 33f120c

Browse files
committed
Make it possible to check for pre-release versions of Nvim
1 parent 86c3639 commit 33f120c

File tree

3 files changed

+128
-33
lines changed

3 files changed

+128
-33
lines changed

src/bridge/api_info.rs

Lines changed: 110 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use std::{collections::HashSet, fmt, hash::Hash};
33
use itertools::Itertools;
44
use rmpv::{Utf8StringRef, Value, ValueRef};
55

6+
#[cfg(test)]
7+
use crate::bridge::nvim_dict;
8+
69
#[derive(Debug, Clone)]
710
pub struct ApiInfoParseError(String);
811

@@ -36,21 +39,27 @@ pub struct ApiVersion {
3639
pub api_level: u64,
3740
pub api_compatible: u64,
3841
pub api_prerelease: bool,
42+
pub prerelease_version: u64,
43+
pub prerelease_commit: String,
44+
pub string: String,
3945
}
4046

4147
impl ApiVersion {
48+
/// A pre-release only matches lower versions when prerelease is set to None.
49+
/// So NVIM v0.12.0-dev-1253+gfb2d736481 matches for the arguments 0, 11, 1, None but not for 0, 12, 0, None.
50+
/// When the prerelease is set to some number it compares the field after -dev in the version string.
51+
/// So the above version matches for the arguments 0, 12, 0, Some(1253), but not for 0, 12, 0, Some(1254).
52+
/// See the tests for more examples.
4253
#[allow(dead_code)]
43-
pub fn has_version(&self, major: u64, minor: u64, patch: u64) -> bool {
44-
let actual_major = self.major;
45-
let actual_minor = self.minor;
46-
let actual_patch = self.patch;
47-
log::trace!("actual nvim version: {actual_major}.{actual_minor}.{actual_patch}");
48-
log::trace!("expect nvim version: {major}.{minor}.{patch}");
49-
let ret = actual_major > major
50-
|| (actual_major == major && actual_minor > minor)
51-
|| (actual_major == major && actual_minor == minor && actual_patch >= patch);
52-
log::trace!("has desired nvim version: {ret}");
53-
ret
54+
pub fn has_version(&self, major: u64, minor: u64, patch: u64, prerelease: Option<u64>) -> bool {
55+
self.major > major
56+
|| (self.major == major && self.minor > minor)
57+
|| ((self.major == major && self.minor == minor && self.patch >= patch)
58+
&& !self.prerelease)
59+
|| (self.major == major
60+
&& self.minor == minor
61+
&& self.patch == patch
62+
&& matches!(prerelease, Some(prerelease) if self.prerelease_version >= prerelease))
5463
}
5564
}
5665

@@ -184,7 +193,10 @@ impl ApiInformation {
184193
}
185194
}
186195

187-
fn parse_version(value: ValueRef) -> std::result::Result<ApiVersion, ApiInfoParseError> {
196+
fn parse_version(
197+
value: ValueRef,
198+
version_str: &str,
199+
) -> std::result::Result<ApiVersion, ApiInfoParseError> {
188200
let mut major = None;
189201
let mut minor = None;
190202
let mut patch = None;
@@ -209,6 +221,20 @@ fn parse_version(value: ValueRef) -> std::result::Result<ApiVersion, ApiInfoPars
209221
}
210222
}
211223

224+
// The api information does unfortunately not contain the detailed git information, so parse that from the version string
225+
// A pre-release has the following format
226+
// NVIM v0.12.0-dev-1253+gfb2d736481
227+
let mut prerelease_version = 0;
228+
let mut prerelease_commit = String::default();
229+
if let Some((_, dev, version)) = version_str.split('-').collect_tuple() {
230+
if dev == "dev" {
231+
if let Some((version, commit)) = version.split("+").collect_tuple() {
232+
prerelease_version = version.parse().unwrap_or(0);
233+
prerelease_commit = commit.to_string();
234+
}
235+
}
236+
}
237+
212238
Ok(ApiVersion {
213239
major: major.ok_or("major field is missing")?,
214240
minor: minor.ok_or("minor field is missing")?,
@@ -217,6 +243,9 @@ fn parse_version(value: ValueRef) -> std::result::Result<ApiVersion, ApiInfoPars
217243
api_level: api_level.ok_or("api_level field is missing")?,
218244
api_compatible: api_compatible.ok_or("api_compatible field is missing")?,
219245
api_prerelease: api_prerelase.ok_or("api_prerelease field is missing")?,
246+
prerelease_version,
247+
prerelease_commit,
248+
string: version_str.to_string(),
220249
})
221250
}
222251

@@ -343,7 +372,10 @@ fn parse_ui_events(value: ValueRef) -> std::result::Result<HashSet<ApiEvent>, Ap
343372
.collect::<std::result::Result<HashSet<_>, _>>()
344373
}
345374

346-
pub fn parse_api_info(value: &[Value]) -> std::result::Result<ApiInformation, ApiInfoParseError> {
375+
pub fn parse_api_info(
376+
value: &[Value],
377+
version_str: &str,
378+
) -> std::result::Result<ApiInformation, ApiInfoParseError> {
347379
let channel = value[0].as_ref().try_into()?;
348380

349381
let metadata: Vec<(ValueRef, ValueRef)> = value[1].as_ref().try_into()?;
@@ -356,7 +388,7 @@ pub fn parse_api_info(value: &[Value]) -> std::result::Result<ApiInformation, Ap
356388
for (k, v) in metadata {
357389
let k: Utf8StringRef = k.try_into()?;
358390
match k.as_str() {
359-
Some("version") => version = Some(parse_version(v)?),
391+
Some("version") => version = Some(parse_version(v, version_str)?),
360392
Some("functions") => functions = Some(parse_functions(v)?),
361393
Some("ui_options") => ui_options = Some(parse_string_vec(v)?),
362394
Some("ui_events") => ui_events = Some(parse_ui_events(v)?),
@@ -372,3 +404,67 @@ pub fn parse_api_info(value: &[Value]) -> std::result::Result<ApiInformation, Ap
372404
ui_events: ui_events.ok_or("ui_events field is missing")?,
373405
})
374406
}
407+
408+
#[test]
409+
fn version_match() {
410+
let value = nvim_dict! {
411+
"major" => 1,
412+
"minor" => 11,
413+
"patch" => 4,
414+
"prerelease" => false,
415+
"api_level" => 0,
416+
"api_compatible" => 0,
417+
"api_prerelease" => false,
418+
};
419+
420+
let version = parse_version(Value::from(value).as_ref(), "NVIM v1.11.4").unwrap();
421+
assert!(version.has_version(1, 11, 4, None));
422+
assert!(version.has_version(1, 11, 3, None));
423+
assert!(version.has_version(1, 10, 0, None));
424+
assert!(version.has_version(0, 11, 4, None));
425+
// We also have a pre-release of previous versions
426+
assert!(version.has_version(1, 11, 4, Some(1253)));
427+
assert!(version.has_version(1, 10, 4, Some(1253)));
428+
assert!(!version.has_version(1, 11, 5, None));
429+
assert!(!version.has_version(1, 12, 4, None));
430+
assert!(!version.has_version(2, 11, 4, None));
431+
// We don't have a pre-release of newer versions
432+
assert!(!version.has_version(1, 12, 0, Some(0)));
433+
assert!(!version.has_version(1, 11, 5, Some(0)));
434+
}
435+
436+
#[test]
437+
fn version_match_prerelease() {
438+
let value = nvim_dict! {
439+
"major" => 1,
440+
"minor" => 12,
441+
"patch" => 0,
442+
"prerelease" => true,
443+
"api_level" => 0,
444+
"api_compatible" => 0,
445+
"api_prerelease" => false,
446+
};
447+
448+
let version = parse_version(
449+
Value::from(value).as_ref(),
450+
"NVIM v1.12.0-dev-1253+gfb2d736481",
451+
)
452+
.unwrap();
453+
assert!(version.has_version(1, 11, 4, None));
454+
assert!(version.has_version(1, 11, 3, None));
455+
assert!(version.has_version(1, 10, 0, None));
456+
assert!(version.has_version(0, 11, 4, None));
457+
// We also have a pre-release of previous versions
458+
assert!(version.has_version(1, 11, 4, Some(1253)));
459+
assert!(version.has_version(1, 10, 4, Some(1253)));
460+
assert!(version.has_version(1, 11, 5, None));
461+
assert!(!version.has_version(1, 12, 4, None));
462+
assert!(!version.has_version(2, 11, 4, None));
463+
// We have a pre-release that allows these
464+
assert!(version.has_version(1, 12, 0, Some(0)));
465+
assert!(version.has_version(1, 12, 0, Some(1253)));
466+
assert!(version.has_version(1, 11, 5, Some(0)));
467+
// But not these
468+
assert!(!version.has_version(1, 12, 1, Some(0)));
469+
assert!(!version.has_version(1, 12, 0, Some(1254)));
470+
}

src/bridge/mod.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub use events::*;
3434
pub use session::NeovimWriter;
3535
pub use ui_commands::{send_ui, start_ui_command_handler, ParallelCommand, SerialCommand};
3636

37-
const NEOVIM_REQUIRED_VERSION: &str = "0.10.0";
37+
const NEOVIM_REQUIRED_VERSION: (u64, u64, u64) = (0, 10, 0);
3838

3939
macro_rules! nvim_dict {
4040
( $( $key:expr => $value:expr ),* $(,)? ) => {
@@ -114,28 +114,24 @@ async fn launch(
114114
.await
115115
.context("Could not locate or start neovim process")?;
116116

117-
// Check the neovim version to ensure its high enough
118-
match nvim_exec_output(
119-
&session.neovim,
120-
&format!("echo has('nvim-{NEOVIM_REQUIRED_VERSION}')"),
121-
)
122-
.await
123-
.as_deref()
117+
let api_information = get_api_information(&session.neovim).await?;
118+
info!(
119+
"Neovide registered to nvim with channel id {}",
120+
api_information.channel
121+
);
122+
123+
let (major, minor, patch) = NEOVIM_REQUIRED_VERSION;
124+
if !api_information
125+
.version
126+
.has_version(major, minor, patch, None)
124127
{
125-
Ok("1") => {} // This is just a guard
126-
_ => {
127-
bail!("Neovide requires nvim version {NEOVIM_REQUIRED_VERSION} or higher. Download the latest version here https://github.com/neovim/neovim/wiki/Installing-Neovim");
128-
}
128+
let found = api_information.version.string;
129+
bail!("Neovide requires nvim version {major}.{minor}.{patch} or higher, but {found} was detected. Download the latest version here https://github.com/neovim/neovim/wiki/Installing-Neovim");
129130
}
130131

131132
let cmdline_settings = settings.get::<CmdLineSettings>();
132133

133134
let should_handle_clipboard = cmdline_settings.wsl || cmdline_settings.server.is_some();
134-
let api_information = get_api_information(&session.neovim).await?;
135-
info!(
136-
"Neovide registered to nvim with channel id {}",
137-
api_information.channel
138-
);
139135
// This is too verbose to keep enabled all the time
140136
// log::info!("Api information {:#?}", api_information);
141137
setup_neovide_specific_state(

src/bridge/setup.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rmpv::Value;
44

55
use super::{
66
api_info::{parse_api_info, ApiInformation},
7-
nvim_dict,
7+
nvim_dict, nvim_exec_output,
88
};
99
use crate::{
1010
bridge::NeovimWriter,
@@ -20,7 +20,10 @@ pub async fn get_api_information(nvim: &Neovim<NeovimWriter>) -> Result<ApiInfor
2020
.await
2121
.context("Error getting API info")?;
2222

23-
parse_api_info(&api_info).context("Failed to parse Neovim api information")
23+
let version = nvim_exec_output(nvim, "version").await?;
24+
log::info!("Neovim version: {version:#?}");
25+
let version_str = version.lines().next().unwrap_or_default();
26+
parse_api_info(&api_info, version_str).context("Failed to parse Neovim api information")
2427
}
2528

2629
pub async fn setup_neovide_specific_state(

0 commit comments

Comments
 (0)