diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py
index fd6cfd61d22..95d169d9062 100644
--- a/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -5707,6 +5707,9 @@ def km_edit_curves(params):
*_template_items_select_actions(params, "curves.select_all"),
("curves.extrude_move", {"type": 'E', "value": 'PRESS'}, None),
("curves.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None),
+ ("curves.select_linked_pick", {"type": 'L', "value": 'PRESS'}, {"properties": [("deselect", False)]}),
+ ("curves.select_linked_pick", {"type": 'L', "value": 'PRESS',
+ "shift": True}, {"properties": [("deselect", True)]}),
("curves.delete", {"type": 'X', "value": 'PRESS'}, None),
("curves.delete", {"type": 'DEL', "value": 'PRESS'}, None),
("curves.select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None),
diff --git a/source/blender/editors/curves/CMakeLists.txt b/source/blender/editors/curves/CMakeLists.txt
index a8a2e0b95b5..8b46dc9d6c3 100644
--- a/source/blender/editors/curves/CMakeLists.txt
+++ b/source/blender/editors/curves/CMakeLists.txt
@@ -31,6 +31,7 @@ set(SRC
intern/curves_ops.cc
intern/curves_selection.cc
intern/curves_undo.cc
+ intern/select_linked_pick.cc
)
set(LIB
diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc
index f1d31fb6766..679baca3987 100644
--- a/source/blender/editors/curves/intern/curves_ops.cc
+++ b/source/blender/editors/curves/intern/curves_ops.cc
@@ -1777,6 +1777,7 @@ void operatortypes_curves()
WM_operatortype_append(CURVES_OT_select_random);
WM_operatortype_append(CURVES_OT_select_ends);
WM_operatortype_append(CURVES_OT_select_linked);
+ WM_operatortype_append(CURVES_OT_select_linked_pick);
WM_operatortype_append(CURVES_OT_select_more);
WM_operatortype_append(CURVES_OT_select_less);
WM_operatortype_append(CURVES_OT_surface_set);
diff --git a/source/blender/editors/curves/intern/select_linked_pick.cc b/source/blender/editors/curves/intern/select_linked_pick.cc
new file mode 100644
index 00000000000..47ed0c1541c
--- /dev/null
+++ b/source/blender/editors/curves/intern/select_linked_pick.cc
@@ -0,0 +1,143 @@
+/* SPDX-FileCopyrightText: 2024 Blender Authors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_context.hh"
+#include "BKE_curves.hh"
+#include "BKE_layer.hh"
+
+#include "RNA_access.hh"
+#include "RNA_define.hh"
+
+#include "DEG_depsgraph.hh"
+
+#include "WM_api.hh"
+#include "WM_types.hh"
+
+#include "ED_curves.hh"
+#include "ED_view3d.hh"
+
+namespace blender::ed::curves {
+
+struct ClosestCurveDataBlock {
+ Curves *curves_id = nullptr;
+ FindClosestData elem = {};
+};
+static ClosestCurveDataBlock find_closest_curve(const Depsgraph &depsgraph,
+ const ViewContext &vc,
+ const Span bases,
+ const int2 &mval)
+{
+ return threading::parallel_reduce(
+ bases.index_range(),
+ 1L,
+ ClosestCurveDataBlock(),
+ [&](const IndexRange range, const ClosestCurveDataBlock &init) {
+ ClosestCurveDataBlock new_closest = init;
+ for (Base *base : bases.slice(range)) {
+ Object &curves_ob = *base->object;
+ Curves &curves_id = *static_cast(curves_ob.data);
+ bke::crazyspace::GeometryDeformation deformation =
+ bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_ob);
+ const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
+ const float4x4 projection = ED_view3d_ob_project_mat_get(vc.rv3d, &curves_ob);
+ foreach_selectable_curve_range(
+ curves,
+ deformation,
+ eHandleDisplay(vc.v3d->overlay.handle_display),
+ [&](IndexRange range, Span positions, StringRef /*selection_name*/) {
+ std::optional new_closest_elem = closest_elem_find_screen_space(
+ vc,
+ curves.points_by_curve(),
+ positions,
+ curves.cyclic(),
+ projection,
+ range,
+ bke::AttrDomain::Curve,
+ mval,
+ new_closest.elem);
+ if (new_closest_elem) {
+ new_closest.elem = *new_closest_elem;
+ new_closest.curves_id = &curves_id;
+ }
+ });
+ }
+ return new_closest;
+ },
+ [](const ClosestCurveDataBlock &a, const ClosestCurveDataBlock &b) {
+ return (a.elem.distance < b.elem.distance) ? a : b;
+ });
+}
+
+static bool select_linked_pick(bContext &C, const int2 &mval, const SelectPick_Params ¶ms)
+{
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(&C);
+ const ViewContext vc = ED_view3d_viewcontext_init(&C, depsgraph);
+ const Vector bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data(
+ vc.scene, vc.view_layer, vc.v3d);
+
+ const ClosestCurveDataBlock closest = find_closest_curve(*depsgraph, vc, bases, mval);
+ if (!closest.curves_id) {
+ return false;
+ }
+
+ bke::CurvesGeometry &closest_curves = closest.curves_id->geometry.wrap();
+ const bke::AttrDomain selection_domain = bke::AttrDomain(closest.curves_id->selection_domain);
+
+ if (selection_domain == bke::AttrDomain::Point) {
+ const OffsetIndices points_by_curve = closest_curves.points_by_curve();
+ foreach_selection_attribute_writer(
+ closest_curves, bke::AttrDomain::Point, [&](bke::GSpanAttributeWriter &selection) {
+ for (const int point : points_by_curve[closest.elem.index]) {
+ apply_selection_operation_at_index(selection.span, point, params.sel_op);
+ }
+ });
+ }
+ else if (selection_domain == bke::AttrDomain::Curve) {
+ bke::GSpanAttributeWriter selection = ensure_selection_attribute(
+ closest_curves, bke::AttrDomain::Curve, CD_PROP_BOOL);
+ apply_selection_operation_at_index(selection.span, closest.elem.index, params.sel_op);
+ selection.finish();
+ }
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a
+ * generic attribute for now. */
+ DEG_id_tag_update(&closest.curves_id->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(&C, NC_GEOM | ND_DATA, closest.curves_id);
+
+ return true;
+}
+
+static int select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ SelectPick_Params params{};
+ params.sel_op = RNA_boolean_get(op->ptr, "deselect") ? SEL_OP_SUB : SEL_OP_ADD;
+ params.deselect_all = false;
+ params.select_passthrough = false;
+
+ if (!select_linked_pick(*C, event->mval, params)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void CURVES_OT_select_linked_pick(wmOperatorType *ot)
+{
+ ot->name = "Select Linked";
+ ot->idname = "CURVES_OT_select_linked_pick";
+ ot->description = "Select all points in the curve under the cursor";
+
+ ot->invoke = select_linked_pick_invoke;
+ ot->poll = editable_curves_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_boolean(ot->srna,
+ "deselect",
+ false,
+ "Deselect",
+ "Deselect linked control points rather than selecting them");
+}
+
+} // namespace blender::ed::curves
diff --git a/source/blender/editors/include/ED_curves.hh b/source/blender/editors/include/ED_curves.hh
index bb785572eb7..21ba843659a 100644
--- a/source/blender/editors/include/ED_curves.hh
+++ b/source/blender/editors/include/ED_curves.hh
@@ -134,6 +134,7 @@ bool curves_poll(bContext *C);
void CURVES_OT_attribute_set(wmOperatorType *ot);
void CURVES_OT_draw(wmOperatorType *ot);
void CURVES_OT_extrude(wmOperatorType *ot);
+void CURVES_OT_select_linked_pick(wmOperatorType *ot);
/** \} */