Repository: GraphiteEditor/Graphite (language: rust)

Issue:
Add a "Select Parent" command
It should go under *Layer* > *Deselect All* (it can skip having an icon for now). For every selected layer, it should move the selection up a level in the hierarchy. Below are a couple before/after examples.

| Before | After |
|--------|--------|
| ![Image](https://github.com/user-attachments/assets/68edab81-ae9e-4b72-bd49-57981779d864) | ![Image](https://github.com/user-attachments/assets/6977c344-87e7-4f0a-b290-b08ed72005f2) |
| ![Image](https://github.com/user-attachments/assets/56cf5e22-1988-4cb4-8ad0-7c7bdd2ad2b5) | ![Image](https://github.com/user-attachments/assets/eeb6740d-f2c8-46ee-936c-29fb298a5ce6) | 


Discussion / hints:
@Keavon  should it be an option in file (menu-bar) named Deselect all.

![Image](https://github.com/user-attachments/assets/6cb5178f-547d-4def-bb36-1ffb00123c7c)
Ah, I meant beneath *Layer* > *Deselect All*.

Fix patch:
diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs
index 211d4cf98c..8e33f194c7 100644
--- a/editor/src/messages/input_mapper/input_mappings.rs
+++ b/editor/src/messages/input_mapper/input_mappings.rs
@@ -336,6 +336,7 @@ pub fn input_mappings() -> Mapping {
 		entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),
 		entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
 		entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder),
+		entry!(KeyDown(Backslash); modifiers=[Alt], action_dispatch=DocumentMessage::SelectParentLayer),
 		entry!(KeyDown(BracketLeft); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepBack),
 		entry!(KeyDown(BracketRight); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepForward),
 		entry!(KeyDown(MouseBack); action_dispatch=DocumentMessage::SelectionStepBack),
diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs
index 8ae84271ea..db2919da94 100644
--- a/editor/src/messages/portfolio/document/document_message.rs
+++ b/editor/src/messages/portfolio/document/document_message.rs
@@ -109,6 +109,7 @@ pub enum DocumentMessage {
 	RenderRulers,
 	RenderScrollbars,
 	SaveDocument,
+	SelectParentLayer,
 	SelectAllLayers,
 	SelectedLayersLower,
 	SelectedLayersLowerToBack,
diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs
index fcda8ddbc2..10b1179071 100644
--- a/editor/src/messages/portfolio/document/document_message_handler.rs
+++ b/editor/src/messages/portfolio/document/document_message_handler.rs
@@ -931,6 +931,31 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
 					name,
 				})
 			}
+			DocumentMessage::SelectParentLayer => {
+				let selected_nodes = self.network_interface.selected_nodes(&[]).unwrap();
+				let selected_layers = selected_nodes.selected_layers(self.metadata());
+
+				let mut parent_layers = HashSet::new();
+
+				// Find the parent of each selected layer
+				for layer in selected_layers {
+					// Get this layer's parent
+					let Some(parent) = layer.parent(self.metadata()) else { continue };
+
+					// Either use the parent, or keep the same layer if it's already at the top level
+					let to_insert = if parent == LayerNodeIdentifier::ROOT_PARENT { layer } else { parent };
+
+					// Add the layer to the set of those which will become selected
+					parent_layers.insert(to_insert.to_node());
+				}
+
+				// Select each parent layer
+				if !parent_layers.is_empty() {
+					let nodes = parent_layers.into_iter().collect();
+					responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
+					responses.add(BroadcastEvent::SelectionChanged);
+				}
+			}
 			DocumentMessage::SelectAllLayers => {
 				let metadata = self.metadata();
 				let all_layers_except_artboards_invisible_and_locked = metadata.all_layers().filter(|&layer| !self.network_interface.is_artboard(&layer.to_node(), &[])).filter(|&layer| {
@@ -1358,6 +1383,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
 			ToggleOverlaysVisibility,
 			ToggleSnapping,
 			Undo,
+			SelectParentLayer,
 			SelectionStepForward,
 			SelectionStepBack,
 			ZoomCanvasTo100Percent,
diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs
index 9ae11ee393..f18d3ca955 100644
--- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs
+++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs
@@ -260,6 +260,13 @@ impl LayoutHolder for MenuBarMessageHandler {
 							disabled: no_active_document || !has_selected_nodes,
 							..MenuBarEntry::default()
 						},
+						MenuBarEntry {
+							label: "Select Parent".into(),
+							shortcut: action_keys!(DocumentMessageDiscriminant::SelectParentLayer),
+							action: MenuBarEntry::create_action(|_| DocumentMessage::SelectParentLayer.into()),
+							disabled: no_active_document || !has_selected_nodes,
+							..MenuBarEntry::default()
+						},
 						MenuBarEntry {
 							label: "Previous Selection".into(),
 							icon: Some("HistoryUndo".into()),


Test patch: