<commit_before>var	sys = require('sys'),
	http = require('http'),
	querystring = require('querystring'),
	events = require('events'),
	oauth = require('node-oauth'),
	streamparser = require('./parser');

function merge(defaults, options) {
	if (typeof options === 'object') {
		var keys = Object.keys(options);
		for (var i = 0, len = keys.length; i < len; i++) {
			var k = keys[i];
			if (options[k] !== undefined) defaults[k] = options[k];
		}
	}
	return defaults;
}

function Twitter(options) {
	if (!(this instanceof Twitter)) return new Twitter(options);

	var defaults = {
		consumer_key: null,
		consumer_secret: null,
		access_token_key: null,
		access_token_secret: null,

		headers: {
			'Accept': '*/*',
			'Connection': 'close',
			'User-Agent': 'node-twitter/' + Twitter.VERSION
		},

		request_token_url: 'https://api.twitter.com/oauth/request_token',
		access_token_url: 'https://api.twitter.com/oauth/access_token',
		authenticate_url: 'https://api.twitter.com/oauth/authenticate',
		authorize_url: 'https://api.twitter.com/oauth/authorize',

		rest_base: 'https://api.twitter.com/1',
		search_base: 'http://search.twitter.com',
		stream_base: 'http://stream.twitter.com/1',
		user_stream_base: 'https://userstream.twitter.com/2',
		site_stream_base: 'https://betastream.twitter.com/2b'
	};
	this.options = merge(defaults, options);

	// FIXME: first null is auth dest; fix when we add auth helper funcs
	this.oauth = new oauth.OAuth(
		this.options.request_token_url,
		this.options.access_token_url,
		this.options.consumer_key,
		this.options.consumer_secret,
		'1.0', null, 'HMAC-SHA1', null,
		this.options.headers);
}
Twitter.VERSION = '0.1';
module.exports = Twitter;

/*
 * GET
 */
Twitter.prototype.get = function(url, params, callback) {
	if (typeof params === 'function') {
		callback = params;
		params = null;
	}

	this.oauth.get(this.options.rest_base + url,
		this.options.access_token_key,
		this.options.access_token_secret,
	function(error, data, response) {
		if (error) {
			var err = new Error('HTTP Error '
				+ error.statusCode + ': '
				+ http.STATUS_CODES[error.statusCode]);
			err.statusCode = error.statusCode;
			err.data = error.data;
			callback(err);
		} else {
			try {
				var json = JSON.parse(data);
			} catch(error) {
				callback(error);
			}
			callback(json);
		}
	});
	return this;
}

/*
 * POST
 */
Twitter.prototype.post = function(url, content, content_type, callback) {
	if (typeof content === 'function') {
		callback = content;
		content = null;
		content_type = null;
	} else if (typeof content_type === 'function') {
		callback = content_type;
		content_type = null;
	}

	this.oauth.post(this.options.rest_base + url,
		this.options.access_token_key,
		this.options.access_token_secret,
		content, content_type,
	function(error, data, response) {
		if (error) {
			var err = new Error('HTTP Error '
				+ error.statusCode + ': '
				+ http.STATUS_CODES[error.statusCode]);
			err.statusCode = error.statusCode;
			err.data = error.data;
			callback(err);
		} else {
			try {
				var json = JSON.parse(data);
			} catch(error) {
				callback(error);
			}
			callback(json);
		}
	});
	return this;
}

/*
 * STREAM
 */
Twitter.prototype.stream = function(method, params, callback) {
	if (typeof params === 'function') {
		callback = params;
		params = null;
	}

	var stream_base = this.options.stream_base;

	if (method === 'user')
		stream_base = this.options.user_stream_base;
	else if (method === 'site')
		stream_base = this.options.site_stream_base;

	var url = '/' + method + '.json?' + querystring.stringify(params);

	var stream = new events.EventEmitter();
	var request = this.oauth.get(stream_base + url,
		this.options.access_token_key,
		this.options.access_token_secret);

	request.on('response', function(response) {
		var parser = new streamparser();

		response.on('data', function(chunk) {
			parser.receive(chunk);
		});
		response.on('end', function () {
			stream.emit('end');
			stream.emit('close');
		});

		parser.on('object', function(data) {
			stream.emit('data', data);
		});
		parser.on('error', function(error) {
			stream.emit('error', new Error('Parser error: ' + error.message));
		});
	});
	request.end();

	callback(stream);
	return this;
}

/*
 * CONVENIENCE FUNCTIONS
 */

// Timeline resources

Twitter.prototype.getPublicTimeline = function(params, callback) {
	var url = '/statuses/public_timeline.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getHomeTimeline = function(params, callback) {
	var url = '/statuses/home_timeline.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getFriendsTimeline = function(params, callback) {
	var url = '/statuses/friends_timeline.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getUserTimeline = function(params, callback) {
	var url = '/statuses/user_timeline.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getMentions = function(params, callback) {
	var url = '/statuses/mentions.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getRetweetedByMe = function(params, callback) {
	var url = '/statuses/retweeted_by_me.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getRetweetedToMe = function(params, callback) {
	var url = '/statuses/retweeted_to_me.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getRetweetsOfMe = function(params, callback) {
	var url = '/statuses/retweets_of_me.json';
	this.get(url, params, callback);
	return this;
}

// Tweets resources

Twitter.prototype.showStatus = function(id, callback) {
	var url = '/statuses/show/' + id + '.json';
	this.get(url, null, callback);
	return this;
}

Twitter.prototype.updateStatus = function(params, callback) {
	var url = '/statuses/update.json';
	var defaults = {
		trim_user: 1,
		include_entities: 1
	};
	params = merge(defaults, params);
	this.post(url, params, null, callback);
	return this;
}

Twitter.prototype.destroyStatus = function(id, callback) {
	var url = '/statuses/destroy/' + id + '.json';
	this.post(url, null, null, callback);
	return this;
}

Twitter.prototype.retweetStatus = function(id, callback) {
	var url = '/statuses/retweet/' + id + '.json';
	this.post(url, null, null, callback);
	return this;
}

Twitter.prototype.retweets = function(id, params, callback) {
	var url = '/statuses/retweets/' + id + '.json';
	this.post(url, params, null, callback);
	return this;
}

Twitter.prototype.retweetedBy = function(id, params, callback) {
	var url = '/statuses/' + id + '/retweeted_by.json';
	this.post(url, params, null, callback);
	return this;
}

Twitter.prototype.retweetedByIds = function(id, params, callback) {
	var url = '/statuses/' + id + '/retweeted_by/ids.json';
	this.post(url, params, null, callback);
	return this;
}

// User resources

// Trends resources

// Local Trends resources

// List resources

// List Members resources

// List Subscribers resources

// Direct Messages resources

// Friendship resources

// Friends and Followers resources

// Account resources

Twitter.prototype.verifyCredentials = function(callback) {
	var url = '/account/verify_credentials.json';
	this.get(url, null, callback);
	return this;
}

// Notification resources

// Block resources

// Spam Reporting resources

// Saved Searches resources

// OAuth resources

// Geo resources

// Legal resources

// Help resources

// Streamed Tweets resources

// Search resources
<commit_msg>Fix node-oauth import/require - node-oauth's module name is just 'oauth'<commit_after>var	sys = require('sys'),
	http = require('http'),
	querystring = require('querystring'),
	events = require('events'),
	oauth = require('oauth'),
	streamparser = require('./parser');

function merge(defaults, options) {
	if (typeof options === 'object') {
		var keys = Object.keys(options);
		for (var i = 0, len = keys.length; i < len; i++) {
			var k = keys[i];
			if (options[k] !== undefined) defaults[k] = options[k];
		}
	}
	return defaults;
}

function Twitter(options) {
	if (!(this instanceof Twitter)) return new Twitter(options);

	var defaults = {
		consumer_key: null,
		consumer_secret: null,
		access_token_key: null,
		access_token_secret: null,

		headers: {
			'Accept': '*/*',
			'Connection': 'close',
			'User-Agent': 'node-twitter/' + Twitter.VERSION
		},

		request_token_url: 'https://api.twitter.com/oauth/request_token',
		access_token_url: 'https://api.twitter.com/oauth/access_token',
		authenticate_url: 'https://api.twitter.com/oauth/authenticate',
		authorize_url: 'https://api.twitter.com/oauth/authorize',

		rest_base: 'https://api.twitter.com/1',
		search_base: 'http://search.twitter.com',
		stream_base: 'http://stream.twitter.com/1',
		user_stream_base: 'https://userstream.twitter.com/2',
		site_stream_base: 'https://betastream.twitter.com/2b'
	};
	this.options = merge(defaults, options);

	// FIXME: first null is auth dest; fix when we add auth helper funcs
	this.oauth = new oauth.OAuth(
		this.options.request_token_url,
		this.options.access_token_url,
		this.options.consumer_key,
		this.options.consumer_secret,
		'1.0', null, 'HMAC-SHA1', null,
		this.options.headers);
}
Twitter.VERSION = '0.1';
module.exports = Twitter;

/*
 * GET
 */
Twitter.prototype.get = function(url, params, callback) {
	if (typeof params === 'function') {
		callback = params;
		params = null;
	}

	this.oauth.get(this.options.rest_base + url,
		this.options.access_token_key,
		this.options.access_token_secret,
	function(error, data, response) {
		if (error) {
			var err = new Error('HTTP Error '
				+ error.statusCode + ': '
				+ http.STATUS_CODES[error.statusCode]);
			err.statusCode = error.statusCode;
			err.data = error.data;
			callback(err);
		} else {
			try {
				var json = JSON.parse(data);
			} catch(error) {
				callback(error);
			}
			callback(json);
		}
	});
	return this;
}

/*
 * POST
 */
Twitter.prototype.post = function(url, content, content_type, callback) {
	if (typeof content === 'function') {
		callback = content;
		content = null;
		content_type = null;
	} else if (typeof content_type === 'function') {
		callback = content_type;
		content_type = null;
	}

	this.oauth.post(this.options.rest_base + url,
		this.options.access_token_key,
		this.options.access_token_secret,
		content, content_type,
	function(error, data, response) {
		if (error) {
			var err = new Error('HTTP Error '
				+ error.statusCode + ': '
				+ http.STATUS_CODES[error.statusCode]);
			err.statusCode = error.statusCode;
			err.data = error.data;
			callback(err);
		} else {
			try {
				var json = JSON.parse(data);
			} catch(error) {
				callback(error);
			}
			callback(json);
		}
	});
	return this;
}

/*
 * STREAM
 */
Twitter.prototype.stream = function(method, params, callback) {
	if (typeof params === 'function') {
		callback = params;
		params = null;
	}

	var stream_base = this.options.stream_base;

	if (method === 'user')
		stream_base = this.options.user_stream_base;
	else if (method === 'site')
		stream_base = this.options.site_stream_base;

	var url = '/' + method + '.json?' + querystring.stringify(params);

	var stream = new events.EventEmitter();
	var request = this.oauth.get(stream_base + url,
		this.options.access_token_key,
		this.options.access_token_secret);

	request.on('response', function(response) {
		var parser = new streamparser();

		response.on('data', function(chunk) {
			parser.receive(chunk);
		});
		response.on('end', function () {
			stream.emit('end');
			stream.emit('close');
		});

		parser.on('object', function(data) {
			stream.emit('data', data);
		});
		parser.on('error', function(error) {
			stream.emit('error', new Error('Parser error: ' + error.message));
		});
	});
	request.end();

	callback(stream);
	return this;
}

/*
 * CONVENIENCE FUNCTIONS
 */

// Timeline resources

Twitter.prototype.getPublicTimeline = function(params, callback) {
	var url = '/statuses/public_timeline.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getHomeTimeline = function(params, callback) {
	var url = '/statuses/home_timeline.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getFriendsTimeline = function(params, callback) {
	var url = '/statuses/friends_timeline.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getUserTimeline = function(params, callback) {
	var url = '/statuses/user_timeline.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getMentions = function(params, callback) {
	var url = '/statuses/mentions.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getRetweetedByMe = function(params, callback) {
	var url = '/statuses/retweeted_by_me.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getRetweetedToMe = function(params, callback) {
	var url = '/statuses/retweeted_to_me.json';
	this.get(url, params, callback);
	return this;
}

Twitter.prototype.getRetweetsOfMe = function(params, callback) {
	var url = '/statuses/retweets_of_me.json';
	this.get(url, params, callback);
	return this;
}

// Tweets resources

Twitter.prototype.showStatus = function(id, callback) {
	var url = '/statuses/show/' + id + '.json';
	this.get(url, null, callback);
	return this;
}

Twitter.prototype.updateStatus = function(params, callback) {
	var url = '/statuses/update.json';
	var defaults = {
		trim_user: 1,
		include_entities: 1
	};
	params = merge(defaults, params);
	this.post(url, params, null, callback);
	return this;
}

Twitter.prototype.destroyStatus = function(id, callback) {
	var url = '/statuses/destroy/' + id + '.json';
	this.post(url, null, null, callback);
	return this;
}

Twitter.prototype.retweetStatus = function(id, callback) {
	var url = '/statuses/retweet/' + id + '.json';
	this.post(url, null, null, callback);
	return this;
}

Twitter.prototype.retweets = function(id, params, callback) {
	var url = '/statuses/retweets/' + id + '.json';
	this.post(url, params, null, callback);
	return this;
}

Twitter.prototype.retweetedBy = function(id, params, callback) {
	var url = '/statuses/' + id + '/retweeted_by.json';
	this.post(url, params, null, callback);
	return this;
}

Twitter.prototype.retweetedByIds = function(id, params, callback) {
	var url = '/statuses/' + id + '/retweeted_by/ids.json';
	this.post(url, params, null, callback);
	return this;
}

// User resources

// Trends resources

// Local Trends resources

// List resources

// List Members resources

// List Subscribers resources

// Direct Messages resources

// Friendship resources

// Friends and Followers resources

// Account resources

Twitter.prototype.verifyCredentials = function(callback) {
	var url = '/account/verify_credentials.json';
	this.get(url, null, callback);
	return this;
}

// Notification resources

// Block resources

// Spam Reporting resources

// Saved Searches resources

// OAuth resources

// Geo resources

// Legal resources

// Help resources

// Streamed Tweets resources

// Search resources
<|endoftext|>


<commit_before>define("ace/snippets/json",["require","exports","module"], function(require, exports, module) {
"use strict";

exports.snippetText =undefined;
exports.scope = "json";

});
<commit_msg>Update tmp/bower_components/ace-builds/src/snippets/json.js<commit_after><|endoftext|>


<commit_before>const {Emitter} = require("atom")
const {shrinkRangeEndToBeforeNewLine, collectRangeInBufferRow} = require("./utils")

module.exports = class OccurrenceManager {
  constructor(vimState) {
    this.vimState = vimState
    const {editor} = vimState

    vimState.onDidDestroy(() => this.destroy())

    this.emitter = new Emitter()
    this.patterns = []

    this.markerLayer = editor.addMarkerLayer()
    this.decorationLayer = editor.decorateMarkerLayer(this.markerLayer, {
      type: "highlight",
      class: "vim-mode-plus-occurrence-base",
    })

    // All maker create/destroy/css-update is done by reacting patters's change.
    // -------------------------
    this.onDidChangePatterns(({pattern, occurrenceType}) => {
      if (pattern) {
        this.markBufferRangeByPattern(pattern, occurrenceType)
      } else {
        this.clearMarkers()
      }
      this.updateEditorElement()
    })

    this.markerLayer.onDidUpdate(() => {
      this.destroyMarkers(this.getMarkers().filter(marker => !marker.isValid()))
    })
  }

  markBufferRangeByPattern(pattern, occurrenceType) {
    const markRange = ({range}) => this.markerLayer.markBufferRange(range, {invalidate: "inside"})
    const {editor} = this.vimState
    if (occurrenceType === "subword") {
      const subwordRegex = editor.getLastCursor().subwordRegExp()
      const cache = {}
      editor.scan(pattern, ({range}) => {
        const {row} = range.start
        if (!cache[row]) cache[row] = collectRangeInBufferRow(editor, row, subwordRegex)
        if (cache[row].some(subwordRange => subwordRange.isEqual(range))) markRange({range})
      })
    } else {
      editor.scan(pattern, markRange)
    }
  }

  updateEditorElement() {
    this.vimState.editorElement.classList.toggle("has-occurrence", this.hasMarkers())
  }

  // Callback get passed following object
  // - pattern: can be undefined on reset event
  onDidChangePatterns(fn) {
    return this.emitter.on("did-change-patterns", fn)
  }

  destroy() {
    this.decorationLayer.destroy()
    this.markerLayer.destroy()
  }

  // Patterns
  hasPatterns() {
    return this.patterns.length > 0
  }

  resetPatterns() {
    this.patterns = []
    this.emitter.emit("did-change-patterns", {})
  }

  addPattern(pattern = null, {reset, occurrenceType = "base"} = {}) {
    if (reset) this.clearMarkers()

    this.patterns.push(pattern)
    this.emitter.emit("did-change-patterns", {pattern, occurrenceType})
  }

  saveLastPattern(occurrenceType) {
    this.vimState.globalState.set("lastOccurrencePattern", this.buildPattern())
    this.vimState.globalState.set("lastOccurrenceType", occurrenceType)
  }

  // Return regex representing final pattern.
  // Used to cache final pattern to each instance of operator so that we can
  // repeat recorded operation by `.`.
  // Pattern can be added interactively one by one, but we save it as union pattern.
  buildPattern() {
    return new RegExp(this.patterns.map(regex => regex.source).join("|"), "g")
  }

  // Markers
  // -------------------------
  clearMarkers() {
    this.markerLayer.clear()
  }

  destroyMarkers(markers) {
    markers.forEach(marker => marker.destroy())
    // whenerver we destroy marker, we should sync `has-occurrence` scope in marker state..
    this.updateEditorElement()
  }

  hasMarkers() {
    return this.markerLayer.getMarkerCount() > 0
  }

  getMarkers() {
    return this.markerLayer.getMarkers()
  }

  getMarkerBufferRanges() {
    return this.markerLayer.getMarkers().map(marker => marker.getBufferRange())
  }

  getMarkerCount() {
    return this.markerLayer.getMarkerCount()
  }

  // Return occurrence markers intersecting given ranges
  getMarkersIntersectsWithSelection(selection, exclusive = false) {
    // findmarkers()'s intersectsBufferRange param have no exclusive control
    // So need extra check to filter out unwanted marker.
    // But basically I should prefer findMarker since It's fast than iterating
    // whole markers manually.
    const range = shrinkRangeEndToBeforeNewLine(selection.getBufferRange())
    return this.markerLayer
      .findMarkers({intersectsBufferRange: range})
      .filter(marker => range.intersectsWith(marker.getBufferRange(), exclusive))
  }

  getMarkerAtPoint(point) {
    // We have to check all returned marker until found, since we do aditional marker validation.
    // e.g. For text `abc()`, mark for `abc` and `(`. cursor on `(` char return multiple marker
    // and we pick `(` by isGreaterThan check.
    return this.markerLayer
      .findMarkers({containsBufferPosition: point})
      .find(marker => marker.getBufferRange().end.isGreaterThan(point))
  }

  // Select occurrence marker bufferRange intersecting current selections.
  // - Return: true/false to indicate success or fail
  //
  // Do special handling for which occurrence range become lastSelection
  // e.g.
  //  - c(change): So that autocomplete+popup shows at original cursor position or near.
  //  - g U(upper-case): So that undo/redo can respect last cursor position.
  select(wise) {
    const isVisualMode = this.vimState.mode === "visual"
    const closestRangeIndexByOriginalSelection = new Map()
    const rangesToSelect = []
    const markersSelected = []
    const {editor} = this.vimState
    const rowsToSelect = new Set()

    for (const selection of editor.getSelections()) {
      const markers = this.getMarkersIntersectsWithSelection(selection, isVisualMode)
      if (!markers.length) continue

      const ranges = markers.map(marker => marker.getBufferRange())
      markersSelected.push(...markers)
      // [HACK] Find closest occurrence range and move it to last item in ranges array.
      // Purpose of this is to make closest range become **last-selection**.
      // It is important to show autocomplete+ popup at proper position( popup shows up at last-selection ).
      // E.g. `c o f`(change occurrence in a-function) show autocomplete+ popup at closest occurrence.
      const closestRange = this.getClosestRangeForSelection(ranges, selection)
      ranges.splice(ranges.indexOf(closestRange), 1) // remove
      ranges.push(closestRange) // then push to last

      if (wise === "linewise") {
        // NOTE: purpose of reverse here is to process closestRange first, make closestRange survive over other ranges.
        for (const range of ranges.slice().reverse()) {
          const row = range.start.row
          if (!rowsToSelect.has(row)) {
            rowsToSelect.add(row)
            rangesToSelect.push(range)
          }
        }
      } else {
        rangesToSelect.push(...ranges)
      }

      const closestRangeIndex = rangesToSelect.indexOf(closestRange)
      if (closestRangeIndex >= 0) {
        // Remember connection between originalSelection and index of closestRange.
        // After select occurrence, selection is re-created, then we have to migrate mutation info using this info.
        closestRangeIndexByOriginalSelection.set(selection, closestRangeIndex)
      }
    }

    if (rangesToSelect.length) {
      if (isVisualMode) {
        // To avoid selected occurrence ruined by normalization when disposing current submode to shift to new submode.
        this.vimState.modeManager.deactivate()
        this.vimState.submode = null
      }

      editor.setSelectedBufferRanges(rangesToSelect)
      const selections = editor.getSelections()
      closestRangeIndexByOriginalSelection.forEach((closestRangeIndex, originalSelection) => {
        this.vimState.mutationManager.migrateMutation(originalSelection, selections[closestRangeIndex])
      })

      // wise of occurrence is default 'characterwise' but when wise was passed.
      // It apply forced wise AFTER select occurrence.
      // Currently only linewise wise is supported.
      // When this is used is linewise-bounded-operator + occurrence operation
      // e.g. `g / o p`(`g /` is linewise-bounded-operator)
      // e.g. `g o v i p D`(`D` is delete-line, linewise-bounded).

      this.destroyMarkers(markersSelected)
      for (const $selection of this.vimState.swrap.getSelections(editor)) {
        $selection.saveProperties()
        if (wise === "linewise") $selection.applyWise(wise)
      }
      const originalSelections = editor.getSelections()
      if (wise === "linewise") editor.mergeSelectionsOnSameRows()
      return true
    } else {
      return false
    }
  }

  // Which occurrence become lastSelection is determined by following order
  //  1. Occurrence under original cursor position
  //  2. forwarding in same row
  //  3. first occurrence in same row
  //  4. forwarding (wrap-end)
  getClosestRangeForSelection(ranges, selection) {
    const point = this.vimState.mutationManager.mutationsBySelection.get(selection).initialPoint

    const range = ranges.find(range => range.containsPoint(point))
    if (range) return range

    const rangesInSameRow = ranges.filter(range => range.start.row === point.row)
    if (rangesInSameRow.length) ranges = rangesInSameRow
    return ranges.find(range => range.start.isGreaterThan(point)) || ranges[0]
  }
}
<commit_msg>:memo: intention for pre-exclude(simplify mutation migration)<commit_after>const {Emitter} = require("atom")
const {shrinkRangeEndToBeforeNewLine, collectRangeInBufferRow} = require("./utils")

module.exports = class OccurrenceManager {
  constructor(vimState) {
    this.vimState = vimState
    const {editor} = vimState

    vimState.onDidDestroy(() => this.destroy())

    this.emitter = new Emitter()
    this.patterns = []

    this.markerLayer = editor.addMarkerLayer()
    this.decorationLayer = editor.decorateMarkerLayer(this.markerLayer, {
      type: "highlight",
      class: "vim-mode-plus-occurrence-base",
    })

    // All maker create/destroy/css-update is done by reacting patters's change.
    // -------------------------
    this.onDidChangePatterns(({pattern, occurrenceType}) => {
      if (pattern) {
        this.markBufferRangeByPattern(pattern, occurrenceType)
      } else {
        this.clearMarkers()
      }
      this.updateEditorElement()
    })

    this.markerLayer.onDidUpdate(() => {
      this.destroyMarkers(this.getMarkers().filter(marker => !marker.isValid()))
    })
  }

  markBufferRangeByPattern(pattern, occurrenceType) {
    const markRange = ({range}) => this.markerLayer.markBufferRange(range, {invalidate: "inside"})
    const {editor} = this.vimState
    if (occurrenceType === "subword") {
      const subwordRegex = editor.getLastCursor().subwordRegExp()
      const cache = {}
      editor.scan(pattern, ({range}) => {
        const {row} = range.start
        if (!cache[row]) cache[row] = collectRangeInBufferRow(editor, row, subwordRegex)
        if (cache[row].some(subwordRange => subwordRange.isEqual(range))) markRange({range})
      })
    } else {
      editor.scan(pattern, markRange)
    }
  }

  updateEditorElement() {
    this.vimState.editorElement.classList.toggle("has-occurrence", this.hasMarkers())
  }

  // Callback get passed following object
  // - pattern: can be undefined on reset event
  onDidChangePatterns(fn) {
    return this.emitter.on("did-change-patterns", fn)
  }

  destroy() {
    this.decorationLayer.destroy()
    this.markerLayer.destroy()
  }

  // Patterns
  hasPatterns() {
    return this.patterns.length > 0
  }

  resetPatterns() {
    this.patterns = []
    this.emitter.emit("did-change-patterns", {})
  }

  addPattern(pattern = null, {reset, occurrenceType = "base"} = {}) {
    if (reset) this.clearMarkers()

    this.patterns.push(pattern)
    this.emitter.emit("did-change-patterns", {pattern, occurrenceType})
  }

  saveLastPattern(occurrenceType) {
    this.vimState.globalState.set("lastOccurrencePattern", this.buildPattern())
    this.vimState.globalState.set("lastOccurrenceType", occurrenceType)
  }

  // Return regex representing final pattern.
  // Used to cache final pattern to each instance of operator so that we can
  // repeat recorded operation by `.`.
  // Pattern can be added interactively one by one, but we save it as union pattern.
  buildPattern() {
    return new RegExp(this.patterns.map(regex => regex.source).join("|"), "g")
  }

  // Markers
  // -------------------------
  clearMarkers() {
    this.markerLayer.clear()
  }

  destroyMarkers(markers) {
    markers.forEach(marker => marker.destroy())
    // whenerver we destroy marker, we should sync `has-occurrence` scope in marker state..
    this.updateEditorElement()
  }

  hasMarkers() {
    return this.markerLayer.getMarkerCount() > 0
  }

  getMarkers() {
    return this.markerLayer.getMarkers()
  }

  getMarkerBufferRanges() {
    return this.markerLayer.getMarkers().map(marker => marker.getBufferRange())
  }

  getMarkerCount() {
    return this.markerLayer.getMarkerCount()
  }

  // Return occurrence markers intersecting given ranges
  getMarkersIntersectsWithSelection(selection, exclusive = false) {
    // findmarkers()'s intersectsBufferRange param have no exclusive control
    // So need extra check to filter out unwanted marker.
    // But basically I should prefer findMarker since It's fast than iterating
    // whole markers manually.
    const range = shrinkRangeEndToBeforeNewLine(selection.getBufferRange())
    return this.markerLayer
      .findMarkers({intersectsBufferRange: range})
      .filter(marker => range.intersectsWith(marker.getBufferRange(), exclusive))
  }

  getMarkerAtPoint(point) {
    // We have to check all returned marker until found, since we do aditional marker validation.
    // e.g. For text `abc()`, mark for `abc` and `(`. cursor on `(` char return multiple marker
    // and we pick `(` by isGreaterThan check.
    return this.markerLayer
      .findMarkers({containsBufferPosition: point})
      .find(marker => marker.getBufferRange().end.isGreaterThan(point))
  }

  // Select occurrence marker bufferRange intersecting current selections.
  // - Return: true/false to indicate success or fail
  //
  // Do special handling for which occurrence range become lastSelection
  // e.g.
  //  - c(change): So that autocomplete+popup shows at original cursor position or near.
  //  - g U(upper-case): So that undo/redo can respect last cursor position.
  select(wise) {
    const isVisualMode = this.vimState.mode === "visual"
    const closestRangeIndexByOriginalSelection = new Map()
    const rangesToSelect = []
    const markersSelected = []
    const {editor} = this.vimState
    const rowsToSelect = new Set()

    for (const selection of editor.getSelections()) {
      const markers = this.getMarkersIntersectsWithSelection(selection, isVisualMode)
      if (!markers.length) continue

      const ranges = markers.map(marker => marker.getBufferRange())
      markersSelected.push(...markers)
      // [HACK] Find closest occurrence range and move it to last item in ranges array.
      // Purpose of this is to make closest range become **last-selection**.
      // It is important to show autocomplete+ popup at proper position( popup shows up at last-selection ).
      // E.g. `c o f`(change occurrence in a-function) show autocomplete+ popup at closest occurrence.
      const closestRange = this.getClosestRangeForSelection(ranges, selection)
      ranges.splice(ranges.indexOf(closestRange), 1) // remove
      ranges.push(closestRange) // then push to last

      // In linewise-occurence, select one occurrence per single line. We achieve this by not adding rangesToSelect.
      // This is far simpler than "select-all-occurrence then merge-selections then re-associates migration information".
      if (wise === "linewise") {
        // NOTE: purpose of reverse here is to process closestRange first, make closestRange survive over other ranges.
        for (const range of ranges.slice().reverse()) {
          const row = range.start.row
          if (!rowsToSelect.has(row)) {
            rowsToSelect.add(row)
            rangesToSelect.push(range)
          }
        }
      } else {
        rangesToSelect.push(...ranges)
      }

      const closestRangeIndex = rangesToSelect.indexOf(closestRange)
      if (closestRangeIndex >= 0) {
        // Remember connection between originalSelection and index of closestRange.
        // After select occurrence, selection is re-created, then we have to migrate mutation info using this info.
        closestRangeIndexByOriginalSelection.set(selection, closestRangeIndex)
      }
    }

    if (rangesToSelect.length) {
      if (isVisualMode) {
        // To avoid selected occurrence ruined by normalization when disposing current submode to shift to new submode.
        this.vimState.modeManager.deactivate()
        this.vimState.submode = null
      }

      editor.setSelectedBufferRanges(rangesToSelect)
      const selections = editor.getSelections()
      closestRangeIndexByOriginalSelection.forEach((closestRangeIndex, originalSelection) => {
        this.vimState.mutationManager.migrateMutation(originalSelection, selections[closestRangeIndex])
      })

      // wise of occurrence is default 'characterwise' but when wise was passed.
      // It apply forced wise AFTER select occurrence.
      // Currently only linewise wise is supported.
      // When this is used is linewise-bounded-operator + occurrence operation
      // e.g. `g / o p`(`g /` is linewise-bounded-operator)
      // e.g. `g o v i p D`(`D` is delete-line, linewise-bounded).

      this.destroyMarkers(markersSelected)
      for (const $selection of this.vimState.swrap.getSelections(editor)) {
        $selection.saveProperties()
        if (wise === "linewise") $selection.applyWise(wise)
      }
      const originalSelections = editor.getSelections()
      if (wise === "linewise") editor.mergeSelectionsOnSameRows()
      return true
    } else {
      return false
    }
  }

  // Which occurrence become lastSelection is determined by following order
  //  1. Occurrence under original cursor position
  //  2. forwarding in same row
  //  3. first occurrence in same row
  //  4. forwarding (wrap-end)
  getClosestRangeForSelection(ranges, selection) {
    const point = this.vimState.mutationManager.mutationsBySelection.get(selection).initialPoint

    const range = ranges.find(range => range.containsPoint(point))
    if (range) return range

    const rangesInSameRow = ranges.filter(range => range.start.row === point.row)
    if (rangesInSameRow.length) ranges = rangesInSameRow
    return ranges.find(range => range.start.isGreaterThan(point)) || ranges[0]
  }
}
<|endoftext|>


<commit_before>/***************************************************************************
* GNU/GPL Kumbia - PHP Rapid Development Framework
* Simple Object Helper
****************************************************************************
* (c) 2008 Emilio Rafael Silveira Tovar <emilio.rst at gmail.com>
****************************************************************************/

var $Kumbia = {
	/**
	 * Contiene las constantes de ejecucion del Framework
	 **/
	Constant : {
		KUMBIA_PATH : '',
		module: '',
		controller_name : '',
		action_name : ''
	},

	/**
	 * Para manipular cadenas
	 **/
	String : {
		/**
		 * Rellena la cadena
		 * @param s cadena a rellenar
		 * @param length hasta donde se rellena
		 * @param pad_string cadena de relleno
		 * @param pad_type por donde se rellena (left, right, both)
		 * @return la cadena rellena
		 **/
		pad : function(s, length, pad_string, pad_type) {
			if(s.length<length) {
				var value;
				if(pad_type=='left') {
					value = pad_string+s;
				} else if(pad_type=='right') {
					value = s+pad_string;
				} else {
					value = pad_string+s+pad_string;
				}
				return this.pad(value, length, pad_string, pad_type);
			} else {
				return s;
			}
		}
	},

	/**
	 * Validadores
	 **/
	Validators : {
		/**
		 * Verifica si es solo digitos
		 * @param s la cadena a verificar
		 * @return boolean
		 **/
		is_digit : function(s) {
			return s.match(/^\d+$/);
		},

		/**
		 * Verifica si es un numero entero
		 * @param s la cadena a verificar
		 * @return boolean
		 **/
		is_integer : function(s) {
			return s.match(/^-?\d+$/);
		},

		/**
		 * Verifica si es numerico
		 * @param s la cadena a verificar
		 * @return boolean
		 **/
		is_numeric : function(s) {
			return s.match(/^-?\d+$/) || s.match(/^-?\d+\.\d+$/);
		},

		/**
		 * Valida si es una fecha
		 * @param s la cadena a verificar
		 * @return boolean
		 **/
		is_date : function(s) {
			var my_date_s = s.split("-");

			if(!s.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)) return false;

			var year = parseInt(my_date_s[0]);
			var month = parseInt(my_date_s[1])-1;
			var day = parseInt(my_date_s[2]);

			if(!(isNaN(year) || isNaN(month) || isNaN(day))){
				var js_date = new Date(year, month, day);

				if((year==js_date.getFullYear()) && (month==js_date.getMonth()) && (day==js_date.getDate())){
					return true;
				}
			}

			return false;
		},

		/**
		 * Verifica si esta vacia
		 * @param s la cadena a verificar
		 * @return boolean
		 **/
		is_empty : function(s) {
			return s=="";
		}
	},

	/**
	 * Obtener parametros con nombre
	 * @param args array de argumentos
	 **/
	get_params : function(args) {
		var params = {};
		var n = 0;
		for(var i=0; i<args.length; i++){
			if(typeof(args[i])=='string') {
				var r = args[i].match(/^([A-Za-z_]+):\s(.+)$/);
			} else {
				r = false;
			}

			if(r) {
				params[r[1]] = r[2];
			} else {
				params[n] = args[i];
				n++;
			}
		}

		return params;
	},

	/**
	 * Metodo que se encarga de serializar para enviar a traves de url
	 * @param params objeto o array asociativo
	 * tambien puede serializar parametros con nombre
	 **/
	serialize : function(params) {
		if(arguments.length>1) params = this.get_params(arguments);
		var p = new Array();
		for(key in params) {
			p.push(key+'='+params[key]);
		}
		return p.join('&');
	},

	/**
	 * Devuelve una url de Kumbia
	 * @param route ruta de direccionamiento "module/controller/action"
	 *
	 * Soporta parametros con nombre como parametros para serializarse por metodo Get
	 * Ej: $Kumbia.get_kumbia_url('controller/action', 'id: 1')
	 *
	 * Soporta parametros para pasar por url bonita
	 * Ej: $Kumbia.get_kumbia_url('controller/action', '1', '2')
	 *
	 * Soporta objeto cuyos atributos se pasaran por metodo Get
	 * Ej: $Kumbia.get_kumbia_url('controller/action', {id: '1'})
	 *
	 * Soporta array cuyos parametros se pasaran utilizando url bonita
	 * Ej: $Kumbia.get_kumbia_url('controller/action', ['1', '2'])
	 **/
	get_kumbia_url : function(route) {
		var params = this.get_params(arguments);
		delete params[0];

		/**
		 * Procedo a construir la ruta base
		 **/
		var new_route = [route];
		if(params.module) {
			new_route = [params.module].concat(new_route);
			delete params.module;
		}
		if(params.application!=undefined) {
			new_route = [params.application].concat(new_route);
			delete params.application;
		}

		/**
		 * Si no es la aplicacion por defecto, entonces la cargo en la ruta
		 **/
		if(this.Constant.application!='') {
			new_route = [this.Constant.application].concat(new_route);
		}

		/**
		 * Serializo los parametros para pasarlos por la url
		 **/
		var params_get = new Array();
		for(key in params) {
			if(params[key] instanceof Array) {
				for(var i=0; i<params[key].length; i++) {
					new_route.push(params[key][i]);
				}
			} else if(params[key] instanceof String) {
				if(this.Validators.is_digit(key)) {
					new_route.push(params[key]);
				} else {
					params_get.push(key+'='+params[key]);
				}
			} else if(params[key] instanceof Object) {
				params_get.push(this.serialize(params[key]));
			} else {
				if(this.Validators.is_digit(key)) {
					new_route.push(params[key]);
				} else {
					params_get.push(key+'='+params[key]);
				}
			}
		}

		/**
		 * Construyo la ruta con parametros
		 **/
		new_route = new_route.join('/');
		if(params_get.length) {
			new_route += '?';
			new_route += params_get.join('&');
		}

		return this.Constant.KUMBIA_PATH + new_route;
	},

	/**
	 * Redirecciona a una url
	 * @param route ruta de direccionamiento "module/controller/action"
	 *
	 * Soporta parametros con nombre como parametros para serializarse por metodo Get
	 * Ej: $Kumbia.redirect('controller/action', 'id: 1')
	 *
	 * Soporta parametros para pasar por url bonita
	 * Ej: $Kumbia.redirect('controller/action', '1', '2')
	 *
	 * Soporta objeto cuyos atributos se pasaran por metodo Get
	 * Ej: $Kumbia.redirect('controller/action', {id: '1'})
	 *
	 * Soporta array cuyos parametros se pasaran utilizando url bonita
	 * Ej: $Kumbia.redirect('controller/action', ['1', '2'])
	 **/
	redirect : function(route) {
		window.location = this.get_kumbia_url.apply(this,arguments);
	},

	/**
	 * Crea una ventana popup
	 * @param route ruta de direccionamiento "module/controller/action"
	 * @param name nombre de la ventana (soportados los de window.open)
	 * Los parametros con nombre corresponden a las especificaciones a utilizar en la creacion (width, height, dependent, ...)
	 **/
	popup : function(route, name) {
		var params = this.get_params(arguments);
		delete params[0];
		delete params[1];

		var spec = new Array();
		for(key in params) {
			spec.push(key+"="+params[key]);
		}

		window.open(this.get_kumbia_url(route), name, spec.join(","));
	}
}
<commit_msg>Eliminando kumbia.js<commit_after><|endoftext|>


<commit_before>import isObject from 'lodash/lang/isPlainObject';
import camelCase from 'lodash/string/camelCase';

import Watcher from './watcher';

let getAvailableParams = (componentModel) => {
  let bindings = componentModel.getBindings();
  let keys = [];

  if (isObject(bindings)) {
    keys = Object.keys(bindings);
  }

  if (componentModel.isAttributeComponent()) {
    let name = componentModel.getName();
    keys.push(name);
  }

  if (componentModel.hasPipes()) {
    let pipes = componentModel.getPipes();
    for (let key of Object.keys(pipes)) {
      let translatedKey = camelCase(key);
      keys.push(translatedKey);
    }
  }

  if (componentModel.getOptions()) {
    let options = componentModel.getOptions();
    for (let key of Object.keys(options)) {
      let translatedKey = camelCase(key);
      keys.push(translatedKey);
    }
  }

  return keys;
};

const processPipes = (componentModel, attrs, parse) => {
  let normalizedPipes = {};

  if (componentModel.hasPipes()) {
    let pipes = componentModel.getPipes();

    for (let key of Object.keys(pipes)) {
      let Pipe = pipes[key];

      // parse pipe from attributes
      let value = parse(key);

      if (!attrs.hasOwnProperty(key)) {
        // if pipe's key is not exists in attributes - create it
        value = new Pipe();
      } else {
        // if pipe's key is exists in attributes - use it
        if (!(value instanceof Pipe)) {
          throw new Error(`"${this[_name]}" - directive pipe "${key}" has wrong class`);
        }
      }

      normalizedPipes[key] = value;
    }
  }

  return normalizedPipes;
};

const processOptions = (componentModel, parse) => {
  let normalizedOptions = {};

  if (componentModel.hasOptions()) {
    let options = componentModel.getOptions();

    for (let key of Object.keys(options)) {
      let optionInstance = parse(key);

      if (optionInstance) {
        let OptionClass = options[key];

        if (!(optionInstance instanceof OptionClass)) {
          throw Error(`options "${key}" has wrong class`);
        }

        normalizedOptions[key] = optionInstance;
      } else {
        normalizedOptions[key] = null;
      }
    }
  }

  return normalizedOptions;
};

let _scope = Symbol('$scope');
let _attrs = Symbol('$attrs');
let _attrValues = Symbol('attributes-inline-values');
let _element = Symbol('$element');

let _isIsolated = Symbol('is-scope-isolated');
let _definitions = Symbol('definitions');
let _watcher = Symbol('$watcher');
let _name = Symbol('name');
let _pipes = Symbol('pipes');
let _options = Symbol('options');

export default class DirectiveParams {
  constructor($scope, $attrs, $element, componentModel) {
    this[_scope] = $scope;
    this[_element] = $element;

    this[_name] = componentModel.getName();
    this[_isIsolated] = componentModel.isIsolated();
    this[_definitions] = getAvailableParams(componentModel);
    this[_watcher] = new Watcher($scope);

    this[_attrs] = $attrs;
    this[_attrValues] = {};

    for (let key of Object.keys(this[_attrs].$attr)) {
      this[_attrValues][key] = this[_attrs][key];
    }

    // setup pipes
    this[_pipes] = processPipes(componentModel, $attrs, (key) => {
      return this.parse(key);
    });

    // setup options
    this[_options] = processOptions(componentModel, (key) => {
      return this.parse(key);
    });

    // initialize getters
    for (let key of this[_definitions]) {
      Object.defineProperty(this, key, {
        set: () => {
          throw new Error('Can not set directive params property');
        },
        get: () => {
          return this.get(key);
        }
      });
    }
  }

  getElement() {
    return this[_element];
  }

  getAttributes() {
    return this[_attrValues];
  }

  isAvailable(key) {
    return this[_definitions].indexOf(key) != -1;
  }

  get(key) {
    if (!this.isAvailable(key)) {
      throw new Error(`"${this[_name]}" - directive param "${key}" is not defined at directive config`);
    }

    let value = null;

    if (this[_options].hasOwnProperty(key)) {
      value = this[_options][key];
    } else if (this[_pipes].hasOwnProperty(key)) {
      value = this[_pipes][key];
    } else {
      value = this[_scope][key];
    }

    return value;
  }

  watch(key, cb) {
    if (!this.isAvailable(key)) {
      throw new Error(`"${this[_name]}" - can not initialize watcher for "${key}" because this params is not defined at directive config`);
    }

    return this[_watcher].watch(key, cb);
  }

  attr(key) {
    let $attrs = this[_attrs];
    return $attrs[key];
  }

  parse(key) {
    let $scope = this[_scope];
    let $attrs = this[_attrs];

    let parsed = undefined;

    // parse - means that we parse attribute value form parent scope
    if ($attrs.hasOwnProperty(key)) {
      let expression = $attrs[key];
      let scopeToParse = this[_isIsolated] ? $scope.$parent : $scope;

      parsed = scopeToParse.$eval(expression);
    } else {
      //throw new Error(`"${this[_name]}" - can not parse "${key}" because this params is not passed to attributes`);
    }

    return parsed;
  }
}
<commit_msg>add directive params hasAttr() method<commit_after>import isObject from 'lodash/lang/isPlainObject';
import camelCase from 'lodash/string/camelCase';

import Watcher from './watcher';

let getAvailableParams = (componentModel) => {
  let bindings = componentModel.getBindings();
  let keys = [];

  if (isObject(bindings)) {
    keys = Object.keys(bindings);
  }

  if (componentModel.isAttributeComponent()) {
    let name = componentModel.getName();
    keys.push(name);
  }

  if (componentModel.hasPipes()) {
    let pipes = componentModel.getPipes();
    for (let key of Object.keys(pipes)) {
      let translatedKey = camelCase(key);
      keys.push(translatedKey);
    }
  }

  if (componentModel.getOptions()) {
    let options = componentModel.getOptions();
    for (let key of Object.keys(options)) {
      let translatedKey = camelCase(key);
      keys.push(translatedKey);
    }
  }

  return keys;
};

const processPipes = (componentModel, attrs, parse) => {
  let normalizedPipes = {};

  if (componentModel.hasPipes()) {
    let pipes = componentModel.getPipes();

    for (let key of Object.keys(pipes)) {
      let Pipe = pipes[key];

      // parse pipe from attributes
      let value = parse(key);

      if (!attrs.hasOwnProperty(key)) {
        // if pipe's key is not exists in attributes - create it
        value = new Pipe();
      } else {
        // if pipe's key is exists in attributes - use it
        if (!(value instanceof Pipe)) {
          throw new Error(`"${this[_name]}" - directive pipe "${key}" has wrong class`);
        }
      }

      normalizedPipes[key] = value;
    }
  }

  return normalizedPipes;
};

const processOptions = (componentModel, parse) => {
  let normalizedOptions = {};

  if (componentModel.hasOptions()) {
    let options = componentModel.getOptions();

    for (let key of Object.keys(options)) {
      let optionInstance = parse(key);

      if (optionInstance) {
        let OptionClass = options[key];

        if (!(optionInstance instanceof OptionClass)) {
          throw Error(`options "${key}" has wrong class`);
        }

        normalizedOptions[key] = optionInstance;
      } else {
        normalizedOptions[key] = null;
      }
    }
  }

  return normalizedOptions;
};

let _scope = Symbol('$scope');
let _attrs = Symbol('$attrs');
let _attrValues = Symbol('attributes-inline-values');
let _element = Symbol('$element');

let _isIsolated = Symbol('is-scope-isolated');
let _definitions = Symbol('definitions');
let _watcher = Symbol('$watcher');
let _name = Symbol('name');
let _pipes = Symbol('pipes');
let _options = Symbol('options');

export default class DirectiveParams {
  constructor($scope, $attrs, $element, componentModel) {
    this[_scope] = $scope;
    this[_element] = $element;

    this[_name] = componentModel.getName();
    this[_isIsolated] = componentModel.isIsolated();
    this[_definitions] = getAvailableParams(componentModel);
    this[_watcher] = new Watcher($scope);

    this[_attrs] = $attrs;
    this[_attrValues] = {};

    for (let key of Object.keys(this[_attrs].$attr)) {
      this[_attrValues][key] = this[_attrs][key];
    }

    // setup pipes
    this[_pipes] = processPipes(componentModel, $attrs, (key) => {
      return this.parse(key);
    });

    // setup options
    this[_options] = processOptions(componentModel, (key) => {
      return this.parse(key);
    });

    // initialize getters
    for (let key of this[_definitions]) {
      Object.defineProperty(this, key, {
        set: () => {
          throw new Error('Can not set directive params property');
        },
        get: () => {
          return this.get(key);
        }
      });
    }
  }

  getElement() {
    return this[_element];
  }

  getAttributes() {
    return this[_attrValues];
  }

  isAvailable(key) {
    return this[_definitions].indexOf(key) != -1;
  }

  get(key) {
    if (!this.isAvailable(key)) {
      throw new Error(`"${this[_name]}" - directive param "${key}" is not defined at directive config`);
    }

    let value = null;

    if (this[_options].hasOwnProperty(key)) {
      value = this[_options][key];
    } else if (this[_pipes].hasOwnProperty(key)) {
      value = this[_pipes][key];
    } else {
      value = this[_scope][key];
    }

    return value;
  }

  watch(key, cb) {
    if (!this.isAvailable(key)) {
      throw new Error(`"${this[_name]}" - can not initialize watcher for "${key}" because this params is not defined at directive config`);
    }

    return this[_watcher].watch(key, cb);
  }

  attr(key) {
    return this[_attrValues][key];
  }

  hasAttr(key) {
    return this[_attrValues].hasOwnProperty(key);
  }

  parse(key) {
    let $scope = this[_scope];
    let $attrs = this[_attrs];

    let parsed = undefined;

    // parse - means that we parse attribute value form parent scope
    if ($attrs.hasOwnProperty(key)) {
      let expression = $attrs[key];
      let scopeToParse = this[_isIsolated] ? $scope.$parent : $scope;

      parsed = scopeToParse.$eval(expression);
    } else {
      //throw new Error(`"${this[_name]}" - can not parse "${key}" because this params is not passed to attributes`);
    }

    return parsed;
  }
}
<|endoftext|>


<commit_before>import Document from 'orbit/document';
import Evented from 'orbit/evented';
import { expose, isArray } from 'orbit/lib/objects';
import { OperationNotAllowed } from './lib/exceptions';

/**
 `Cache` provides a thin wrapper over an internally maintained instance of a
 `Document`.

 `Cache` prepares records to be cached according to a specified schema. The
 schema also determines the paths at which records will be stored.

 Once cached, data can be accessed at a particular path with `retrieve`. The
 size of data at a path can be accessed with `length`.

 @class Cache
 @namespace OC
 @param {OC.Schema} schema
 @param {Object}  [options]
 @param {Boolean} [options.trackRefs=true] Should `refs` be maintained for each record, indicating which other records reference them?
 @param {Boolean} [options.trackChanges=true] Should the `didTransform` event be triggered after calls to `tranform`?
 @param {Boolean} [options.trackRefChanges=false] Should the `didTransform` event be triggered after references are updated?
 @constructor
 */
var Cache = function() {
  this.init.apply(this, arguments);
};

Cache.prototype = {
  constructor: Cache,

  init: function(schema, options) {
    options = options || {};
    this.trackRefs = options.trackRefs !== undefined ? options.trackRefs : true;
    this.trackChanges = options.trackChanges !== undefined ? options.trackChanges : true;
    this.trackRefChanges = options.trackRefChanges !== undefined ? options.trackRefChanges : false;

    this._doc = new Document(null, {arrayBasedPaths: true});

    Evented.extend(this);

    // Expose methods from the Document interface
    expose(this, this._doc, 'reset');

    this.schema = schema;
    for (var model in schema.models) {
      if (schema.models.hasOwnProperty(model)) {
        this._doc.add([model], {});
      }
    }
  },

  /**
   Return the size of data at a particular path

   @method length
   @param path
   @returns {Number}
   */
  length: function(path) {
    var data = this.retrieve(path);
    if (data === null) {
      return null;
    } else if (isArray(data)) {
      return data.length;
    } else {
      return Object.keys(data).length;
    }
  },

  /**
   Return data at a particular path.

   Returns `null` if the path does not exist in the document.

   @method retrieve
   @param path
   @returns {Object}
   */
  retrieve: function(path) {
    try {
      return this._doc.retrieve(path);
    } catch(e) {
      return null;
    }
  },

  /**
   Transforms the document with an RFC 6902-compliant operation.

   Currently limited to `add`, `remove` and `replace` operations.

   Throws `PathNotFoundException` if the path does not exist in the document.

   @method transform
   @param {Object} operation
   @param {String} operation.op Must be "add", "remove", or "replace"
   @param {Array or String} operation.path Path to target location
   @param {Object} operation.value Value to set. Required for "add" and "replace"
   */
  transform: function(operation) {
    var op = operation.op,
        path = operation.path;

    path = this._doc.deserializePath(path);

    if (op !== 'add' && op !== 'remove' && op !== 'replace') {
      throw new OperationNotAllowed('Cache#transform requires an "add", "remove" or "replace" operation.');
    }

    if (path.length < 2) {
      throw new OperationNotAllowed('Cache#transform requires an operation with a path >= 2 segments.');
    }

    if (this.trackRefs && (op === 'remove' || op === 'replace')) {
      this._removeRefs(path);
    }

    this._transform(operation, this.trackChanges);

    if (this.trackRefs && (op === 'add' || op === 'replace')) {
      this._addRefs(path, operation.value);
    }
  },

  _transform: function(operation, track) {
    console.log('_transform', operation, track);
    if (track) {
      var inverse = this._doc.transform(operation, true);
      this.emit('didTransform', operation, inverse);

    } else {
      this._doc.transform(operation, false);
    }
  },

  _addRefs: function(path, value) {
//    console.log('_addRefs', path, value);
    if (value) {
      var _this = this,
          type = path[0],
          id = path[1],
          linkSchema,
          linkValue;

      if (path.length === 2) {
        // when a whole record is added, add inverse links for every link
        if (value.links) {
          Object.keys(value.links).forEach(function (link) {
            linkSchema = _this.schema.models[type].links[link];
            linkValue = value.links[link];

            if (linkSchema.type === 'hasMany') {
              Object.keys(linkValue).forEach(function(v) {
                _this._addRef(linkSchema, type, id, link, v);
              });

            } else {
              _this._addRef(linkSchema, type, id, link, linkValue);
            }
          });
        }

      } else if (path.length > 3) {
        var link = path[3];

        linkSchema = _this.schema.models[type].links[link];

        if (path.length === 5) {
          linkValue = path[4];
        } else {
          linkValue = value;
        }

        this._addRef(linkSchema, type, id, link, linkValue);
      }
    }
  },

  _addRef: function(linkSchema, type, id, link, value) {
    console.log('_addRef', linkSchema, type, id, link, value);

    if (value && typeof value === 'string') {
      var linkPath = [type, id, 'links', link];
      if (linkSchema.type === 'hasMany') {
        linkPath.push(value);
      }
      linkPath = '/' + linkPath.join('/');

      var refsPath = [linkSchema.model, value, 'refs'];
      var refs = this.retrieve(refsPath);
      if (!refs) {
        refs = {};
        refs[linkPath] = true;
        this._transformRef('add', refsPath, refs);

      } else {
        refsPath.push(linkPath);
        refs = this.retrieve(refsPath);
        if (!refs) {
          this._transformRef('add', refsPath, true);
        }
      }
    }
  },

  _removeRefs: function(path) {
//    console.log('_removeRefs', path);

    var value = this.retrieve(path);
    if (value) {
      var _this = this,
          type = path[0],
          id = path[1],
          linkSchema,
          linkValue;

      if (path.length === 2) {
        // when a whole record is removed, remove any links that reference it
        if (value.refs) {
          console.log('removeRefs from deleted record', type, id, value.refs);

          var operation;
          Object.keys(value.refs).forEach(function(path) {
            path = _this._doc.deserializePath(path);

            if (path.length === 4) {
              operation = {
                op: 'replace',
                path: path,
                value: null
              };
            } else {
              operation = {
                op: 'remove',
                path: path
              };
            }

            try {
              _this._transform(operation, _this.trackChanges);
            } catch(e) {
              console.log('Cache._transform() exception:', e, 'operation:', operation);
            }
          });
        }

        // when a whole record is removed, remove references corresponding to each link
        if (value.links) {
          Object.keys(value.links).forEach(function (link) {
            linkSchema = _this.schema.models[type].links[link];
            linkValue = value.links[link];

            if (linkSchema.type === 'hasMany') {
              Object.keys(linkValue).forEach(function(v) {
                _this._removeRef(linkSchema, type, id, link, v);
              });

            } else {
              _this._removeRef(linkSchema, type, id, link, linkValue);
            }
          });
        }

      } else if (path.length > 3) {
        var link = path[3];

        linkSchema = _this.schema.models[type].links[link];

        if (path.length === 5) {
          linkValue = path[4];
        } else {
          linkValue = value;
        }

        this._removeRef(linkSchema, type, id, link, linkValue);
      }
    }
  },

  _removeRef: function(linkSchema, type, id, link, value) {
    console.log('_removeRef', linkSchema, type, id, link, value);

    if (value && typeof value === 'string') {
      var linkPath = [type, id, 'links', link];
      if (linkSchema.type === 'hasMany') {
        linkPath.push(value);
      }
      linkPath = '/' + linkPath.join('/');

      var refsPath = [linkSchema.model, value, 'refs', linkPath];
      this._transformRef('remove', refsPath);
    }
  },

  _transformRef: function(op, path, value) {
    var operation = {
      op: op,
      path: path
    };
    if (value) {
      operation.value = value;
    }
    try {
      this._transform(operation, this.trackRefChanges);
    } catch(e) {
      console.log('Cache._transformRef() exception', e, 'for operation', operation);
    }
  }
};

export default Cache;<commit_msg>Comment out logging<commit_after>import Document from 'orbit/document';
import Evented from 'orbit/evented';
import { expose, isArray } from 'orbit/lib/objects';
import { OperationNotAllowed } from './lib/exceptions';

/**
 `Cache` provides a thin wrapper over an internally maintained instance of a
 `Document`.

 `Cache` prepares records to be cached according to a specified schema. The
 schema also determines the paths at which records will be stored.

 Once cached, data can be accessed at a particular path with `retrieve`. The
 size of data at a path can be accessed with `length`.

 @class Cache
 @namespace OC
 @param {OC.Schema} schema
 @param {Object}  [options]
 @param {Boolean} [options.trackRefs=true] Should `refs` be maintained for each record, indicating which other records reference them?
 @param {Boolean} [options.trackChanges=true] Should the `didTransform` event be triggered after calls to `tranform`?
 @param {Boolean} [options.trackRefChanges=false] Should the `didTransform` event be triggered after references are updated?
 @constructor
 */
var Cache = function() {
  this.init.apply(this, arguments);
};

Cache.prototype = {
  constructor: Cache,

  init: function(schema, options) {
    options = options || {};
    this.trackRefs = options.trackRefs !== undefined ? options.trackRefs : true;
    this.trackChanges = options.trackChanges !== undefined ? options.trackChanges : true;
    this.trackRefChanges = options.trackRefChanges !== undefined ? options.trackRefChanges : false;

    this._doc = new Document(null, {arrayBasedPaths: true});

    Evented.extend(this);

    // Expose methods from the Document interface
    expose(this, this._doc, 'reset');

    this.schema = schema;
    for (var model in schema.models) {
      if (schema.models.hasOwnProperty(model)) {
        this._doc.add([model], {});
      }
    }
  },

  /**
   Return the size of data at a particular path

   @method length
   @param path
   @returns {Number}
   */
  length: function(path) {
    var data = this.retrieve(path);
    if (data === null) {
      return null;
    } else if (isArray(data)) {
      return data.length;
    } else {
      return Object.keys(data).length;
    }
  },

  /**
   Return data at a particular path.

   Returns `null` if the path does not exist in the document.

   @method retrieve
   @param path
   @returns {Object}
   */
  retrieve: function(path) {
    try {
      return this._doc.retrieve(path);
    } catch(e) {
      return null;
    }
  },

  /**
   Transforms the document with an RFC 6902-compliant operation.

   Currently limited to `add`, `remove` and `replace` operations.

   Throws `PathNotFoundException` if the path does not exist in the document.

   @method transform
   @param {Object} operation
   @param {String} operation.op Must be "add", "remove", or "replace"
   @param {Array or String} operation.path Path to target location
   @param {Object} operation.value Value to set. Required for "add" and "replace"
   */
  transform: function(operation) {
    var op = operation.op,
        path = operation.path;

    path = this._doc.deserializePath(path);

    if (op !== 'add' && op !== 'remove' && op !== 'replace') {
      throw new OperationNotAllowed('Cache#transform requires an "add", "remove" or "replace" operation.');
    }

    if (path.length < 2) {
      throw new OperationNotAllowed('Cache#transform requires an operation with a path >= 2 segments.');
    }

    if (this.trackRefs && (op === 'remove' || op === 'replace')) {
      this._removeRefs(path);
    }

    this._transform(operation, this.trackChanges);

    if (this.trackRefs && (op === 'add' || op === 'replace')) {
      this._addRefs(path, operation.value);
    }
  },

  _transform: function(operation, track) {
//    console.log('_transform', operation, track);
    if (track) {
      var inverse = this._doc.transform(operation, true);
      this.emit('didTransform', operation, inverse);

    } else {
      this._doc.transform(operation, false);
    }
  },

  _addRefs: function(path, value) {
//    console.log('_addRefs', path, value);
    if (value) {
      var _this = this,
          type = path[0],
          id = path[1],
          linkSchema,
          linkValue;

      if (path.length === 2) {
        // when a whole record is added, add inverse links for every link
        if (value.links) {
          Object.keys(value.links).forEach(function (link) {
            linkSchema = _this.schema.models[type].links[link];
            linkValue = value.links[link];

            if (linkSchema.type === 'hasMany') {
              Object.keys(linkValue).forEach(function(v) {
                _this._addRef(linkSchema, type, id, link, v);
              });

            } else {
              _this._addRef(linkSchema, type, id, link, linkValue);
            }
          });
        }

      } else if (path.length > 3) {
        var link = path[3];

        linkSchema = _this.schema.models[type].links[link];

        if (path.length === 5) {
          linkValue = path[4];
        } else {
          linkValue = value;
        }

        this._addRef(linkSchema, type, id, link, linkValue);
      }
    }
  },

  _addRef: function(linkSchema, type, id, link, value) {
//    console.log('_addRef', linkSchema, type, id, link, value);

    if (value && typeof value === 'string') {
      var linkPath = [type, id, 'links', link];
      if (linkSchema.type === 'hasMany') {
        linkPath.push(value);
      }
      linkPath = '/' + linkPath.join('/');

      var refsPath = [linkSchema.model, value, 'refs'];
      var refs = this.retrieve(refsPath);
      if (!refs) {
        refs = {};
        refs[linkPath] = true;
        this._transformRef('add', refsPath, refs);

      } else {
        refsPath.push(linkPath);
        refs = this.retrieve(refsPath);
        if (!refs) {
          this._transformRef('add', refsPath, true);
        }
      }
    }
  },

  _removeRefs: function(path) {
//    console.log('_removeRefs', path);

    var value = this.retrieve(path);
    if (value) {
      var _this = this,
          type = path[0],
          id = path[1],
          linkSchema,
          linkValue;

      if (path.length === 2) {
        // when a whole record is removed, remove any links that reference it
        if (value.refs) {
//          console.log('removeRefs from deleted record', type, id, value.refs);

          var operation;
          Object.keys(value.refs).forEach(function(path) {
            path = _this._doc.deserializePath(path);

            if (path.length === 4) {
              operation = {
                op: 'replace',
                path: path,
                value: null
              };
            } else {
              operation = {
                op: 'remove',
                path: path
              };
            }

            try {
              _this._transform(operation, _this.trackChanges);
            } catch(e) {
              console.log('Cache._transform() exception:', e, 'operation:', operation);
            }
          });
        }

        // when a whole record is removed, remove references corresponding to each link
        if (value.links) {
          Object.keys(value.links).forEach(function (link) {
            linkSchema = _this.schema.models[type].links[link];
            linkValue = value.links[link];

            if (linkSchema.type === 'hasMany') {
              Object.keys(linkValue).forEach(function(v) {
                _this._removeRef(linkSchema, type, id, link, v);
              });

            } else {
              _this._removeRef(linkSchema, type, id, link, linkValue);
            }
          });
        }

      } else if (path.length > 3) {
        var link = path[3];

        linkSchema = _this.schema.models[type].links[link];

        if (path.length === 5) {
          linkValue = path[4];
        } else {
          linkValue = value;
        }

        this._removeRef(linkSchema, type, id, link, linkValue);
      }
    }
  },

  _removeRef: function(linkSchema, type, id, link, value) {
//    console.log('_removeRef', linkSchema, type, id, link, value);

    if (value && typeof value === 'string') {
      var linkPath = [type, id, 'links', link];
      if (linkSchema.type === 'hasMany') {
        linkPath.push(value);
      }
      linkPath = '/' + linkPath.join('/');

      var refsPath = [linkSchema.model, value, 'refs', linkPath];
      this._transformRef('remove', refsPath);
    }
  },

  _transformRef: function(op, path, value) {
    var operation = {
      op: op,
      path: path
    };
    if (value) {
      operation.value = value;
    }
    try {
      this._transform(operation, this.trackRefChanges);
    } catch(e) {
      console.log('Cache._transformRef() exception', e, 'for operation', operation);
    }
  }
};

export default Cache;<|endoftext|>