mirror of
https://github.com/atom/atom.git
synced 2026-01-24 06:18:03 -05:00
ModalSelector. for find file and stuff
This commit is contained in:
@@ -1,102 +0,0 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
|
||||
File = require 'fs'
|
||||
Pane = require 'pane'
|
||||
|
||||
jQuery = $
|
||||
Modal = require 'modal'
|
||||
|
||||
require 'filefinder/stringscore'
|
||||
|
||||
module.exports =
|
||||
class FilefinderPane extends Pane
|
||||
html: require "filefinder/filefinder.html"
|
||||
|
||||
constructor: (@filefinder) ->
|
||||
$('#filefinder input').live 'keydown', @onKeydown
|
||||
@modal = new Modal @html
|
||||
|
||||
onKeydown: (e) =>
|
||||
keys = up: 38, down: 40, enter: 13
|
||||
|
||||
if e.keyCode is keys.enter
|
||||
@openSelected()
|
||||
false
|
||||
else if e.keyCode is keys.up
|
||||
@moveUp()
|
||||
else if e.keyCode is keys.down
|
||||
@moveDown()
|
||||
else
|
||||
@filterFiles()
|
||||
|
||||
toggle: ->
|
||||
if @modal.showing
|
||||
@modal.hide()
|
||||
else
|
||||
@showFinder()
|
||||
|
||||
paths: ->
|
||||
_paths = []
|
||||
for dir in File.list window.url
|
||||
continue if /\.git|Cocoa/.test dir
|
||||
if File.isDirectory dir
|
||||
_paths.push File.listDirectoryTree dir
|
||||
else
|
||||
_paths.push dir
|
||||
_.reject _.flatten(_paths), (dir) -> File.isDirectory dir
|
||||
|
||||
showFinder: ->
|
||||
@modal.show()
|
||||
@files = []
|
||||
for file in @paths()
|
||||
@files.push file.replace "#{window.url}/", ''
|
||||
@filterFiles()
|
||||
|
||||
findMatchingFiles: (query) ->
|
||||
return [] if not query
|
||||
|
||||
results = []
|
||||
for file in @files
|
||||
score = file.score query
|
||||
if score > 0
|
||||
# Basename matches count for more.
|
||||
if not query.match '/'
|
||||
if name.match '/'
|
||||
score += name.replace(/^.*\//, '').score query
|
||||
else
|
||||
score *= 2
|
||||
results.push [score, file]
|
||||
|
||||
sorted = results.sort (a, b) -> b[0] - a[0]
|
||||
_.map sorted, (el) -> el[1]
|
||||
|
||||
filterFiles: ->
|
||||
if query = $('#filefinder input').val()
|
||||
files = @findMatchingFiles query
|
||||
else
|
||||
files = @files
|
||||
$('#filefinder ul').empty()
|
||||
for file in files[0..10]
|
||||
$('#filefinder ul').append "<li>#{file}</li>"
|
||||
$('#filefinder input').focus()
|
||||
$('#filefinder li:first').addClass 'selected'
|
||||
|
||||
openSelected: ->
|
||||
dir = window.url
|
||||
file = $('#filefinder .selected').text()
|
||||
window.open "#{dir}/#{file}"
|
||||
@toggle()
|
||||
|
||||
moveUp: ->
|
||||
selected = $('#filefinder .selected')
|
||||
if selected.prev().length
|
||||
selected.prev().addClass 'selected'
|
||||
selected.removeClass 'selected'
|
||||
|
||||
moveDown: ->
|
||||
selected = $('#filefinder .selected')
|
||||
if selected.next().length
|
||||
selected.next().addClass 'selected'
|
||||
selected.removeClass 'selected'
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
Extension = require 'extension'
|
||||
FilefinderPane = require 'filefinder/filefinder-pane'
|
||||
ModalSelector = require 'modal-selector'
|
||||
|
||||
module.exports =
|
||||
class Filefinder extends Extension
|
||||
@@ -7,8 +9,9 @@ class Filefinder extends Extension
|
||||
atom.keybinder.load require.resolve "filefinder/key-bindings.coffee"
|
||||
atom.on 'project:open', @startup
|
||||
|
||||
startup: =>
|
||||
@pane = new FilefinderPane this
|
||||
startup: (@project) =>
|
||||
@pane = new ModalSelector _.map @project.urls(), (url) ->
|
||||
name: (url.replace "#{window.url}/", ''), url: url
|
||||
|
||||
toggle: ->
|
||||
@pane?.toggle()
|
||||
@pane?.toggle()
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<style>
|
||||
#modal .content {
|
||||
background: #ededed;
|
||||
padding: 0;
|
||||
}
|
||||
#modal .close {
|
||||
display: none;
|
||||
}
|
||||
#filefinder .filelist {
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
padding: 10px 0;
|
||||
}
|
||||
#filefinder input[type=search] {
|
||||
width: 95%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#modal .content {
|
||||
min-width: 200px;
|
||||
height: 100%;
|
||||
background-color: #DDE3EA;
|
||||
color: #000;
|
||||
border-right: 1px solid #B4B4B4;
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#modal .content .cwd {
|
||||
padding-top: 5px;
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
color: #708193;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
#modal .content ul {
|
||||
margin: 0;
|
||||
padding-top: 2px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#modal .content li {
|
||||
padding: 0;
|
||||
padding-left: 5px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#modal .content li.selected {
|
||||
background-image: -webkit-gradient(linear,0% 0,0% 100%,from(#BCCBEB),to(#8094BB));
|
||||
border-top: 1px solid #A0AFCD;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id='filefinder'>
|
||||
<input type='search'>
|
||||
<br>
|
||||
<ul class='filelist'>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,122 +0,0 @@
|
||||
/*!
|
||||
* string_score.js: String Scoring Algorithm 0.1.9
|
||||
*
|
||||
* http://joshaven.com/string_score
|
||||
* https://github.com/joshaven/string_score
|
||||
*
|
||||
* Copyright (C) 2009-2011 Joshaven Potter <yourtech@gmail.com>
|
||||
* Copyright (C) 2010-2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||
* MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Date: Tue Mar 1 2011
|
||||
*/
|
||||
|
||||
/**
|
||||
* Scores a string against another string.
|
||||
* 'Hello World'.score('he'); //=> 0.5931818181818181
|
||||
* 'Hello World'.score('Hello'); //=> 0.7318181818181818
|
||||
*/
|
||||
String.prototype.score = function(abbreviation, fuzziness) {
|
||||
var total_character_score = 0,
|
||||
abbreviation_length = abbreviation.length,
|
||||
string = this,
|
||||
string_length = string.length,
|
||||
start_of_string_bonus,
|
||||
abbreviation_score,
|
||||
fuzzies=1,
|
||||
final_score;
|
||||
|
||||
// If the string is equal to the abbreviation, perfect match.
|
||||
if (string == abbreviation) {return 1.0;}
|
||||
|
||||
// Walk through abbreviation and add up scores.
|
||||
for (var i = 0,
|
||||
character_score/* = 0*/,
|
||||
index_in_string/* = 0*/,
|
||||
c/* = ''*/,
|
||||
index_c_lowercase/* = 0*/,
|
||||
index_c_uppercase/* = 0*/,
|
||||
min_index/* = 0*/;
|
||||
i < abbreviation_length;
|
||||
++i) {
|
||||
|
||||
// Find the first case-insensitive match of a character.
|
||||
c = abbreviation[i];
|
||||
|
||||
//index_in_string = __first_valid_index(
|
||||
// string.indexOf(c.toLowerCase()),
|
||||
// string.indexOf(c.toUpperCase())
|
||||
//);
|
||||
// Inlined the above call below.
|
||||
index_c_lowercase = string.indexOf(c.toLowerCase());
|
||||
index_c_uppercase = string.indexOf(c.toUpperCase());
|
||||
min_index = Math.min(index_c_lowercase, index_c_uppercase);
|
||||
index_in_string = (min_index > -1) ?
|
||||
min_index :
|
||||
Math.max(index_c_lowercase, index_c_uppercase);
|
||||
// End inlining.
|
||||
|
||||
if (index_in_string === -1) {
|
||||
if (fuzziness) {
|
||||
fuzzies += 1-fuzziness;
|
||||
break;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
character_score = 0.1;
|
||||
}
|
||||
|
||||
// Set base score for matching 'c'.
|
||||
|
||||
// Same case bonus.
|
||||
if (string[index_in_string] === c) {
|
||||
character_score += 0.1;
|
||||
}
|
||||
|
||||
// Consecutive letter & start-of-string Bonus
|
||||
if (index_in_string === 0) {
|
||||
// Increase the score when matching first character of the
|
||||
// remainder of the string
|
||||
character_score += 0.6;
|
||||
if (i === 0) {
|
||||
// If match is the first character of the string
|
||||
// & the first character of abbreviation, add a
|
||||
// start-of-string match bonus.
|
||||
start_of_string_bonus = 1 //true;
|
||||
}
|
||||
}
|
||||
|
||||
// Acronym Bonus
|
||||
// Weighing Logic: Typing the first character of an acronym is as if you
|
||||
// preceded it with two perfect character matches.
|
||||
if (string.charAt(index_in_string - 1) === ' ') {
|
||||
character_score += 0.8; // * Math.min(index_in_string, 5); // Cap bonus at 0.4 * 5
|
||||
}
|
||||
|
||||
// Left trim the already matched part of the string
|
||||
// (forces sequential matching).
|
||||
string = string.substring(index_in_string + 1, string_length);
|
||||
|
||||
total_character_score += character_score;
|
||||
} // end of for loop
|
||||
|
||||
// Uncomment to weigh smaller words higher.
|
||||
// return total_character_score / string_length;
|
||||
|
||||
abbreviation_score = total_character_score / abbreviation_length;
|
||||
//percentage_of_matched_string = abbreviation_length / string_length;
|
||||
//word_score = abbreviation_score * percentage_of_matched_string;
|
||||
|
||||
// Reduce penalty for longer strings.
|
||||
//final_score = (word_score + abbreviation_score) / 2;
|
||||
final_score = ((abbreviation_score * (abbreviation_length / string_length)) + abbreviation_score) / 2;
|
||||
|
||||
final_score = final_score / fuzzies;
|
||||
|
||||
if (start_of_string_bonus && (final_score + 0.15 < 1)) {
|
||||
final_score += 0.15;
|
||||
}
|
||||
|
||||
return final_score;
|
||||
};
|
||||
159
src/atom/modal-selector.coffee
Normal file
159
src/atom/modal-selector.coffee
Normal file
@@ -0,0 +1,159 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
|
||||
Modal = require 'modal'
|
||||
|
||||
jQuery = $
|
||||
require 'stringscore'
|
||||
|
||||
module.exports =
|
||||
class ModalSelector extends Modal
|
||||
selectorHTML: '''
|
||||
<div id="modal-selector">
|
||||
<input type="search">
|
||||
<br>
|
||||
<ul class="list">
|
||||
</ul>
|
||||
</div>
|
||||
'''
|
||||
|
||||
showing: false
|
||||
|
||||
# The items to filter. An Array of {name:name, url:url} objects.
|
||||
list: []
|
||||
|
||||
constructor: (@list) ->
|
||||
super @selectorHTML
|
||||
|
||||
head = $('head')[0]
|
||||
style = document.createElement 'style'
|
||||
rules = document.createTextNode @selectorCSS
|
||||
style.type = 'text/css'
|
||||
style.appendChild rules
|
||||
head.appendChild style
|
||||
|
||||
$('#modal-selector input').live 'keydown', @onKeydown
|
||||
|
||||
onKeydown: (e) =>
|
||||
keys = up: 38, down: 40, enter: 13
|
||||
|
||||
if e.keyCode is keys.enter
|
||||
@openSelected()
|
||||
false
|
||||
else if e.keyCode is keys.up
|
||||
@moveUp()
|
||||
else if e.keyCode is keys.down
|
||||
@moveDown()
|
||||
else
|
||||
@filter()
|
||||
|
||||
show: ->
|
||||
console.log 'cool'
|
||||
super
|
||||
@filter()
|
||||
|
||||
filter: ->
|
||||
if query = $('#modal-selector input').val()
|
||||
items = @findMatchingItems query
|
||||
else
|
||||
items = @list
|
||||
$('#modal-selector ul').empty()
|
||||
for {name, url} in items[0..10]
|
||||
$('#modal-selector ul').append "<li data-url=#{url}>#{name}</li>"
|
||||
$('#modal-selector input').focus()
|
||||
$('#modal-selector li:first').addClass 'selected'
|
||||
|
||||
findMatchingItems: (query) ->
|
||||
return [] if not query
|
||||
|
||||
results = []
|
||||
for item in @list
|
||||
{name, url} = item
|
||||
score = name.score query
|
||||
if score > 0
|
||||
# Basename matches count for more.
|
||||
if not query.match '/'
|
||||
if name.match '/'
|
||||
score += name.replace(/^.*\//, '').score query
|
||||
else
|
||||
score *= 2
|
||||
results.push [score, item]
|
||||
|
||||
sorted = results.sort (a, b) -> b[0] - a[0]
|
||||
_.map sorted, (el) -> el[1]
|
||||
|
||||
openSelected: ->
|
||||
text = $('#modal-selector .selected').text()
|
||||
window.open _.find(@list, (item) -> item.name is text).url
|
||||
@toggle()
|
||||
|
||||
moveUp: ->
|
||||
selected = $('#modal-selector .selected')
|
||||
if selected.prev().length
|
||||
selected.prev().addClass 'selected'
|
||||
selected.removeClass 'selected'
|
||||
|
||||
moveDown: ->
|
||||
selected = $('#modal-selector .selected')
|
||||
if selected.next().length
|
||||
selected.next().addClass 'selected'
|
||||
selected.removeClass 'selected'
|
||||
|
||||
selectorCSS: '''
|
||||
#modal .content {
|
||||
background: #ededed;
|
||||
padding: 0;
|
||||
}
|
||||
#modal .close {
|
||||
display: none;
|
||||
}
|
||||
#modal-selector .list {
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
padding: 10px 0;
|
||||
}
|
||||
#modal-selector input[type=search] {
|
||||
width: 95%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#modal .content {
|
||||
min-width: 200px;
|
||||
height: 100%;
|
||||
background-color: #DDE3EA;
|
||||
color: #000;
|
||||
border-right: 1px solid #B4B4B4;
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#modal .content .cwd {
|
||||
padding-top: 5px;
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
color: #708193;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
#modal .content ul {
|
||||
margin: 0;
|
||||
padding-top: 2px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#modal .content li {
|
||||
padding: 0;
|
||||
padding-left: 5px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#modal .content li.selected {
|
||||
background-image: -webkit-gradient(linear,0% 0,0% 100%,from(#BCCBEB),to(#8094BB));
|
||||
border-top: 1px solid #A0AFCD;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
'''
|
||||
Reference in New Issue
Block a user