Repository: skim-rs/skim (2026-04-10T14:15:20Z)

Issue:
--border none as in FZF (support for neovim fzf-lua)
Implement value `none` for option `--border` as in FZF


Discussion:

Patch:
diff --git a/man/man1/sk.1 b/man/man1/sk.1
index 296a47aa..506ca886 100644
--- a/man/man1/sk.1
+++ b/man/man1/sk.1
@@ -409,7 +409,7 @@ Number of lines of the input treated as header
 
 The  first N lines of the input are treated as the sticky header. When \-\-with\-nth is set, the lines are transformed just like the other lines that follow.
 .TP
-\fB\-\-border\fR [\fI<BORDER>...\fR]
+\fB\-\-border\fR [\fI<BORDER>...\fR] [default: none]
 Draw borders around the UI components
 .br
 
@@ -417,6 +417,10 @@ Draw borders around the UI components
 \fIPossible values:\fR
 .RS 14
 .IP \(bu 2
+force\-off: ForceOff disables borders around popups too set with no_border
+.IP \(bu 2
+none
+.IP \(bu 2
 plain
 .IP \(bu 2
 rounded
diff --git a/shell/completion.bash b/shell/completion.bash
index 0f4978f5..58f5b3eb 100644
--- a/shell/completion.bash
+++ b/shell/completion.bash
@@ -166,7 +166,7 @@ _sk() {
                     return 0
                     ;;
                 --border)
-                    COMPREPLY=($(compgen -W "plain rounded double thick light-double-dashed heavy-double-dashed light-triple-dashed heavy-triple-dashed light-quadruple-dashed heavy-quadruple-dashed quadrant-inside quadrant-outside" -- "${cur}"))
+                    COMPREPLY=($(compgen -W "force-off none plain rounded double thick light-double-dashed heavy-double-dashed light-triple-dashed heavy-triple-dashed light-quadruple-dashed heavy-quadruple-dashed quadrant-inside quadrant-outside" -- "${cur}"))
                     return 0
                     ;;
                 --multiline)
diff --git a/shell/completion.fish b/shell/completion.fish
index dc7719cb..84014834 100644
--- a/shell/completion.fish
+++ b/shell/completion.fish
@@ -49,7 +49,9 @@ inline\t''
 hidden\t''"
 complete -c sk -l header -d 'Set header, displayed next to the info' -r
 complete -c sk -l header-lines -d 'Number of lines of the input treated as header' -r
-complete -c sk -l border -d 'Draw borders around the UI components' -r -f -a "plain\t''
+complete -c sk -l border -d 'Draw borders around the UI components' -r -f -a "force-off\t'ForceOff disables borders around popups too set with no_border'
+none\t''
+plain\t''
 rounded\t''
 double\t''
 thick\t''
diff --git a/shell/completion.nu b/shell/completion.nu
index 95057bda..251a2e69 100644
--- a/shell/completion.nu
+++ b/shell/completion.nu
@@ -25,7 +25,7 @@ module completions {
   }
 
   def "nu-complete sk border" [] {
-    [ "plain" "rounded" "double" "thick" "light-double-dashed" "heavy-double-dashed" "light-triple-dashed" "heavy-triple-dashed" "light-quadruple-dashed" "heavy-quadruple-dashed" "quadrant-inside" "quadrant-outside" ]
+    [ "force-off" "none" "plain" "rounded" "double" "thick" "light-double-dashed" "heavy-double-dashed" "light-triple-dashed" "heavy-triple-dashed" "light-quadruple-dashed" "heavy-quadruple-dashed" "quadrant-inside" "quadrant-outside" ]
   }
 
   def "nu-complete sk shell" [] {
diff --git a/shell/completion.zsh b/shell/completion.zsh
index 9a33b7ec..c2af7e91 100644
--- a/shell/completion.zsh
+++ b/shell/completion.zsh
@@ -59,7 +59,20 @@ reverse-list\:"Display from the top of the screen, prompt at the bottom"))' \
 '--info=[Set matching result count display position]:INFO:(default inline hidden)' \
 '--header=[Set header, displayed next to the info]:HEADER:_default' \
 '--header-lines=[Number of lines of the input treated as header]:HEADER_LINES:_default' \
-'--border=[Draw borders around the UI components]::BORDER:(plain rounded double thick light-double-dashed heavy-double-dashed light-triple-dashed heavy-triple-dashed light-quadruple-dashed heavy-quadruple-dashed quadrant-inside quadrant-outside)' \
+'--border=[Draw borders around the UI components]::BORDER:((force-off\:"ForceOff disables borders around popups too set with no_border"
+none\:""
+plain\:""
+rounded\:""
+double\:""
+thick\:""
+light-double-dashed\:""
+heavy-double-dashed\:""
+light-triple-dashed\:""
+heavy-triple-dashed\:""
+light-quadruple-dashed\:""
+heavy-quadruple-dashed\:""
+quadrant-inside\:""
+quadrant-outside\:""))' \
 '--multiline=[Split item text into multiple display lines at the given separator character defaults to \\n if read0 is set, and \\\\n if not (matching literal \\n in text)]::MULTILINE:_default' \
 '--scrollbar=[Set scrollbar style for the item list]:THUMB:_default' \
 '--history=[History file]:HISTORY_FILE:_default' \
diff --git a/src/options.rs b/src/options.rs
index 70839f10..97c07240 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -543,10 +543,10 @@ pub struct SkimOptions {
     ///
     #[cfg_attr(
         feature = "cli",
-        arg(long, default_missing_value = "plain", help_heading = "Display", num_args=0..)
+        arg(long, default_missing_value = "plain", help_heading = "Display", default_value = "none", num_args=0..)
     )]
     #[debug(skip)]
-    pub border: Option<BorderType>,
+    pub border: BorderType,
 
     /// Disables all borders, including in tmux/zellij popups
     #[cfg_attr(feature = "cli", arg(long, help_heading = "Display", overrides_with = "border"))]
@@ -1224,7 +1224,7 @@ impl SkimOptions {
             self.typos = Typos::Disabled;
         }
         if self.no_border {
-            self.border = None;
+            self.border = BorderType::ForceOff;
         }
 
         if let Some(ref filter_query) = self.filter
diff --git a/src/popup/tmux.rs b/src/popup/tmux.rs
index 0744e7f5..d245e04d 100644
--- a/src/popup/tmux.rs
+++ b/src/popup/tmux.rs
@@ -25,16 +25,13 @@ impl TmuxPopup {
         ]);
 
         let border = {
-            use crate::tui::BorderType::{Plain, Rounded, Thick};
-            if options.no_border {
-                "none"
-            } else {
-                match options.border {
-                    None | Some(Plain) => "single",
-                    Some(Rounded) => "rounded",
-                    Some(Thick) => "heavy",
-                    Some(_) => "double",
-                }
+            use crate::tui::BorderType::{ForceOff, None, Plain, Rounded, Thick};
+            match options.border {
+                ForceOff => "none",
+                None | Plain => "single",
+                Rounded => "rounded",
+                Thick => "heavy",
+                _ => "double",
             }
         };
 
diff --git a/src/popup/zellij.rs b/src/popup/zellij.rs
index 0b351d21..0619e91f 100644
--- a/src/popup/zellij.rs
+++ b/src/popup/zellij.rs
@@ -58,7 +58,7 @@ impl ZellijPopup {
                     .map_or(".".to_string(), |d| d.to_string_lossy().to_string()),
             ]);
 
-        if options.no_border {
+        if options.border == crate::tui::BorderType::ForceOff {
             cmd.args(["--borderless", "true"]);
         }
 
diff --git a/src/tui/header.rs b/src/tui/header.rs
index 60c424b6..bb7b85de 100644
--- a/src/tui/header.rs
+++ b/src/tui/header.rs
@@ -42,8 +42,8 @@ pub struct Header {
     /// The number of spaces to show before the header
     indent_size: u16,
     theme: Arc<ColorTheme>,
-    /// Border type, if borders are enabled
-    pub border: Option<BorderType>,
+    /// Border type
+    pub border: BorderType,
     /// Whether to reverse the order of `header_lines` (for default/bottom-to-top layout)
     reverse_lines: bool,
     /// Reverse layout
@@ -155,10 +155,10 @@ impl SkimWidget for Header {
     }
 
     fn render(&mut self, area: Rect, buf: &mut Buffer) -> SkimRender {
-        let block = if let Some(border_type) = self.border {
+        let block = if let Some(border_type) = self.border.into_ratatui() {
             Block::default()
                 .borders(Borders::ALL)
-                .border_type(border_type.into())
+                .border_type(border_type)
                 .border_style(self.theme.border)
         } else {
             Block::default()
diff --git a/src/tui/input.rs b/src/tui/input.rs
index db6a5497..6969c7e1 100644
--- a/src/tui/input.rs
+++ b/src/tui/input.rs
@@ -142,8 +142,8 @@ pub struct Input {
     pub cursor_pos: u16,
     pub alternate_cursor_pos: u16,
     pub theme: Arc<ColorTheme>,
-    /// Border type, if borders are enabled
-    pub border: Option<BorderType>,
+    /// Border type
+    pub border: BorderType,
     /// Status information to display as the input's title
     pub status_info: Option<StatusInfo>,
     /// How to display the info/status (default, inline, or hidden)
@@ -450,10 +450,10 @@ impl SkimWidget for Input {
         let mut block = Block::default();
 
         // Add borders if enabled
-        if let Some(border_type) = self.border {
+        if let Some(border_type) = self.border.into_ratatui() {
             block = block
                 .borders(Borders::ALL)
-                .border_type(border_type.into())
+                .border_type(border_type)
                 .border_style(self.theme.border);
         }
 
diff --git a/src/tui/item_list.rs b/src/tui/item_list.rs
index 78de30e9..9a81e5ff 100644
--- a/src/tui/item_list.rs
+++ b/src/tui/item_list.rs
@@ -78,8 +78,8 @@ pub struct ItemList {
     pub(crate) wrap: bool,
     /// When Some, split item text on this separator and show each part on its own line
     pub(crate) multiline: Option<String>,
-    /// Border type, if borders are enabled
-    pub border: Option<BorderType>,
+    /// Border type
+    pub border: BorderType,
     /// When true, prepend each item's match score to its display text
     pub(crate) show_score: bool,
     pub(crate) show_index: bool,
@@ -572,10 +572,10 @@ impl SkimWidget for ItemList {
         Widget::render(Clear, area, buf);
 
         // Render border if enabled
-        if let Some(border_type) = this.border {
+        if let Some(border_type) = this.border.into_ratatui() {
             let block = Block::default()
                 .borders(Borders::ALL)
-                .border_type(border_type.into())
+                .border_type(border_type)
                 .border_style(this.theme.border);
             Widget::render(block, area, buf);
         }
diff --git a/src/tui/mod.rs b/src/tui/mod.rs
index 30d5518e..b71a5fba 100644
--- a/src/tui/mod.rs
+++ b/src/tui/mod.rs
@@ -135,11 +135,15 @@ impl std::fmt::Display for Size {
 /// This mirrors Ratatui's border type
 ///
 /// We need it so that we can properly use `ValueEnum`
-#[derive(Default, Clone, Copy)]
+#[derive(Default, Clone, Copy, PartialEq, Eq)]
 #[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
 #[allow(missing_docs)]
 pub enum BorderType {
+    /// `ForceOff` disables borders around popups too
+    /// set with `no_border`
+    ForceOff,
     #[default]
+    None,
     Plain,
     Rounded,
     Double,
@@ -158,9 +162,19 @@ pub enum BorderType {
     QuadrantOutside,
 }
 
-impl From<BorderType> for ratatui::widgets::BorderType {
-    fn from(val: BorderType) -> Self {
-        match val {
+impl BorderType {
+    fn is_none(self) -> bool {
+        matches!(self, BorderType::None | BorderType::ForceOff)
+    }
+    fn is_some(self) -> bool {
+        !self.is_none()
+    }
+    fn into_ratatui(self) -> Option<ratatui::widgets::BorderType> {
+        if self.is_none() {
+            return None;
+        }
+
+        Some(match self {
             BorderType::Plain => ratatui::widgets::BorderType::Plain,
             BorderType::Rounded => ratatui::widgets::BorderType::Rounded,
             BorderType::Double => ratatui::widgets::BorderType::Double,
@@ -173,7 +187,8 @@ impl From<BorderType> for ratatui::widgets::BorderType {
             BorderType::HeavyQuadrupleDashed => ratatui::widgets::BorderType::HeavyQuadrupleDashed,
             BorderType::QuadrantInside => ratatui::widgets::BorderType::QuadrantInside,
             BorderType::QuadrantOutside => ratatui::widgets::BorderType::QuadrantOutside,
-        }
+            BorderType::None | BorderType::ForceOff => unreachable!(),
+        })
     }
 }
 
diff --git a/src/tui/preview.rs b/src/tui/preview.rs
index 941eac10..a2ab187f 100644
--- a/src/tui/preview.rs
+++ b/src/tui/preview.rs
@@ -76,8 +76,8 @@ pub struct Preview {
     /// Channel to signal thread interruption
     interrupt_tx: Option<mpsc::Sender<()>>,
     pub theme: Arc<ColorTheme>,
-    /// Border type, if borders are enabled
-    pub border: Option<BorderType>,
+    /// Border type
+    pub border: BorderType,
     pub direction: Direction,
     pub wrap: bool,
     pty: Option<PtyPair>,
@@ -482,8 +482,8 @@ impl SkimWidget for Preview {
         let mut block = Block::new().style(self.theme.normal).border_style(self.theme.border);
 
         // Add borders based on direction and border setting
-        if let Some(border_type) = self.border {
-            block = block.borders(Borders::ALL).border_type(border_type.into());
+        if let Some(border_type) = self.border.into_ratatui() {
+            block = block.borders(Borders::ALL).border_type(border_type);
         } else {
             // No border on preview itself - separator will be drawn between areas
             match self.direction {


Test patch:
diff --git a/tests/options.rs b/tests/options.rs
index bde749ff..c6d72784 100644
--- a/tests/options.rs
+++ b/tests/options.rs
@@ -456,6 +456,14 @@ insta_test!(opt_multiple_flags_layout_and_reverse, ["a b c", "d e f"], &["--layo
     @snap;
 });
 
+insta_test!(opt_border_default, ["a", "b", "c", "ac"], &["-q", "a", "--border"], {
+    @snap;
+});
+
+insta_test!(opt_border_none, ["a", "b", "c", "ac"], &["-q", "a", "--border", "none"], {
+    @snap;
+});
+
 insta_test!(opt_border_plain, ["a", "b", "c", "ac"], &["-q", "a", "--border", "plain"], {
     @snap;
 });
diff --git a/tests/snapshots/options__opt_border_default@001.snap b/tests/snapshots/options__opt_border_default@001.snap
new file mode 100644
index 00000000..9b08c981
--- /dev/null
+++ b/tests/snapshots/options__opt_border_default@001.snap
@@ -0,0 +1,29 @@
+---
+source: tests/options.rs
+description: "input: items [\"a\", \"b\", \"c\", \"ac\"]\noptions: -q a --border"
+---
+"┌──────────────────────────────────────────────────────────────────────────────┐"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│                                                                              │"
+"│  ac                                                                          │"
+"│> a                                                                           │"
+"└──────────────────────────────────────────────────────────────────────────────┘"
+"┌  2/4──────────────────────────────────────────────────────────────────────0/0┐"
+"│> a                                                                           │"
+"└──────────────────────────────────────────────────────────────────────────────┘"
+cursor: (23, 5)
diff --git a/tests/snapshots/options__opt_border_none@001.snap b/tests/snapshots/options__opt_border_none@001.snap
new file mode 100644
index 00000000..bdc141f1
--- /dev/null
+++ b/tests/snapshots/options__opt_border_none@001.snap
@@ -0,0 +1,29 @@
+---
+source: tests/options.rs
+description: "input: items [\"a\", \"b\", \"c\", \"ac\"]\noptions: -q a --border none"
+---
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"                                                                                "
+"  ac                                                                            "
+"> a                                                                             "
+"  2/4                                                                        0/0"
+"> a                                                                             "
+cursor: (24, 4)