diff --git a/package-lock.json b/package-lock.json
index eed8892ea..ba19175ee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2544,8 +2544,7 @@
"integrity": "sha1-hJgDKzttHMge68X3lpDY/in6v08="
},
"go-to-line": {
- "version": "https://www.atom.io/api/packages/go-to-line/versions/0.33.0/tarball",
- "integrity": "sha512-YD5zEkGQRTl6jrgAIOQ0Zr0rB/f/yPifxhz4od2kN+JfGVeb76xD9FG5OkR9dr0vEtPfJeDbG2WWTJ1JGMShqQ=="
+ "version": "file:packages/go-to-line"
},
"graceful-fs": {
"version": "4.1.11",
diff --git a/package.json b/package.json
index c1eb18674..d4342f695 100644
--- a/package.json
+++ b/package.json
@@ -69,7 +69,7 @@
"git-utils": "5.2.1",
"github": "https://www.atom.io/api/packages/github/versions/0.20.1/tarball",
"glob": "^7.1.1",
- "go-to-line": "https://www.atom.io/api/packages/go-to-line/versions/0.33.0/tarball",
+ "go-to-line": "file:packages/go-to-line",
"grammar-selector": "https://www.atom.io/api/packages/grammar-selector/versions/0.50.1/tarball",
"grim": "1.5.0",
"image-view": "https://www.atom.io/api/packages/image-view/versions/0.63.1/tarball",
@@ -204,7 +204,7 @@
"fuzzy-finder": "1.8.2",
"github": "0.20.1",
"git-diff": "file:./packages/git-diff",
- "go-to-line": "0.33.0",
+ "go-to-line": "file:./packages/go-to-line",
"grammar-selector": "0.50.1",
"image-view": "0.63.1",
"incompatible-packages": "file:./packages/incompatible-packages",
diff --git a/packages/README.md b/packages/README.md
index 3cc90bdec..04d8946fc 100644
--- a/packages/README.md
+++ b/packages/README.md
@@ -35,7 +35,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **fuzzy-finder** | [`atom/fuzzy-finder`][fuzzy-finder] | |
| **github** | [`atom/github`][github] | |
| **git-diff** | [`./git-diff`](./git-diff) | [#17843](https://github.com/atom/atom/issues/17843) |
-| **go-to-line** | [`atom/go-to-line`][go-to-line] | [#17844](https://github.com/atom/atom/issues/17844) |
+| **go-to-line** | [`./go-to-line`](./go-to-line) | [#17844](https://github.com/atom/atom/issues/17844) |
| **grammar-selector** | [`atom/grammar-selector`][grammar-selector] | [#17845](https://github.com/atom/atom/issues/17845) |
| **image-view** | [`atom/image-view`][image-view] | |
| **incompatible-packages** | [`./incompatible-packages`](./incompatible-packages) | [#17846](https://github.com/atom/atom/issues/17846) |
@@ -115,7 +115,6 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
[find-and-replace]: https://github.com/atom/find-and-replace
[fuzzy-finder]: https://github.com/atom/fuzzy-finder
[github]: https://github.com/atom/github
-[go-to-line]: https://github.com/atom/go-to-line
[grammar-selector]: https://github.com/atom/grammar-selector
[image-view]: https://github.com/atom/image-view
[keybinding-resolver]: https://github.com/atom/keybinding-resolver
diff --git a/packages/go-to-line/.gitignore b/packages/go-to-line/.gitignore
new file mode 100644
index 000000000..b271ae615
--- /dev/null
+++ b/packages/go-to-line/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+Thumbs.db
+node_modules
+npm-debug.log
diff --git a/packages/go-to-line/LICENSE.md b/packages/go-to-line/LICENSE.md
new file mode 100644
index 000000000..4d231b456
--- /dev/null
+++ b/packages/go-to-line/LICENSE.md
@@ -0,0 +1,20 @@
+Copyright (c) 2014 GitHub Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/go-to-line/README.md b/packages/go-to-line/README.md
new file mode 100644
index 000000000..d29ccff74
--- /dev/null
+++ b/packages/go-to-line/README.md
@@ -0,0 +1,5 @@
+# Go To Line package
+
+Move the cursor to a specific line in the editor using ctrl-g.
+
+![](https://f.cloud.github.com/assets/671378/2241602/fdd88c4c-9cd8-11e3-9d14-74844ec7da01.png)
diff --git a/packages/go-to-line/keymaps/go-to-line.cson b/packages/go-to-line/keymaps/go-to-line.cson
new file mode 100644
index 000000000..2d588a2b7
--- /dev/null
+++ b/packages/go-to-line/keymaps/go-to-line.cson
@@ -0,0 +1,15 @@
+'.platform-darwin, .platform-win32, .platform-linux':
+ 'ctrl-g': 'go-to-line:toggle'
+
+'.go-to-line atom-text-editor[mini]':
+ 'enter': 'core:confirm',
+ 'escape': 'core:cancel'
+
+'.platform-darwin .go-to-line atom-text-editor[mini]':
+ 'cmd-w': 'core:cancel'
+
+'.platform-win32 .go-to-line atom-text-editor[mini]':
+ 'ctrl-w': 'core:cancel'
+
+'.platform-linux .go-to-line atom-text-editor[mini]':
+ 'ctrl-w': 'core:cancel'
diff --git a/packages/go-to-line/lib/go-to-line-view.js b/packages/go-to-line/lib/go-to-line-view.js
new file mode 100644
index 000000000..66759f9d9
--- /dev/null
+++ b/packages/go-to-line/lib/go-to-line-view.js
@@ -0,0 +1,105 @@
+'use babel'
+
+import { Point, TextEditor } from 'atom'
+
+class GoToLineView {
+ constructor () {
+ this.miniEditor = new TextEditor({ mini: true })
+ this.miniEditor.element.addEventListener('blur', this.close.bind(this))
+
+ this.message = document.createElement('div')
+ this.message.classList.add('message')
+
+ this.element = document.createElement('div')
+ this.element.classList.add('go-to-line')
+ this.element.appendChild(this.miniEditor.element)
+ this.element.appendChild(this.message)
+
+ this.panel = atom.workspace.addModalPanel({
+ item: this,
+ visible: false
+ })
+ atom.commands.add('atom-text-editor', 'go-to-line:toggle', () => {
+ this.toggle()
+ return false
+ })
+ atom.commands.add(this.miniEditor.element, 'core:confirm', () => {
+ this.navigate()
+ })
+ atom.commands.add(this.miniEditor.element, 'core:cancel', () => {
+ this.close()
+ })
+ this.miniEditor.onWillInsertText((arg) => {
+ if (arg.text.match(/[^0-9:]/)) {
+ arg.cancel()
+ }
+ })
+ this.miniEditor.onDidChange(() => {
+ this.navigate({keepOpen: true})
+ })
+ }
+
+ toggle () {
+ this.panel.isVisible() ? this.close() : this.open()
+ }
+
+ close () {
+ if (!this.panel.isVisible()) return
+ this.miniEditor.setText('')
+ this.panel.hide()
+ if (this.miniEditor.element.hasFocus()) {
+ this.restoreFocus()
+ }
+ }
+
+ navigate (options = {}) {
+ const lineNumber = this.miniEditor.getText()
+ const editor = atom.workspace.getActiveTextEditor()
+ if (!options.keepOpen) {
+ this.close()
+ }
+ if (!editor || !lineNumber.length) return
+
+ const currentRow = editor.getCursorBufferPosition().row
+ const rowLineNumber = lineNumber.split(/:+/)[0] || ''
+ const row = rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow
+ const columnLineNumber = lineNumber.split(/:+/)[1] || ''
+ const column = columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1
+
+ const position = new Point(row, column)
+ editor.setCursorBufferPosition(position)
+ editor.unfoldBufferRow(row)
+ if (column < 0) {
+ editor.moveToFirstCharacterOfLine()
+ }
+ editor.scrollToBufferPosition(position, {
+ center: true
+ })
+ }
+
+ storeFocusedElement () {
+ this.previouslyFocusedElement = document.activeElement
+ return this.previouslyFocusedElement
+ }
+
+ restoreFocus () {
+ if (this.previouslyFocusedElement && this.previouslyFocusedElement.parentElement) {
+ return this.previouslyFocusedElement.focus()
+ }
+ atom.views.getView(atom.workspace).focus()
+ }
+
+ open () {
+ if (this.panel.isVisible() || !atom.workspace.getActiveTextEditor()) return
+ this.storeFocusedElement()
+ this.panel.show()
+ this.message.textContent = 'Enter a or : to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7'
+ this.miniEditor.element.focus()
+ }
+}
+
+export default {
+ activate () {
+ return new GoToLineView()
+ }
+}
diff --git a/packages/go-to-line/menus/go-to-line.cson b/packages/go-to-line/menus/go-to-line.cson
new file mode 100644
index 000000000..60a220e45
--- /dev/null
+++ b/packages/go-to-line/menus/go-to-line.cson
@@ -0,0 +1,7 @@
+'menu': [
+ 'label': 'Edit'
+ 'submenu': [
+ 'label': 'Go to Line'
+ 'command': 'go-to-line:toggle'
+ ]
+]
diff --git a/packages/go-to-line/package.json b/packages/go-to-line/package.json
new file mode 100644
index 000000000..4f6e87581
--- /dev/null
+++ b/packages/go-to-line/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "go-to-line",
+ "version": "0.33.0",
+ "main": "./lib/go-to-line-view",
+ "description": "Jump to a specific editor line number with `ctrl-g`.",
+ "license": "MIT",
+ "scripts": {
+ "lint": "standard"
+ },
+ "activationCommands": {
+ "atom-text-editor": [
+ "go-to-line:toggle"
+ ]
+ },
+ "repository": "https://github.com/atom/atom",
+ "engines": {
+ "atom": "*"
+ },
+ "devDependencies": {
+ "standard": "^8.6.0"
+ },
+ "standard": {
+ "globals": [
+ "atom",
+ "waitsForPromise"
+ ],
+ "ignore": [
+ "spec/fixtures"
+ ]
+ }
+}
diff --git a/packages/go-to-line/spec/fixtures/sample.js b/packages/go-to-line/spec/fixtures/sample.js
new file mode 100644
index 000000000..cb53d4078
--- /dev/null
+++ b/packages/go-to-line/spec/fixtures/sample.js
@@ -0,0 +1,70 @@
+var quicksort = function () {
+ var sort = function(items) {
+ if (items.length <= 1) return items;
+ var pivot = items.shift(), current, left = [], right = [];
+ while(items.length > 0) {
+ current = items.shift();
+ current < pivot ? left.push(current) : right.push(current);
+ }
+ return sort(left).concat(pivot).concat(sort(right));
+ };
+
+ return sort(Array.apply(this, arguments));
+};
+
+// adapted from:
+// https://github.com/nzakas/computer-science-in-javascript/tree/master/algorithms/sorting/merge-sort-recursive
+var mergeSort function (items){
+ var merge = function (left, right){
+ var result = [];
+ var il = 0;
+ var ir = 0;
+
+ while (il < left.length && ir < right.length){
+ if (left[il] < right[ir]){
+ result.push(left[il++]);
+ } else {
+ result.push(right[ir++]);
+ }
+ }
+
+ return result.concat(left.slice(il)).concat(right.slice(ir));
+ };
+
+ if (items.length < 2) {
+ return items;
+ }
+
+ var middle = Math.floor(items.length / 2),
+ left = items.slice(0, middle),
+ right = items.slice(middle),
+ params = merge(mergeSort(left), mergeSort(right));
+
+ // Add the arguments to replace everything between 0 and last item in the array
+ params.unshift(0, items.length);
+ items.splice.apply(items, params);
+ return items;
+};
+
+// adapted from:
+// https://github.com/nzakas/computer-science-in-javascript/blob/master/algorithms/sorting/bubble-sort/bubble-sort.js
+var bubbleSort = function (items){
+ var swap = function (items, firstIndex, secondIndex){
+ var temp = items[firstIndex];
+ items[firstIndex] = items[secondIndex];
+ items[secondIndex] = temp;
+ };
+
+ var len = items.length,
+ i, j, stop;
+
+ for (i=0; i < len; i++){
+ for (j=0, stop=len-i; j < stop; j++){
+ if (items[j] > items[j+1]){
+ swap(items, j, j+1);
+ }
+ }
+ }
+
+ return items;
+};
diff --git a/packages/go-to-line/spec/go-to-line-spec.js b/packages/go-to-line/spec/go-to-line-spec.js
new file mode 100644
index 000000000..8b9f3ba92
--- /dev/null
+++ b/packages/go-to-line/spec/go-to-line-spec.js
@@ -0,0 +1,165 @@
+'use babel'
+
+/* eslint-env jasmine */
+
+import GoToLineView from '../lib/go-to-line-view'
+
+describe('GoToLine', () => {
+ let editor = null
+ let editorView = null
+ let goToLine = null
+
+ beforeEach(() => {
+ waitsForPromise(() => {
+ return atom.workspace.open('sample.js')
+ })
+
+ runs(() => {
+ const workspaceElement = atom.views.getView(atom.workspace)
+ workspaceElement.style.height = '200px'
+ workspaceElement.style.width = '1000px'
+ jasmine.attachToDOM(workspaceElement)
+ editor = atom.workspace.getActiveTextEditor()
+ editorView = atom.views.getView(editor)
+ goToLine = GoToLineView.activate()
+ editor.setCursorBufferPosition([1, 0])
+ })
+ })
+
+ describe('when go-to-line:toggle is triggered', () => {
+ it('adds a modal panel', () => {
+ expect(goToLine.panel.isVisible()).toBeFalsy()
+ atom.commands.dispatch(editorView, 'go-to-line:toggle')
+ expect(goToLine.panel.isVisible()).toBeTruthy()
+ })
+ })
+
+ describe('when entering a line number', () => {
+ it('only allows 0-9 and the colon character to be entered in the mini editor', () => {
+ expect(goToLine.miniEditor.getText()).toBe('')
+ goToLine.miniEditor.insertText('a')
+ expect(goToLine.miniEditor.getText()).toBe('')
+ goToLine.miniEditor.insertText('path/file.txt:56')
+ expect(goToLine.miniEditor.getText()).toBe('')
+ goToLine.miniEditor.insertText(':')
+ expect(goToLine.miniEditor.getText()).toBe(':')
+ goToLine.miniEditor.setText('')
+ goToLine.miniEditor.insertText('4')
+ expect(goToLine.miniEditor.getText()).toBe('4')
+ })
+ })
+
+ describe('when typing line numbers (auto-navigation)', () => {
+ it('automatically scrolls to the desired line', () => {
+ goToLine.miniEditor.insertText('13')
+ expect(editor.getCursorBufferPosition()).toEqual([12, 0])
+ })
+ })
+
+ describe('when typing line and column numbers (auto-navigation)', () => {
+ it('automatically scrolls to the desired line and column', () => {
+ goToLine.miniEditor.insertText('3:8')
+ expect(editor.getCursorBufferPosition()).toEqual([2, 7])
+ })
+ })
+
+ describe('when entering a line number and column number', () => {
+ it('moves the cursor to the column number of the line specified', () => {
+ expect(goToLine.miniEditor.getText()).toBe('')
+ goToLine.miniEditor.insertText('3:14')
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ expect(editor.getCursorBufferPosition()).toEqual([2, 13])
+ })
+
+ it('centers the selected line', () => {
+ goToLine.miniEditor.insertText('45:4')
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ const rowsPerPage = editor.getRowsPerPage()
+ const currentRow = editor.getCursorBufferPosition().row - 1
+ expect(editor.getFirstVisibleScreenRow()).toBe(currentRow - Math.ceil(rowsPerPage / 2))
+ expect(editor.getLastVisibleScreenRow()).toBe(currentRow + Math.floor(rowsPerPage / 2))
+ })
+ })
+
+ describe('when entering a line number greater than the number of rows in the buffer', () => {
+ it('moves the cursor position to the first character of the last line', () => {
+ atom.commands.dispatch(editorView, 'go-to-line:toggle')
+ expect(goToLine.panel.isVisible()).toBeTruthy()
+ expect(goToLine.miniEditor.getText()).toBe('')
+ goToLine.miniEditor.insertText('71')
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ expect(goToLine.panel.isVisible()).toBeFalsy()
+ expect(editor.getCursorBufferPosition()).toEqual([70, 0])
+ })
+ })
+
+ describe('when entering a column number greater than the number in the specified line', () => {
+ it('moves the cursor position to the last character of the specified line', () => {
+ atom.commands.dispatch(editorView, 'go-to-line:toggle')
+ expect(goToLine.panel.isVisible()).toBeTruthy()
+ expect(goToLine.miniEditor.getText()).toBe('')
+ goToLine.miniEditor.insertText('3:43')
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ expect(goToLine.panel.isVisible()).toBeFalsy()
+ expect(editor.getCursorBufferPosition()).toEqual([2, 40])
+ })
+ })
+
+ describe('when core:confirm is triggered', () => {
+ describe('when a line number has been entered', () => {
+ it('moves the cursor to the first character of the line', () => {
+ goToLine.miniEditor.insertText('3')
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ expect(editor.getCursorBufferPosition()).toEqual([2, 4])
+ })
+ })
+
+ describe('when the line number entered is nested within foldes', () => {
+ it('unfolds all folds containing the given row', () => {
+ expect(editor.indentationForBufferRow(6)).toEqual(3)
+ editor.foldAll()
+ expect(editor.screenRowForBufferRow(6)).toEqual(0)
+ goToLine.miniEditor.insertText('7')
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ expect(editor.getCursorBufferPosition()).toEqual([6, 6])
+ })
+ })
+ })
+
+ describe('when no line number has been entered', () => {
+ it('closes the view and does not update the cursor position', () => {
+ atom.commands.dispatch(editorView, 'go-to-line:toggle')
+ expect(goToLine.panel.isVisible()).toBeTruthy()
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ expect(goToLine.panel.isVisible()).toBeFalsy()
+ expect(editor.getCursorBufferPosition()).toEqual([1, 0])
+ })
+ })
+
+ describe('when no line number has been entered, but a column number has been entered', () => {
+ it('navigates to the column of the current line', () => {
+ atom.commands.dispatch(editorView, 'go-to-line:toggle')
+ expect(goToLine.panel.isVisible()).toBeTruthy()
+ goToLine.miniEditor.insertText('4:1')
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ expect(goToLine.panel.isVisible()).toBeFalsy()
+ expect(editor.getCursorBufferPosition()).toEqual([3, 0])
+ atom.commands.dispatch(editorView, 'go-to-line:toggle')
+ expect(goToLine.panel.isVisible()).toBeTruthy()
+ goToLine.miniEditor.insertText(':19')
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
+ expect(goToLine.panel.isVisible()).toBeFalsy()
+ expect(editor.getCursorBufferPosition()).toEqual([3, 18])
+ })
+ })
+
+ describe('when core:cancel is triggered', () => {
+ it('closes the view and does not update the cursor position', () => {
+ atom.commands.dispatch(editorView, 'go-to-line:toggle')
+ expect(goToLine.panel.isVisible()).toBeTruthy()
+ atom.commands.dispatch(goToLine.miniEditor.element, 'core:cancel')
+ expect(goToLine.panel.isVisible()).toBeFalsy()
+ expect(editor.getCursorBufferPosition()).toEqual([1, 0])
+ })
+ })
+})