diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp
index 41eb5c0bde9a2c84ae765985a4a8694b10105c01..883d0c744db6fd5270f406627e91e129861c6bed 100644
--- a/src/quick/items/qquicktextinput.cpp
+++ b/src/quick/items/qquicktextinput.cpp
@@ -881,6 +881,8 @@ void QQuickTextInput::setFocusOnPress(bool b)
 
     Whether the TextInput should scroll when the text is longer than the width. By default this is
     set to true.
+
+    \sa ensureVisible()
 */
 bool QQuickTextInput::autoScroll() const
 {
@@ -1721,33 +1723,24 @@ void QQuickTextInput::geometryChanged(const QRectF &newGeometry,
     QQuickImplicitSizeItem::geometryChanged(newGeometry, oldGeometry);
 }
 
-void QQuickTextInputPrivate::updateHorizontalScroll()
+void QQuickTextInputPrivate::ensureVisible(int position, int preeditCursor, int preeditLength)
 {
     Q_Q(QQuickTextInput);
-#ifndef QT_NO_IM
-    QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor + m_preeditCursor);
-    const int preeditLength = m_textLayout.preeditAreaText().length();
-#else
-    QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor);
-#endif
+    QTextLine textLine = m_textLayout.lineForTextPosition(position + preeditCursor);
     const qreal width = qMax<qreal>(0, q->width());
     qreal cix = 0;
     qreal widthUsed = 0;
-    if (currentLine.isValid()) {
-#ifndef QT_NO_IM
-        cix = currentLine.cursorToX(m_cursor + preeditLength);
-#else
-        cix = currentLine.cursorToX(m_cursor);
-#endif
+    if (textLine.isValid()) {
+        cix = textLine.cursorToX(position + preeditLength);
         const qreal cursorWidth = cix >= 0 ? cix : width - cix;
-        widthUsed = qMax(currentLine.naturalTextWidth(), cursorWidth);
+        widthUsed = qMax(textLine.naturalTextWidth(), cursorWidth);
     }
     int previousScroll = hscroll;
 
-    if (!autoScroll || widthUsed <=  width || m_echoMode == QQuickTextInput::NoEcho) {
+    if (widthUsed <= width) {
         hscroll = 0;
     } else {
-        Q_ASSERT(currentLine.isValid());
+        Q_ASSERT(textLine.isValid());
         if (cix - hscroll >= width) {
             // text doesn't fit, cursor is to the right of br (scroll right)
             hscroll = cix - width;
@@ -1767,7 +1760,7 @@ void QQuickTextInputPrivate::updateHorizontalScroll()
         if (preeditLength > 0) {
             // check to ensure long pre-edit text doesn't push the cursor
             // off to the left
-             cix = currentLine.cursorToX(m_cursor + qMax(0, m_preeditCursor - 1));
+             cix = textLine.cursorToX(position + qMax(0, preeditCursor - 1));
              if (cix < hscroll)
                  hscroll = cix;
         }
@@ -1777,6 +1770,20 @@ void QQuickTextInputPrivate::updateHorizontalScroll()
         textLayoutDirty = true;
 }
 
+void QQuickTextInputPrivate::updateHorizontalScroll()
+{
+    if (autoScroll && m_echoMode != QQuickTextInput::NoEcho) {
+#ifndef QT_NO_IM
+        const int preeditLength = m_textLayout.preeditAreaText().length();
+        ensureVisible(m_cursor, m_preeditCursor, preeditLength);
+#else
+        ensureVisible(m_cursor);
+#endif
+    } else {
+        hscroll = 0;
+    }
+}
+
 void QQuickTextInputPrivate::updateVerticalScroll()
 {
     Q_Q(QQuickTextInput);
@@ -2634,14 +2641,16 @@ void QQuickTextInputPrivate::init()
     }
 }
 
-void QQuickTextInput::updateCursorRectangle()
+void QQuickTextInput::updateCursorRectangle(bool scroll)
 {
     Q_D(QQuickTextInput);
     if (!isComponentComplete())
         return;
 
-    d->updateHorizontalScroll();
-    d->updateVerticalScroll();
+    if (scroll) {
+        d->updateHorizontalScroll();
+        d->updateVerticalScroll();
+    }
     d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
     update();
     emit cursorRectangleChanged();
@@ -4402,5 +4411,21 @@ void QQuickTextInputPrivate::deleteEndOfLine()
     finishChange(priorState);
 }
 
+/*!
+    \qmlmethod QtQuick::TextInput::ensureVisible(int position)
+    \since 5.4
+
+    Scrolls the contents of the text input so that the specified character
+    \a position is visible inside the boundaries of the text input.
+
+    \sa autoScroll
+*/
+void QQuickTextInput::ensureVisible(int position)
+{
+    Q_D(QQuickTextInput);
+    d->ensureVisible(position);
+    updateCursorRectangle(false);
+}
+
 QT_END_NAMESPACE
 
diff --git a/src/quick/items/qquicktextinput_p.h b/src/quick/items/qquicktextinput_p.h
index 211aba87038fe3dea64eea94e7175ba459faa140..66cabb9cfef33e873aaac0eb7a0693663bd64c18 100644
--- a/src/quick/items/qquicktextinput_p.h
+++ b/src/quick/items/qquicktextinput_p.h
@@ -355,11 +355,12 @@ public Q_SLOTS:
     void redo();
     void insert(int position, const QString &text);
     void remove(int start, int end);
+    Q_REVISION(3) void ensureVisible(int position);
 
 private Q_SLOTS:
     void selectionChanged();
     void createCursor();
-    void updateCursorRectangle();
+    void updateCursorRectangle(bool scroll = true);
     void q_canPasteChanged();
     void q_updateAlignment();
     void triggerPreprocess();
diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h
index 2cf127608eca40dbb622e68c22c511afc5f42ada..facc6356a1f8c1357739b958a5a5bee631fe62e7 100644
--- a/src/quick/items/qquicktextinput_p_p.h
+++ b/src/quick/items/qquicktextinput_p_p.h
@@ -148,6 +148,7 @@ public:
 
     void init();
     void startCreatingCursor();
+    void ensureVisible(int position, int preeditCursor = 0, int preeditLength = 0);
     void updateHorizontalScroll();
     void updateVerticalScroll();
     bool determineHorizontalAlignment();
diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp
index 8451b527cef4eee05abba29065e116677c8eceaa..fc1be16bc867ee01663dd1b2256ef0504b419915 100644
--- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp
+++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp
@@ -232,6 +232,8 @@ private slots:
     void baselineOffset_data();
     void baselineOffset();
 
+    void ensureVisible();
+
 private:
     void simulateKey(QWindow *, int key);
 
@@ -6464,6 +6466,50 @@ void tst_qquicktextinput::baselineOffset()
     }
 }
 
+void tst_qquicktextinput::ensureVisible()
+{
+    QQmlComponent component(&engine);
+    component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
+    QScopedPointer<QObject> object(component.create());
+    QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
+    QVERIFY(input);
+
+    input->setWidth(QFontMetrics(input->font()).averageCharWidth() * 3);
+    input->setText("Hello World");
+
+    QTextLayout layout;
+    layout.setText(input->text());
+    layout.setFont(input->font());
+
+    if (!qmlDisableDistanceField()) {
+        QTextOption option;
+        option.setUseDesignMetrics(true);
+        layout.setTextOption(option);
+    }
+    layout.beginLayout();
+    QTextLine line = layout.createLine();
+    layout.endLayout();
+
+    input->ensureVisible(0);
+
+    QCOMPARE(input->boundingRect().x(), qreal(0));
+    QCOMPARE(input->boundingRect().y(), qreal(0));
+    QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
+    QCOMPARE(input->boundingRect().height(), line.height());
+
+    QSignalSpy cursorSpy(input, SIGNAL(cursorRectangleChanged()));
+    QVERIFY(cursorSpy.isValid());
+
+    input->ensureVisible(input->length());
+
+    QCOMPARE(cursorSpy.count(), 1);
+
+    QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
+    QCOMPARE(input->boundingRect().y(), qreal(0));
+    QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
+    QCOMPARE(input->boundingRect().height(), line.height());
+}
+
 QTEST_MAIN(tst_qquicktextinput)
 
 #include "tst_qquicktextinput.moc"