├── .gitignore ├── LICENSE.md ├── README.md ├── lib ├── editor-handler.coffee └── sublime-select.coffee ├── package.json └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sublime Style Column Selection 2 | 3 | Enable Sublime style 'Column Selection', allowing you to drag across lines to select a block of text with carets on each line. 4 | 5 | Also similar to Textmate's 'Multiple Carets', or BBEdit's 'Block Select' 6 | 7 | ![](https://raw.github.com/bigfive/atom-sublime-select/master/screenshot.png) 8 | 9 | ## Usage 10 | Hold the modifier key then click and drag with the configured mouse button across multiple lines. Dragging vertically places carets on each line at that column; dragging horizontally as well selects the text on each line. 11 | 12 | Default key combinations are: 13 | 14 | |Platform |Modifier Key |Mouse Button | 15 | |---------|-------------|-------------| 16 | |Windows |Alt |Left | 17 | |OS X |Option |Left | 18 | |Linux |Shift |Left | 19 | 20 | ## Settings 21 | The modifier key and mouse button can both be configured from the package's settings page. Available options: 22 | 23 | ### Mouse Button 24 | - Left 25 | - Middle 26 | - Right 27 | 28 | ### Key Trigger 29 | - Shift 30 | - Alt/Option 31 | - Ctrl 32 | - None 33 | 34 | To use middle mouse selection working since version 1.4.0 please use the following settings. 35 | 36 | ![](https://cloud.githubusercontent.com/assets/633193/12469581/e829bcd0-c027-11e5-8104-901fc0ff4a73.png) 37 | -------------------------------------------------------------------------------- /lib/editor-handler.coffee: -------------------------------------------------------------------------------- 1 | {Point} = require 'atom' 2 | 3 | module.exports = 4 | class SublimeSelectEditorHandler 5 | constructor: (editor, inputCfg) -> 6 | @editor = editor 7 | @inputCfg = inputCfg 8 | @_resetState() 9 | @_setup_vars() 10 | 11 | subscribe: -> 12 | @selection_observer = @editor.onDidChangeSelectionRange @onRangeChange 13 | @editorElement.addEventListener 'mousedown', @onMouseDown 14 | @editorElement.addEventListener 'mousemove', @onMouseMove 15 | @editorElement.addEventListener 'mouseup', @onMouseEventToHijack 16 | @editorElement.addEventListener 'mouseleave', @onMouseEventToHijack 17 | @editorElement.addEventListener 'mouseenter', @onMouseEventToHijack 18 | @editorElement.addEventListener 'contextmenu', @onMouseEventToHijack 19 | @editorElement.addEventListener 'blur', @onBlur 20 | 21 | unsubscribe: -> 22 | @_resetState() 23 | @selection_observer.dispose() 24 | @editorElement.removeEventListener 'mousedown', @onMouseDown 25 | @editorElement.removeEventListener 'mousemove', @onMouseMove 26 | @editorElement.removeEventListener 'mouseup', @onMouseEventToHijack 27 | @editorElement.removeEventListener 'mouseleave', @onMouseEventToHijack 28 | @editorElement.removeEventListener 'mouseenter', @onMouseEventToHijack 29 | @editorElement.removeEventListener 'contextmenu', @onMouseEventToHijack 30 | @editorElement.removeEventListener 'blur', @onBlur 31 | 32 | # ------- 33 | # Event Handlers 34 | # ------- 35 | 36 | onMouseDown: (e) => 37 | if @mouseStartPos 38 | e.preventDefault() 39 | return false 40 | 41 | if @_mainMouseAndKeyDown(e) 42 | @_resetState() 43 | @mouseStartPos = @_screenPositionForMouseEvent(e) 44 | @mouseEndPos = @mouseStartPos 45 | e.preventDefault() 46 | return false 47 | 48 | onMouseMove: (e) => 49 | if @mouseStartPos 50 | e.preventDefault() 51 | if @_mainMouseDown(e) 52 | @mouseEndPos = @_screenPositionForMouseEvent(e) 53 | return if @mouseEndPos.isEqual @mouseEndPosPrev 54 | @_selectBoxAroundCursors() 55 | @mouseEndPosPrev = @mouseEndPos 56 | return false 57 | if e.which == 0 58 | @_resetState() 59 | 60 | # Hijack all the mouse events while selecting 61 | onMouseEventToHijack: (e) => 62 | if @mouseStartPos 63 | e.preventDefault() 64 | return false 65 | 66 | onBlur: (e) => 67 | @_resetState() 68 | 69 | onRangeChange: (newVal) => 70 | if @mouseStartPos and !newVal.selection.isSingleScreenLine() 71 | newVal.selection.destroy() 72 | @_selectBoxAroundCursors() 73 | 74 | # ------- 75 | # Methods 76 | # ------- 77 | 78 | _resetState: -> 79 | @mouseStartPos = null 80 | @mouseEndPos = null 81 | 82 | _setup_vars: -> 83 | @editorElement ?= atom.views.getView @editor 84 | @editorComponent ?= @editorElement.component 85 | 86 | # I had to create my own version of @editorComponent.screenPositionFromMouseEvent 87 | _screenPositionForMouseEvent: (e) -> 88 | @_setup_vars() 89 | pixelPosition = @editorComponent.pixelPositionForMouseEvent(e) 90 | targetTop = pixelPosition.top 91 | targetLeft = pixelPosition.left 92 | defaultCharWidth = @editor.getDefaultCharWidth() 93 | row = Math.floor(targetTop / @editor.getLineHeightInPixels()) 94 | targetLeft = Infinity if row > @editor.getLastScreenRow() 95 | row = Math.min(row, @editor.getLastScreenRow()) 96 | row = Math.max(0, row) 97 | column = Math.round (targetLeft) / defaultCharWidth 98 | new Point(row, column) 99 | 100 | # methods for checking mouse/key state against config 101 | _mainMouseDown: (e) -> 102 | e.which is @inputCfg.mouseNum 103 | 104 | _mainMouseAndKeyDown: (e) -> 105 | if @inputCfg.selectKey 106 | @_mainMouseDown(e) and e[@inputCfg.selectKey] 107 | else 108 | @_mainMouseDown(e) 109 | 110 | _numCharsInScreenRange: (screenRange) -> 111 | bufferRange = @editor.bufferRangeForScreenRange(screenRange) 112 | contentsOfRange = @editor.getTextInBufferRange(bufferRange) 113 | contentsOfRange.length 114 | 115 | # Do the actual selecting 116 | _selectBoxAroundCursors: -> 117 | if @mouseStartPos and @mouseEndPos 118 | emptyRanges = [] 119 | ranges = [] 120 | 121 | for row in [@mouseStartPos.row..@mouseEndPos.row] 122 | @mouseEndPos.column = 0 if @mouseEndPos.column < 0 123 | range = [[row, @mouseStartPos.column], [row, @mouseEndPos.column]] 124 | numChars = @_numCharsInScreenRange(range) 125 | if numChars == 0 126 | emptyRanges.push range 127 | else 128 | ranges.push range 129 | 130 | finalRanges = if ranges.length then ranges else emptyRanges 131 | if finalRanges.length 132 | isReversed = @mouseEndPos.column < @mouseStartPos.column 133 | @editor.setSelectedScreenRanges finalRanges, {reversed: isReversed} 134 | -------------------------------------------------------------------------------- /lib/sublime-select.coffee: -------------------------------------------------------------------------------- 1 | packageName = "Sublime-Style-Column-Selection" 2 | 3 | os = require 'os' 4 | SublimeSelectEditorHandler = require './editor-handler.coffee' 5 | 6 | defaultCfg = switch os.platform() 7 | when 'win32' 8 | selectKey: 'altKey' 9 | selectKeyName: 'Alt' 10 | mouseNum: 1 11 | mouseName: "Left" 12 | when 'darwin' 13 | selectKey: 'altKey' 14 | selectKeyName: 'Alt' 15 | mouseNum: 1 16 | mouseName: "Left" 17 | when 'linux' 18 | selectKey: 'shiftKey' 19 | selectKeyName: 'Shift' 20 | mouseNum: 1 21 | mouseName: "Left" 22 | else 23 | selectKey: 'shiftKey' 24 | selectKeyName: 'Shift' 25 | mouseNum: 1 26 | mouseName: "Left" 27 | 28 | mouseNumMap = 29 | Left: 1, 30 | Middle: 2, 31 | Right: 3 32 | 33 | selectKeyMap = 34 | Shift: 'shiftKey', 35 | Alt: 'altKey', 36 | Ctrl: 'ctrlKey', 37 | 38 | selectKeyMap.Cmd = 'metaKey' if os.platform() == 'darwin' 39 | 40 | selectKeyMap.None = null 41 | 42 | inputCfg = defaultCfg 43 | 44 | module.exports = 45 | 46 | config: 47 | mouseButtonTrigger: 48 | title: "Mouse Button" 49 | description: "The mouse button that will trigger column selection. 50 | If empty, the default will be used #{defaultCfg.mouseName} mouse button." 51 | type: 'string' 52 | enum: (key for key, value of mouseNumMap) 53 | default: defaultCfg.mouseName 54 | 55 | selectKeyTrigger: 56 | title: "Select Key" 57 | description: "The key that will trigger column selection. 58 | If empty, the default will be used #{defaultCfg.selectKeyName} key." 59 | type: 'string' 60 | enum: (key for key, value of selectKeyMap) 61 | default: defaultCfg.selectKeyName 62 | 63 | activate: (state) -> 64 | @observers = [] 65 | @editor_handler = null 66 | 67 | @observers.push atom.config.observe "#{packageName}.mouseButtonTrigger", (newValue) => 68 | inputCfg.mouseName = newValue 69 | inputCfg.mouseNum = mouseNumMap[newValue] 70 | 71 | @observers.push atom.config.observe "#{packageName}.selectKeyTrigger", (newValue) => 72 | inputCfg.selectKeyName = newValue 73 | inputCfg.selectKey = selectKeyMap[newValue] 74 | 75 | @observers.push atom.workspace.onDidChangeActivePaneItem @switch_editor_handler 76 | @observers.push atom.workspace.onDidAddPane @switch_editor_handler 77 | @observers.push atom.workspace.onDidDestroyPane @switch_editor_handler 78 | @switch_editor_handler() 79 | 80 | deactivate: -> 81 | @editor_handler?.unsubscribe() 82 | observer.dispose() for observer in @observers 83 | @observers = null 84 | @editor_handler = null 85 | 86 | switch_editor_handler: => 87 | @editor_handler?.unsubscribe() 88 | active_editor = atom.workspace.getActiveTextEditor() 89 | if active_editor 90 | @editor_handler = new SublimeSelectEditorHandler(active_editor, inputCfg) 91 | @editor_handler.subscribe() 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sublime-Style-Column-Selection", 3 | "main": "./lib/sublime-select", 4 | "version": "1.7.5", 5 | "description": "Enable Sublime style 'Column Selection'. Just hold 'alt' while you select, or select using your middle mouse button. Also similar to TextMate's 'Multiple Carets', or BBEdit's 'Block Select'", 6 | "repository": "https://github.com/bigfive/atom-sublime-select", 7 | "license": "MIT", 8 | "engines": { 9 | "atom": ">0.165.0" 10 | }, 11 | "dependencies": {} 12 | } 13 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigfive/atom-sublime-select/17f43b8a981207298f3df8724bb86d38909a33bf/screenshot.png --------------------------------------------------------------------------------