on master help sign in
Show whether file readonly in statusline (#7740)
fcbac48Connortsui20
Cargo.lock+1
@@ -1392,6 +1392,7 @@ dependencies = [
1392 "log",
1392 "log",
1393 "once_cell",
1393 "once_cell",
1394 "parking_lot",
1394 "parking_lot",
1395+ "rustix 0.38.4",
1395 "serde",
1396 "serde",
1396 "serde_json",
1397 "serde_json",
1397 "slotmap",
1398 "slotmap",
book/src/configuration.md+2−1
@@ -89,7 +89,7 @@ The `[editor.statusline]` key takes the following sub-keys:
89
89
90| Key | Description | Default |
90| Key | Description | Default |
91| --- | --- | --- |
91| --- | --- | --- |
92−| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "file-modification-indicator"]` |
92+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
93| `center` | A list of elements aligned to the middle of the statusline | `[]` |
93| `center` | A list of elements aligned to the middle of the statusline | `[]` |
94| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
94| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
95| `separator` | The character used to separate elements in the statusline | `"│"` |
95| `separator` | The character used to separate elements in the statusline | `"│"` |
@@ -108,6 +108,7 @@ The following statusline elements can be configured:
108| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
108| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
109| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
109| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
110| `file-line-ending` | The file line endings (CRLF or LF) |
110| `file-line-ending` | The file line endings (CRLF or LF) |
111+| `read-only-indicator` | An indicator that shows `[readonly]` when a file cannot be written |
111| `total-line-numbers` | The total line numbers of the opened file |
112| `total-line-numbers` | The total line numbers of the opened file |
112| `file-type` | The type of the opened file |
113| `file-type` | The type of the opened file |
113| `diagnostics` | The number of warnings and/or errors |
114| `diagnostics` | The number of warnings and/or errors |
helix-term/src/commands/typed.rs+1−1
@@ -672,7 +672,7 @@ pub fn write_all_impl(
672 }
672 }
673 if doc.path().is_none() {
673 if doc.path().is_none() {
674 if write_scratch {
674 if write_scratch {
675− errors.push("cannot write a buffer without a filename\n");
675+ errors.push("cannot write a buffer without a filename");
676 }
676 }
677 return None;
677 return None;
678 }
678 }
helix-term/src/ui/statusline.rs+14
@@ -145,6 +145,7 @@ where
145 helix_view::editor::StatusLineElement::FileModificationIndicator => {
145 helix_view::editor::StatusLineElement::FileModificationIndicator => {
146 render_file_modification_indicator
146 render_file_modification_indicator
147 }
147 }
148+ helix_view::editor::StatusLineElement::ReadOnlyIndicator => render_read_only_indicator,
148 helix_view::editor::StatusLineElement::FileEncoding => render_file_encoding,
149 helix_view::editor::StatusLineElement::FileEncoding => render_file_encoding,
149 helix_view::editor::StatusLineElement::FileLineEnding => render_file_line_ending,
150 helix_view::editor::StatusLineElement::FileLineEnding => render_file_line_ending,
150 helix_view::editor::StatusLineElement::FileType => render_file_type,
151 helix_view::editor::StatusLineElement::FileType => render_file_type,
@@ -442,6 +443,19 @@ where
442 write(context, title, None);
443 write(context, title, None);
443}
444}
444
445
446+fn render_read_only_indicator<F>(context: &mut RenderContext, write: F)
447+where
448+ F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
449+{
450+ let title = if context.doc.readonly {
451+ " [readonly] "
452+ } else {
453+ ""
454+ }
455+ .to_string();
456+ write(context, title, None);
457+}
458+
445fn render_file_base_name<F>(context: &mut RenderContext, write: F)
459fn render_file_base_name<F>(context: &mut RenderContext, write: F)
446where
460where
447 F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
461 F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
helix-view/Cargo.toml+1
@@ -51,6 +51,7 @@ clipboard-win = { version = "4.5", features = ["std"] }
51
51
52[target.'cfg(unix)'.dependencies]
52[target.'cfg(unix)'.dependencies]
53libc = "0.2"
53libc = "0.2"
54+rustix = { version = "0.38", features = ["fs"] }
54
55
55[dev-dependencies]
56[dev-dependencies]
56helix-tui = { path = "../helix-tui" }
57helix-tui = { path = "../helix-tui" }
helix-view/src/document.rs+35
@@ -185,6 +185,8 @@ pub struct Document {
185
185
186 // when document was used for most-recent-used buffer picker
186 // when document was used for most-recent-used buffer picker
187 pub focused_at: std::time::Instant,
187 pub focused_at: std::time::Instant,
188+
189+ pub readonly: bool,
188}
190}
189
191
190/// Inlay hints for a single `(Document, View)` combo.
192/// Inlay hints for a single `(Document, View)` combo.
@@ -673,6 +675,7 @@ impl Document {
673 config,
675 config,
674 version_control_head: None,
676 version_control_head: None,
675 focused_at: std::time::Instant::now(),
677 focused_at: std::time::Instant::now(),
678+ readonly: false,
676 }
679 }
677 }
680 }
678
681
@@ -955,6 +958,33 @@ impl Document {
955 }
958 }
956 }
959 }
957
960
961+ #[cfg(unix)]
962+ // Detect if the file is readonly and change the readonly field if necessary (unix only)
963+ pub fn detect_readonly(&mut self) {
964+ use rustix::fs::{access, Access};
965+ // Allows setting the flag for files the user cannot modify, like root files
966+ self.readonly = match &self.path {
967+ None => false,
968+ Some(p) => access(p, Access::WRITE_OK).is_err(),
969+ };
970+ }
971+
972+ #[cfg(not(unix))]
973+ // Detect if the file is readonly and change the readonly field if necessary (non-unix os)
974+ pub fn detect_readonly(&mut self) {
975+ // TODO Use the Windows' function `CreateFileW` to check if a file is readonly
976+ // Discussion: https://github.com/helix-editor/helix/pull/7740#issuecomment-1656806459
977+ // Vim implementation: https://github.com/vim/vim/blob/4c0089d696b8d1d5dc40568f25ea5738fa5bbffb/src/os_win32.c#L7665
978+ // Windows binding: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Storage/FileSystem/fn.CreateFileW.html
979+ self.readonly = match &self.path {
980+ None => false,
981+ Some(p) => match std::fs::metadata(p) {
982+ Err(_) => false,
983+ Ok(metadata) => metadata.permissions().readonly(),
984+ },
985+ };
986+ }
987+
958 /// Reload the document from its path.
988 /// Reload the document from its path.
959 pub fn reload(
989 pub fn reload(
960 &mut self,
990 &mut self,
@@ -969,6 +999,9 @@ impl Document {
969 .ok_or_else(|| anyhow!("can't find file to reload from {:?}", self.display_name()))?
999 .ok_or_else(|| anyhow!("can't find file to reload from {:?}", self.display_name()))?
970 .to_owned();
1000 .to_owned();
971
1001
1002+ // Once we have a valid path we check if its readonly status has changed
1003+ self.detect_readonly();
1004+
972 let mut file = std::fs::File::open(&path)?;
1005 let mut file = std::fs::File::open(&path)?;
973 let (rope, ..) = from_reader(&mut file, Some(encoding))?;
1006 let (rope, ..) = from_reader(&mut file, Some(encoding))?;
974
1007
@@ -1018,6 +1051,8 @@ impl Document {
1018 // and error out when document is saved
1051 // and error out when document is saved
1019 self.path = path;
1052 self.path = path;
1020
1053
1054+ self.detect_readonly();
1055+
1021 Ok(())
1056 Ok(())
1022 }
1057 }
1023
1058
helix-view/src/editor.rs+4
@@ -421,6 +421,7 @@ impl Default for StatusLineConfig {
421 E::Mode,
421 E::Mode,
422 E::Spinner,
422 E::Spinner,
423 E::FileName,
423 E::FileName,
424+ E::ReadOnlyIndicator,
424 E::FileModificationIndicator,
425 E::FileModificationIndicator,
425 ],
426 ],
426 center: vec![],
427 center: vec![],
@@ -473,6 +474,9 @@ pub enum StatusLineElement {
473 // The file modification indicator
474 // The file modification indicator
474 FileModificationIndicator,
475 FileModificationIndicator,
475
476
477+ /// An indicator that shows `"[readonly]"` when a file cannot be written
478+ ReadOnlyIndicator,
479+
476 /// The file encoding
480 /// The file encoding
477 FileEncoding,
481 FileEncoding,
478
482